From 66cea5a2bf36c98311f557817785ab0b3ad6eeee Mon Sep 17 00:00:00 2001 From: Moritz Date: Sat, 15 Nov 2025 01:04:03 +0100 Subject: [PATCH] basic uci protocol --- src/engine.rs | 36 +++++++ src/lib.rs | 1 + src/main.rs | 60 ++++++++--- src/movegen/legal_check.rs | 2 +- src/parsing.rs | 13 +-- src/search/minimax.rs | 6 +- tests/bishop_move_generation.rs | 85 --------------- tests/is_square_attacked.rs | 52 --------- tests/king_move_generation.rs | 145 -------------------------- tests/knight_move_generation.rs | 96 ----------------- tests/make_undo.rs | 108 ------------------- tests/move_to_algebraic_conversion.rs | 92 ---------------- tests/pawn_move_generation.rs | 142 ------------------------- tests/perft.rs | 4 +- tests/queen_move_generation.rs | 93 ----------------- tests/rook_move_generation.rs | 101 ------------------ 16 files changed, 94 insertions(+), 942 deletions(-) create mode 100644 src/engine.rs delete mode 100644 tests/bishop_move_generation.rs delete mode 100644 tests/is_square_attacked.rs delete mode 100644 tests/king_move_generation.rs delete mode 100644 tests/knight_move_generation.rs delete mode 100644 tests/make_undo.rs delete mode 100644 tests/move_to_algebraic_conversion.rs delete mode 100644 tests/pawn_move_generation.rs delete mode 100644 tests/queen_move_generation.rs delete mode 100644 tests/rook_move_generation.rs diff --git a/src/engine.rs b/src/engine.rs new file mode 100644 index 0000000..c80d3ac --- /dev/null +++ b/src/engine.rs @@ -0,0 +1,36 @@ +// ... (your use statements) +use crate::board::Board; +use crate::search::minimax::minimax; + +pub struct Engine { + pub name: String, + pub author: String, + pub board: Board, +} + +impl Engine { + pub fn new(name: String, author: String) -> Engine { + // Use the standard starting position + let board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + Engine { + name, + author, + board + } + } + + pub fn setpos(&mut self, fen: &str) { + self.board = Board::from_fen(fen); + } + + pub fn search(&mut self, depth: u8) { + let (opt_move, _score) = minimax(&mut self.board, depth); + + if let Some(mv) = opt_move { + println!("bestmove {}", mv); + } else { + // UCI format for no legal moves (checkmate/stalemate) + println!("bestmove null"); + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4dfce6b..955196f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,3 +6,4 @@ pub mod eval; pub mod display; pub mod parsing; pub mod search; +pub mod engine; diff --git a/src/main.rs b/src/main.rs index 6a74aa2..e249057 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,52 @@ -use chess_engine::board::Board; -use chess_engine::movegen::generate_pseudo_legal_moves; -use chess_engine::movegen::legal_check::is_king_attacked; -use chess_engine::r#move::*; -use chess_engine::search::minimax; -use chess_engine::search::minimax::minimax; +use std::io::{self, BufRead}; +use chess_engine::engine::Engine; fn main() { - let mut board = Board::from_fen("rnb1kbnr/pppppppp/8/8/8/4q3/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); - let (opt_move, _) = minimax(&mut board, 5); - if let Some(mv) = opt_move { - println!("Found best move: {}", mv) - } else { - println!("No moves found") + // Create a new engine instance + let mut engine = Engine::new("Yakari".to_string(), "EiSiMo".to_string()); + + loop { + // Start the main UCI loop + for line in io::stdin().lock().lines() { + let input = line.unwrap_or_else(|_| "quit".to_string()); + let tokens: Vec<&str> = input.split_whitespace().collect(); + + if tokens.is_empty() { + continue; + } + + match tokens[0] { + "uci" => { + println!("id name {}", engine.name); + println!("id author {}", engine.author); + println!("uciok"); + } + "isready" => { + println!("readyok"); + } + "position" => { + // Example: "position startpos moves e2e4 e7e5" + // Or: "position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" + // You'll need to write a parser for this! + // For now, let's just handle the "fen" part simply. + if tokens.len() > 1 && tokens[1] == "fen" { + let fen = tokens[2..].join(" "); + engine.setpos(&fen); + } + } + "go" => { + // Example: "go depth 6" + // For now, we'll just use the fixed depth from your search function. + engine.search(5); + } + "quit" => { + break; // Exit the loop and the program + } + _ => { + // Unknown command, just ignore + } + } + } } + } diff --git a/src/movegen/legal_check.rs b/src/movegen/legal_check.rs index 26fe5a8..0c276a2 100644 --- a/src/movegen/legal_check.rs +++ b/src/movegen/legal_check.rs @@ -4,7 +4,7 @@ use crate::movegen::tables::{get_bishop_attacks, get_rook_attacks, ATTACKING_PAW use crate::square::{Square, SQUARES}; /// Checks if the king of the side that is NOT to move is in check -pub fn is_king_attacked(board: &Board) -> bool { +pub fn is_other_king_attacked(board: &Board) -> bool { let king = board.pieces[PieceType::King as usize][!board.side_to_move as usize]; is_square_attacked(board, SQUARES[king.trailing_zeros() as usize], board.side_to_move) } diff --git a/src/parsing.rs b/src/parsing.rs index 96c3c67..421f6a4 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -250,16 +250,6 @@ impl Move { pub fn to_algebraic(&self) -> String { let flags = self.get_flags(); - // Handle castling first. In this new format, the "to" square is - // the *king's* destination square (g1/c1 or g8/c8). - // Your old implementation reading the file is still fine. - if (flags == MOVE_FLAG_WK_CASTLE) || (flags == MOVE_FLAG_BK_CASTLE) { - return "O-O".to_string(); - } - if (flags == MOVE_FLAG_WQ_CASTLE) || (flags == MOVE_FLAG_BQ_CASTLE) { - return "O-O-O".to_string(); - } - let from_val = self.0 & MOVE_FROM_MASK; let to_val = (self.0 & MOVE_TO_MASK) >> 6; @@ -281,4 +271,7 @@ impl Move { format!("{}{}", from_str, to_str) } } + + // TODO + // pub fn from_algebraic(s: &str, board: &Board) -> Move {} } \ No newline at end of file diff --git a/src/search/minimax.rs b/src/search/minimax.rs index c8854e2..622197f 100644 --- a/src/search/minimax.rs +++ b/src/search/minimax.rs @@ -1,7 +1,7 @@ use crate::board::{Board, Color}; // <-- Assuming you have a Color enum (e.g., Color::White, Color::Black) use crate::eval::basic::evaluate_board; use crate::movegen::generate_pseudo_legal_moves; -use crate::movegen::legal_check::is_king_attacked; +use crate::movegen::legal_check::is_other_king_attacked; use crate::r#move::{Move, MoveList}; @@ -26,7 +26,7 @@ pub fn minimax(board: &mut Board, depth: u8) -> (Option, i32) { for mv in list.iter() { let undo_mv = board.make_move(*mv); - let is_illegal = is_king_attacked(board); + let is_illegal = is_other_king_attacked(board); if is_illegal { board.undo_move(undo_mv); continue; @@ -44,7 +44,7 @@ pub fn minimax(board: &mut Board, depth: u8) -> (Option, i32) { } if !legal_moves_found { - if is_king_attacked(board) { + if is_other_king_attacked(board) { return (None, -i32::MAX); } else { return (None, 0); diff --git a/tests/bishop_move_generation.rs b/tests/bishop_move_generation.rs deleted file mode 100644 index 366a73c..0000000 --- a/tests/bishop_move_generation.rs +++ /dev/null @@ -1,85 +0,0 @@ -use chess_engine::board::Board; -use chess_engine::movegen::sliders::generate_bishop_moves; -use chess_engine::r#move::MoveList; - -/// Compares two move list strings ignoring the order of moves. -fn assert_moves_equal(actual_str: &str, expected_str: &str) { - let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); - let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); - - actual_moves.sort(); - expected_moves.sort(); - - assert_eq!(actual_moves, expected_moves); -} - -#[test] -fn test_bishop_moves_single() { - let fen_standard = "8/8/8/1p1p4/2B5/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen_standard); - let mut list = MoveList::new(); - generate_bishop_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "c4b5 c4d5 c4b3 c4d3 c4a2 c4e2 c4f1"); -} - -#[test] -fn test_bishop_moves_empty_board_center() { - // Läufer in der Mitte (e4) auf einem leeren Brett - let fen = "8/8/8/8/4B3/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_bishop_moves(&board, &mut list); - - let expected = "e4d3 e4c2 e4b1 e4f5 e4g6 e4h7 e4d5 e4c6 e4b7 e4a8 e4f3 e4g2 e4h1"; - assert_moves_equal(&list.to_string(), expected); -} - -#[test] -fn test_bishop_moves_from_corner() { - // Läufer in der Ecke (a1) auf einem leeren Brett - let fen = "8/8/8/8/8/8/8/B7 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_bishop_moves(&board, &mut list); - - let expected = "a1b2 a1c3 a1d4 a1e5 a1f6 a1g7 a1h8"; - assert_moves_equal(&list.to_string(), expected); -} - -#[test] -fn test_bishop_moves_friendly_blockers() { - // Läufer auf c1, blockiert von EIGENEN Bauern auf b2 und d2. - // Darf b2/d2 NICHT schlagen und nicht darüber springen. - let fen = "8/8/8/8/8/8/1P1P4/2B5 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_bishop_moves(&board, &mut list); - - // Es sollten KEINE Züge generiert werden. - assert_moves_equal(&list.to_string(), ""); -} - -#[test] -fn test_bishop_moves_mixed_blockers_and_captures() { - let fen = "8/8/1p3P2/8/3B4/8/1p3P2/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_bishop_moves(&board, &mut list); - - let expected = "d4c3 d4e3 d4b2 d4c5 d4b6 d4e5"; - assert_moves_equal(&list.to_string(), expected); -} - -#[test] -fn test_bishop_moves_black_turn() { - // Schwarzer Läufer auf c5, weiße Bauern auf b4 und d4. - let fen = "8/8/8/2b5/1P1P4/8/8/8 b - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_bishop_moves(&board, &mut list); - - // Schlagzüge: b4, d4 - // Ruhige Züge: b6, a7, d6, e7, f8 - let expected = "c5b4 c5d4 c5b6 c5a7 c5d6 c5e7 c5f8"; - assert_moves_equal(&list.to_string(), expected); -} diff --git a/tests/is_square_attacked.rs b/tests/is_square_attacked.rs deleted file mode 100644 index a028028..0000000 --- a/tests/is_square_attacked.rs +++ /dev/null @@ -1,52 +0,0 @@ -use chess_engine::board::{Board, Color}; -use chess_engine::movegen::legal_check::is_square_attacked; -use chess_engine::square::Square; - - -fn assert_square_attacked(board: &mut Board, square: Square, white: bool, black: bool) { - assert_eq!(is_square_attacked(board, square, Color::White), white, "{}", square); - assert_eq!(is_square_attacked(board, square, Color::Black), black, "{}", square); -} - -#[test] -fn test_is_attacked_kiwipete() { - let mut board = Board::from_fen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1"); - assert_square_attacked(&mut board, Square::B1, true, false); - assert_square_attacked(&mut board, Square::C1, true, false); - assert_square_attacked(&mut board, Square::D1, true, false); - assert_square_attacked(&mut board, Square::F1, true, false); - assert_square_attacked(&mut board, Square::G1, true, false); - - assert_square_attacked(&mut board, Square::A3, true, true); - assert_square_attacked(&mut board, Square::B3, true, false); - assert_square_attacked(&mut board, Square::D3, true, true); - assert_square_attacked(&mut board, Square::E3, true, false); - assert_square_attacked(&mut board, Square::G3, true, false); - - assert_square_attacked(&mut board, Square::A4, true, true); - assert_square_attacked(&mut board, Square::C4, true, true); - assert_square_attacked(&mut board, Square::D4, false, false); - assert_square_attacked(&mut board, Square::F4, true, false); - assert_square_attacked(&mut board, Square::G4, true, true); - assert_square_attacked(&mut board, Square::H4, false, true); - - assert_square_attacked(&mut board, Square::A5, false, false); - assert_square_attacked(&mut board, Square::B5, true, true); - assert_square_attacked(&mut board, Square::C5, false, true); - assert_square_attacked(&mut board, Square::F5, true, true); - assert_square_attacked(&mut board, Square::G5, true, false); - assert_square_attacked(&mut board, Square::H5, true, true); - - assert_square_attacked(&mut board, Square::C6, true, true); - assert_square_attacked(&mut board, Square::D6, false, true); - assert_square_attacked(&mut board, Square::H6, true, true); - - assert_square_attacked(&mut board, Square::B7, false, true); - assert_square_attacked(&mut board, Square::H7, false, true); - - assert_square_attacked(&mut board, Square::B8, false, true); - assert_square_attacked(&mut board, Square::C8, false, true); - assert_square_attacked(&mut board, Square::D8, false, true); - assert_square_attacked(&mut board, Square::F8, false, true); - assert_square_attacked(&mut board, Square::G8, false, true); -} \ No newline at end of file diff --git a/tests/king_move_generation.rs b/tests/king_move_generation.rs deleted file mode 100644 index 107b048..0000000 --- a/tests/king_move_generation.rs +++ /dev/null @@ -1,145 +0,0 @@ -use chess_engine::board::Board; -use chess_engine::movegen::non_sliders::generate_king_moves; -use chess_engine::r#move::MoveList; - -/// Compares two move list strings ignoring the order of moves. -fn assert_moves_equal(actual_str: &str, expected_str: &str) { - let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); - let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); - - actual_moves.sort(); - expected_moves.sort(); - - assert_eq!(actual_moves, expected_moves); -} - -#[test] -fn test_king_moves_start_pos_blocked() { - let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - let board = Board::from_fen(fen_standard); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - // King is completely blocked in the start position - assert_moves_equal(&list.to_string(), ""); -} - -#[test] -fn test_king_moves_center() { - let fen = "8/8/8/8/4K3/8/8/8 w - - 0 1"; // King on e4 - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - // 8 moves from e4 - assert_moves_equal(&list.to_string(), "e4d3 e4d4 e4d5 e4e3 e4e5 e4f3 e4f4 e4f5"); -} - -#[test] -fn test_king_moves_corner() { - let fen = "K7/8/8/8/8/8/8/8 w - - 0 1"; // King on a8 - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - // 3 moves from a8 - assert_moves_equal(&list.to_string(), "a8a7 a8b7 a8b8"); -} - -#[test] -fn test_king_moves_blocked_friendly() { - // King on d4, surrounded by friendly pawns - let fen = "8/8/8/3P1P2/3K4/3P1P2/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - // d3, d5, f3, f5 are blocked. - // c3, c4, c5, e3, e4, e5 are free. - assert_moves_equal(&list.to_string(), "d4c3 d4c4 d4c5 d4e3 d4e4 d4e5"); -} - -#[test] -fn test_king_moves_capture_and_blocked() { - // King on d4 - // Friendly: c3, e5 - // Enemy: c5, e3 - let fen = "8/8/8/2p1P3/3K4/2P1p3/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - // Blocked: c3, e5 - // Captures: c5, e3 - // Empty: c4, d3, d5, e4, f3, f4, f5 - // Note: f3, f4, f5 are valid moves - assert_moves_equal(&list.to_string(), "d4c4 d4d3 d4d5 d4e4 d4c5 d4e3"); -} - -#[test] -fn test_king_moves_black() { - let fen = "8/8/8/8/4k3/8/8/8 b - - 0 1"; // Black king on e4 - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - // 8 moves from e4 - assert_moves_equal(&list.to_string(), "e4d3 e4d4 e4d5 e4e3 e4e5 e4f3 e4f4 e4f5"); -} - -#[test] -fn test_king_moves_castling_white_all() { - // King on e1, rooks on a1, h1. All rights. No pieces between. - let fen = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - // 5 standard moves + 2 castling moves (e1g1, e1c1) - assert_moves_equal(&list.to_string(), "e1d1 e1d2 e1e2 e1f1 e1f2 O-O O-O-O"); -} - -#[test] -fn test_king_moves_castling_black_all() { - // King on e8, rooks on a8, h8. All rights. No pieces between. - let fen = "r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - // 5 standard moves + 2 castling moves (e8g8, e8c8) - assert_moves_equal(&list.to_string(), "O-O O-O-O e8d8 e8e7 e8f8 e8d7 e8f7"); -} - -#[test] -fn test_king_moves_castling_blocked_pieces_white() { - // White: Queenside blocked by knight - let fen = "r3k2r/8/8/8/8/8/8/RN2K2R w KQkq - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - // Should only get Kingside castling (e1g1) and standard moves - assert_moves_equal(&list.to_string(), "e1d1 e1d2 e1e2 e1f1 e1f2 O-O"); -} - -#[test] -fn test_king_moves_castling_blocked_pieces_black() { - // Black: Kingside blocked by bishop - let fen = "r3kb1r/8/8/8/8/8/8/R3K2R b KQkq - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "e8d8 e8d7 e8e7 e8f7 O-O-O"); -} - -#[test] -fn test_king_moves_castling_no_rights() { - // Same as `test_king_moves_castling_white_all` but no castling rights - let fen = "r3k2r/8/8/8/8/8/8/R3K2R w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - // 5 standard moves, 0 castling - assert_moves_equal(&list.to_string(), "e1d1 e1d2 e1e2 e1f1 e1f2"); -} - -#[test] -fn test_king_moves_empty_board() { - let fen = "8/8/8/8/8/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_king_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), ""); -} \ No newline at end of file diff --git a/tests/knight_move_generation.rs b/tests/knight_move_generation.rs deleted file mode 100644 index 41fae3a..0000000 --- a/tests/knight_move_generation.rs +++ /dev/null @@ -1,96 +0,0 @@ -use chess_engine::board::Board; -use chess_engine::movegen::non_sliders::generate_knight_moves; -use chess_engine::r#move::MoveList; - -/// Compares two move list strings ignoring the order of moves. -fn assert_moves_equal(actual_str: &str, expected_str: &str) { - let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); - let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); - - actual_moves.sort(); - expected_moves.sort(); - - assert_eq!(actual_moves, expected_moves); -} - -#[test] -fn test_knight_move_generation() { - let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - let board = Board::from_fen(fen_standard); - let mut list = MoveList::new(); - generate_knight_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "b1a3 b1c3 g1f3 g1h3"); -} - -#[test] -fn test_knight_move_generation_black() { - let fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_knight_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "b8a6 b8c6 g8f6 g8h6"); -} - -#[test] -fn test_knight_moves_center() { - let fen = "8/8/8/3N4/8/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_knight_moves(&board, &mut list); - // 8 moves from d5 - assert_moves_equal(&list.to_string(), "d5b4 d5b6 d5c3 d5c7 d5e3 d5e7 d5f4 d5f6"); -} - -#[test] -fn test_knight_moves_capture() { - // Black knight on e5, white pawn on d3 - let fen = "8/8/8/4n3/8/3P4/8/8 b - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_knight_moves(&board, &mut list); - // 8 moves, including capture on d3 - assert_moves_equal(&list.to_string(), "e5c4 e5c6 e5d3 e5d7 e5f3 e5f7 e5g4 e5g6"); -} - -#[test] -fn test_knight_moves_blocked_friendly() { - // Knight on d4, some moves are blocked, some are not. - let fen = "8/8/3P1P2/3P1P2/3N4/3P1P2/3P1P2/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_knight_moves(&board, &mut list); - // f3, f5, d3, d5, d6, f6 are blocked. - // c2, e2, b3, b5, c6, e6 are free. - assert_moves_equal(&list.to_string(), "d4c2 d4e2 d4b3 d4b5 d4c6 d4e6"); -} - -#[test] -fn test_knight_moves_corner() { - let fen = "N7/8/8/8/8/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_knight_moves(&board, &mut list); - // 2 moves from a8 - assert_moves_equal(&list.to_string(), "a8b6 a8c7"); -} - -#[test] -fn test_knight_moves_capture_and_blocked() { - // White knights on b1 and g1. - // b1: a3 (friendly), c3 (friendly), d2 (enemy) - // g1: f3 (empty), h3 (empty), e2 (empty) - let fen = "8/8/8/8/8/P1P5/3p4/1N4NR w K - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_knight_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "b1d2 g1e2 g1f3 g1h3"); -} - -#[test] -fn test_knight_moves_empty_board() { - let fen = "8/8/8/8/8/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_knight_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), ""); -} \ No newline at end of file diff --git a/tests/make_undo.rs b/tests/make_undo.rs deleted file mode 100644 index 6ba2806..0000000 --- a/tests/make_undo.rs +++ /dev/null @@ -1,108 +0,0 @@ -use chess_engine::board::Board; -use chess_engine::movegen::generate_pseudo_legal_moves; -use chess_engine::r#move::MoveList; - -/// Helper function to run the make/undo integrity check for any given board state. -/// This avoids code duplication in all the specific test cases. -fn assert_make_undo_integrity(board: &mut Board) { - let mut list = MoveList::new(); - generate_pseudo_legal_moves(&board, &mut list); - - // If no moves are generated (e.g., stalemate/checkmate), the test passes. - if list.is_empty() { - return; - } - - for mv in list.iter() { - let board_before_make = board.to_fen(); - let undo_obj = board.make_move(*mv); - let board_after_make = board.to_fen(); - - // Ensure the board actually changed - assert_ne!( - board_before_make, board_after_make, - "Board did not change after make_move for move: {:?}", mv - ); - - board.undo_move(undo_obj); - let board_after_undo = board.to_fen(); - - // Ensure the board is perfectly restored - assert_eq!( - board_before_make, board_after_undo, - "Board state mismatch after undo_move for move: {:?}", mv - ); - } -} - -#[test] -fn test_make_undo_standard() { - let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - let mut board = Board::from_fen(fen_standard); - assert_make_undo_integrity(&mut board); -} - -#[test] -/// Tests a complex, mid-game position with many interactions. -/// This is the famous "Kiwipete" FEN used for perft testing. -fn test_make_undo_kiwipete() { - let fen = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1"; - let mut board = Board::from_fen(fen); - assert_make_undo_integrity(&mut board); -} - -#[test] -/// Tests the en passant capture for White (e.g., e5xd6). -/// This ensures the black pawn on d5 is correctly removed and restored. -fn test_make_undo_en_passant_white() { - // Position after 1. e4 e6 2. e5 d5 - let fen = "rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPP2PPP/RNBQKBNR w KQkq d6 0 3"; - let mut board = Board::from_fen(fen); - assert_make_undo_integrity(&mut board); -} - -#[test] -/// Tests the en passant capture for Black (e.g., d5xe6). -/// This ensures the white pawn on e5 is correctly removed and restored. -fn test_make_undo_en_passant_black() { - // Position after 1. d4 c5 2. d5 e5 - let fen = "rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PPP1PPPP/RNBQKBNR b KQkq e6 0 3"; - let mut board = Board::from_fen(fen); - assert_make_undo_integrity(&mut board); -} - -#[test] -/// Tests White's kingside (O-O) and queenside (O-O-O) castling. -/// Ensures both rook and king moves are correctly undone. -fn test_make_undo_castling_white() { - let fen = "r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1"; - let mut board = Board::from_fen(fen); - assert_make_undo_integrity(&mut board); -} - -#[test] -/// Tests Black's kingside (O-O) and queenside (O-O-O) castling. -/// Ensures both rook and king moves are correctly undone. -fn test_make_undo_castling_black() { - let fen = "r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R b KQkq - 0 1"; - let mut board = Board::from_fen(fen); - assert_make_undo_integrity(&mut board); -} - -#[test] -/// Tests white pawn promotions (quiet and capture) to Q, R, B, N. -fn test_make_undo_promotions_white() { - // White pawn on c7, black rooks on b8 and d8 to test capture-promotions - let fen = "1r1rkb1r/2Ppppb1/8/8/8/8/1PPPPPP1/RNBQKBNR w KQk - 0 1"; - let mut board = Board::from_fen(fen); - assert_make_undo_integrity(&mut board); -} - -#[test] -/// Tests black pawn promotions (quiet and capture) to q, r, b, n. -fn test_make_undo_promotions_black() { - // Black pawn on g2, white rooks on f1 and h1 to test capture-promotions - let fen = "RNBQKBNR/1PPPPP2/8/8/8/8/6p1/R4RK1 b Qkq - 0 1"; - let mut board = Board::from_fen(fen); - assert_make_undo_integrity(&mut board); -} \ No newline at end of file diff --git a/tests/move_to_algebraic_conversion.rs b/tests/move_to_algebraic_conversion.rs deleted file mode 100644 index ab6be5c..0000000 --- a/tests/move_to_algebraic_conversion.rs +++ /dev/null @@ -1,92 +0,0 @@ -use chess_engine::r#move::*; -use chess_engine::square::Square; - -#[test] -fn test_quiet_move_white_pawn() { - // Test 1: Standard Quiet Move (White Pawn) - // (from: E2, to: E4) - // NOTE: This was MOVE_TYPE_FLAG_QUIET, but in the new system it's a specific flag. - // The algebraic notation is the same, so we test with the new specific flag. - let m_quiet = Move::new(Square::E2, Square::E4, MOVE_FLAG_QUIET); - assert_eq!(m_quiet.to_algebraic(), "e2e4"); -} - -#[test] -fn test_quiet_move_black_knight() { - // Test 2: Standard Quiet Move (Black Knight) - // (from: B8, to: C6) - let m_knight = Move::new(Square::B8, Square::C6, MOVE_FLAG_QUIET); - assert_eq!(m_knight.to_algebraic(), "b8c6"); -} - -#[test] -fn test_en_passant_move() { - // Test 3: En Passant Move (Notation is same as quiet move) - // (from: E5, to: F6) - let m_ep = Move::new(Square::E5, Square::F6, MOVE_FLAG_EN_PASSANT); - assert_eq!(m_ep.to_algebraic(), "e5f6"); -} - -#[test] -fn test_promotion_to_queen() { - // Test 4: Promotion to Queen (Push) - // (from: E7, to: E8) - let m_promo_q = Move::new(Square::E7, Square::E8, MOVE_FLAG_PROMO_Q); - assert_eq!(m_promo_q.to_algebraic(), "e7e8q"); -} - -#[test] -fn test_promotion_to_rook() { - // Test 5: Promotion to Rook (Push) - // (from: A7, to: A8) - let m_promo_r = Move::new(Square::A7, Square::A8, MOVE_FLAG_PROMO_R); - assert_eq!(m_promo_r.to_algebraic(), "a7a8r"); -} - -#[test] -fn test_promotion_to_bishop() { - // Test 6: Promotion to Bishop (Capture) - // (from: G2, to: H1) - let m_promo_b = Move::new(Square::G2, Square::H1, MOVE_FLAG_PROMO_B_CAP); - assert_eq!(m_promo_b.to_algebraic(), "g2h1b"); -} - -#[test] -fn test_promotion_to_knight() { - // Test 7: Promotion to Knight (Capture) - // (from: G7, to: F8) - let m_promo_n = Move::new(Square::G7, Square::F8, MOVE_FLAG_PROMO_N_CAP); - assert_eq!(m_promo_n.to_algebraic(), "g7f8n"); -} - -#[test] -fn test_white_kingside_castling() { - // Test 8: White Kingside Castling - // (from: E1, to: G1) - let m_castle_wk = Move::new(Square::E1, Square::G1, MOVE_FLAG_WK_CASTLE); - assert_eq!(m_castle_wk.to_algebraic(), "O-O"); -} - -#[test] -fn test_white_queenside_castling() { - // Test 9: White Queenside Castling - // (from: E1, to: C1) - let m_castle_wq = Move::new(Square::E1, Square::C1, MOVE_FLAG_WQ_CASTLE); - assert_eq!(m_castle_wq.to_algebraic(), "O-O-O"); -} - -#[test] -fn test_black_kingside_castling() { - // Test 10: Black Kingside Castling - // (from: E8, to: G8) - let m_castle_bk = Move::new(Square::E8, Square::G8, MOVE_FLAG_BK_CASTLE); - assert_eq!(m_castle_bk.to_algebraic(), "O-O"); -} - -#[test] -fn test_black_queenside_castling() { - // Test 11: Black Queenside Castling - // (from: E8, to: C8) - let m_castle_bq = Move::new(Square::E8, Square::C8, MOVE_FLAG_BQ_CASTLE); - assert_eq!(m_castle_bq.to_algebraic(), "O-O-O"); -} diff --git a/tests/pawn_move_generation.rs b/tests/pawn_move_generation.rs deleted file mode 100644 index 70654d3..0000000 --- a/tests/pawn_move_generation.rs +++ /dev/null @@ -1,142 +0,0 @@ -use chess_engine::board::Board; -use chess_engine::movegen::pawns::generate_pawn_moves; -use chess_engine::r#move::MoveList; - -/// Compares two move list strings ignoring the order of moves. -fn assert_moves_equal(actual_str: &str, expected_str: &str) { - let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); - let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); - - actual_moves.sort(); - expected_moves.sort(); - - assert_eq!(actual_moves, expected_moves); -} - -#[test] -fn test_pawn_moves_start_pos() { - let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - let board = Board::from_fen(fen_standard); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "a2a3 a2a4 b2b3 b2b4 c2c3 c2c4 d2d3 d2d4 e2e3 e2e4 f2f3 f2f4 g2g3 g2g4 h2h3 h2h4"); -} - -#[test] -fn test_pawn_moves_black_start_pos() { - let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1"; - let board = Board::from_fen(fen_standard); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "a7a6 a7a5 b7b6 b7b5 c7c6 c7c5 d7d6 d7d5 e7e6 e7e5 f7f6 f7f5 g7g6 g7g5 h7h6 h7h5"); -} - -#[test] -fn test_pawn_moves_black_blocked_and_captures() { - // Black pawns on a7, c7, e7, g7. White pawns on b6, d6, f6. - let fen = "8/p1p1p1p1/1P1P1P2/8/8/8/8/8 b - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "a7a6 a7a5 a7b6 c7c6 c7c5 c7b6 c7d6 e7e6 e7e5 e7d6 e7f6 g7g6 g7g5 g7f6"); -} - -#[test] -fn test_pawn_moves_black_side_captures() { - // Test captures on A and H files (no wrap-around) - let fen = "8/8/8/8/8/1p4p1/P6P/8 b - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "b3b2 b3a2 g3g2 g3h2"); -} - -#[test] -fn test_pawn_moves_white_side_captures() { - // Test captures on A and H files (no wrap-around) - let fen = "8/p6p/1P4P1/8/8/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "b6b7 b6a7 g6g7 g6h7"); -} - -#[test] -fn test_pawn_moves_black_en_passant() { - // White just moved e2e4, en passant target is e3. Black pawn on d4. - let fen = "rnbqkbnr/ppp1pppp/8/8/3pP3/8/PPP2PPP/RNBQKBNR b KQkq e3 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "a7a6 a7a5 b7b6 b7b5 c7c6 c7c5 d4d3 d4e3 e7e6 e7e5 f7f6 f7f5 g7g6 g7g5 h7h6 h7h5"); -} - -#[test] -fn test_pawn_moves_white_en_passant() { - // Black just moved d7d5, en passant target is d6. White pawn on e5. - let fen = "rnbqkbnr/ppp1pppp/8/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "a2a3 a2a4 b2b3 b2b4 c2c3 c2c4 d2d3 d2d4 e5e6 e5d6 f2f3 f2f4 g2g3 g2g4 h2h3 h2h4"); -} - -#[test] -fn test_pawn_moves_black_promotion() { - let fen = "8/8/8/8/8/8/p1p1p1p1/8 b - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), - "a2a1q a2a1r a2a1b a2a1n c2c1q c2c1r c2c1b c2c1n e2e1q e2e1r e2e1b e2e1n g2g1q g2g1r g2g1b g2g1n"); -} - -#[test] -fn test_pawn_moves_white_promotion() { - let fen = "8/P1P1P1P1/8/8/8/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), - "a7a8q a7a8r a7a8b a7a8n c7c8q c7c8r c7c8b c7c8n e7e8q e7e8r e7e8b e7e8n g7g8q g7g8r g7g8b g7g8n"); -} - -#[test] -fn test_pawn_moves_black_promotion_capture() { - // Black pawn on b2. White rooks on a1 and c1. - let fen = "8/8/8/8/8/8/1p6/R1R5 b - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), - "b2b1q b2b1r b2b1b b2b1n b2a1q b2a1r b2a1b b2a1n b2c1q b2c1r b2c1b b2c1n"); -} - -#[test] -fn test_pawn_moves_white_promotion_capture() { - // White pawn on a7. Black rooks on b8 and d8. - let fen = "1r1r4/P7/8/8/8/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), - "a7a8q a7a8r a7a8b a7a8n a7b8q a7b8r a7b8b a7b8n"); -} - -#[test] -fn test_pawn_moves_black_midgame_random() { - let fen = "r1bqkb1r/1p2pppp/p1n2n2/3p4/2BNP3/2N5/PPP2PPP/R1BQK2R b KQkq - 1 6"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "a6a5 b7b6 b7b5 d5c4 d5e4 e7e6 e7e5 g7g6 g7g5 h7h6 h7h5"); -} - -#[test] -fn test_pawn_moves_white_midgame_random() { - let fen = "r1bqk2r/2ppbppp/p1n2n2/1p2p3/B1P1P3/5N2/PP1P1PPP/RNBQR1K1 w kq - 0 7"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_pawn_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "a2a3 b2b3 b2b4 c4c5 c4b5 d2d3 d2d4 g2g3 g2g4 h2h3 h2h4"); -} diff --git a/tests/perft.rs b/tests/perft.rs index fb4d2bd..ba69b25 100644 --- a/tests/perft.rs +++ b/tests/perft.rs @@ -1,6 +1,6 @@ use chess_engine::board::Board; use chess_engine::movegen::generate_pseudo_legal_moves; -use chess_engine::movegen::legal_check::is_king_attacked; +use chess_engine::movegen::legal_check::is_other_king_attacked; use chess_engine::r#move::MoveList; fn count_legal_moves_recursive(board: &mut Board, depth: u8) -> u64 { @@ -16,7 +16,7 @@ fn count_legal_moves_recursive(board: &mut Board, depth: u8) -> u64 { // Store the undo info when making the move let undo_info = board.make_move(*mv); - if !is_king_attacked(board) { + if !is_other_king_attacked(board) { leaf_nodes += count_legal_moves_recursive(board, depth - 1); } diff --git a/tests/queen_move_generation.rs b/tests/queen_move_generation.rs deleted file mode 100644 index d78f469..0000000 --- a/tests/queen_move_generation.rs +++ /dev/null @@ -1,93 +0,0 @@ -use chess_engine::board::Board; -use chess_engine::movegen::sliders::generate_queen_moves; -use chess_engine::r#move::MoveList; - -/// Compares two move list strings ignoring the order of moves. -fn assert_moves_equal(actual_str: &str, expected_str: &str) { - let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); - let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); - - actual_moves.sort(); - expected_moves.sort(); - - assert_eq!(actual_moves, expected_moves); -} - -#[test] -fn test_queen_moves_single_rook() { - let fen_standard = "8/1Q2p3/8/8/8/8/6p1/8 w - - 0 1"; - let board = Board::from_fen(fen_standard); - let mut list = MoveList::new(); - generate_queen_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "b7a8 b7a7 b7a6 b7b8 b7b6 b7b5 b7b4 b7b3 b7b2 b7b1 b7c8 b7c7 b7c6 b7d7 b7e7 b7d5 b7e4 b7f3 b7g2"); -} - -#[test] -fn test_queen_center_open() { - // Queen in the center, open board, should generate 27 moves - let fen = "4k3/8/8/8/3Q4/8/8/4K3 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_queen_moves(&board, &mut list); - let expected = "d4a1 d4b2 d4c3 d4e5 d4f6 d4g7 d4h8 \ - d4a4 d4b4 d4c4 d4e4 d4f4 d4g4 d4h4 \ - d4d1 d4d2 d4d3 d4d5 d4d6 d4d7 d4d8 \ - d4a7 d4b6 d4c5 d4e3 d4f2 d4g1"; - assert_moves_equal(&list.to_string(), expected); -} - -#[test] -fn test_queen_corner_blocked_friendly() { - // Queen in corner, completely blocked by friendly pieces - let fen = "rnbqkbnr/pppppppp/8/8/8/8/PP1PP1PP/QNBQKBNR w Kkq - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_queen_moves(&board, &mut list); - assert_moves_equal(&list.to_string(), "d1a4 d1b3 d1c2"); -} - -#[test] -fn test_queen_multiple_captures_black() { - // Black queen on h8, with multiple white pieces to capture - let fen = "q3k3/P1P1P1P1/8/8/8/P1P1P1P1/8/4K3 b - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_queen_moves(&board, &mut list); - let expected = "a8b8 a8c8 a8d8 a8a7 a8b7 a8c6 a8d5 a8e4 a8f3 a8g2 a8h1"; - assert_moves_equal(&list.to_string(), expected); -} - -#[test] -fn test_multiple_queens() { - let fen = "4k3/8/8/8/8/8/8/Q3K2Q w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_queen_moves(&board, &mut list); - let expected = "a1a2 a1a3 a1a4 a1a5 a1a6 a1a7 a1a8 a1b1 a1c1 a1d1 a1b2 a1c3 a1d4 a1e5 a1f6 a1g7 a1h8 \ - h1h2 h1h3 h1h4 h1h5 h1h6 h1h7 h1h8 h1g1 h1f1 h1g2 h1f3 h1e4 h1d5 h1c6 h1b7 h1a8"; - assert_moves_equal(&list.to_string(), expected); -} - -#[test] -fn test_queen_rook_only() { - // Queen on d4, bishop moves blocked by friendly pawns - let fen = "4k3/8/8/2P1P3/3Q4/2P1P3/8/4K3 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_queen_moves(&board, &mut list); - let expected = "d4a4 d4b4 d4c4 d4e4 d4f4 d4g4 d4h4 \ - d4d1 d4d2 d4d3 d4d5 d4d6 d4d7 d4d8"; - assert_moves_equal(&list.to_string(), expected); -} - -#[test] -fn test_queen_bishop_only() { - // Queen on d4, rook moves blocked by friendly pawns - let fen = "4k3/8/8/3P4/2PQP3/3P4/8/4K3 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_queen_moves(&board, &mut list); - let expected = "d4a1 d4b2 d4c3 d4e5 d4f6 d4g7 d4h8 \ - d4a7 d4b6 d4c5 d4e3 d4f2 d4g1"; - assert_moves_equal(&list.to_string(), expected); -} diff --git a/tests/rook_move_generation.rs b/tests/rook_move_generation.rs deleted file mode 100644 index e6afd4f..0000000 --- a/tests/rook_move_generation.rs +++ /dev/null @@ -1,101 +0,0 @@ -use chess_engine::board::Board; -use chess_engine::movegen::sliders::generate_rook_moves; -use chess_engine::r#move::MoveList; - -/// Compares two move list strings ignoring the order of moves. -fn assert_moves_equal(actual_str: &str, expected_str: &str) { - let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); - let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); - - actual_moves.sort(); - expected_moves.sort(); - - assert_eq!(actual_moves, expected_moves); -} - -#[test] -fn test_rook_moves_single_rook() { - let fen_standard = "8/8/8/2b5/2Rb4/2b5/8/8 w - - 0 1"; - let board = Board::from_fen(fen_standard); - let mut list = MoveList::new(); - generate_rook_moves(&board, &mut list); - // This FEN has a White Rook at c4, and Black Bishops at c5, d4, and c3. - // It should be able to capture all three and move left to a4/b4. - assert_moves_equal(&list.to_string(), "c4a4 c4b4 c4c3 c4c5 c4d4"); -} - -#[test] -fn test_rook_moves_empty_board() { - let fen = "8/8/8/8/3R4/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_rook_moves(&board, &mut list); - let expected = "d4d1 d4d2 d4d3 d4d5 d4d6 d4d7 d4d8 d4a4 d4b4 d4c4 d4e4 d4f4 d4g4 d4h4"; - assert_moves_equal(&list.to_string(), expected); -} - -#[test] -fn test_rook_moves_corner_blocked_black() { - let fen = "r6k/1p6/8/8/8/8/8/K7 b - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_rook_moves(&board, &mut list); - // Black rook at a8. Friendly king at h8, friendly pawn at b7. - // Rook can move down the a-file and right along the 8th rank, stopping before h8. - let expected = "a8a7 a8a6 a8a5 a8a4 a8a3 a8a2 a8a1 a8b8 a8c8 a8d8 a8e8 a8f8 a8g8"; - assert_moves_equal(&list.to_string(), expected); -} - -#[test] -fn test_rook_moves_double_rooks_friendly_block() { - let fen = "8/8/8/8/R3R3/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_rook_moves(&board, &mut list); - // Rooks at a4 and e4. They block each other horizontally. - // Rook a4 moves a1-a8 and b4, c4, d4. - // Rook e4 moves e1-e8, f4, g4, h4 AND d4, c4, b4. - let expected = "a4a1 a4a2 a4a3 a4a5 a4a6 a4a7 a4a8 a4b4 a4c4 a4d4 \ - e4e1 e4e2 e4e3 e4e5 e4e6 e4e7 e4e8 e4f4 e4g4 e4h4 \ - e4d4 e4c4 e4b4"; - assert_moves_equal(&list.to_string(), expected); -} - -#[test] -fn test_rook_moves_capture_stops_movegen() { - let fen = "r7/P7/8/8/8/8/8/8 b - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_rook_moves(&board, &mut list); - // Black rook at a8, White pawn at a7. - // The rook can capture at a7, but cannot move past it. - // It can still move horizontally. - let expected = "a8a7 a8b8 a8c8 a8d8 a8e8 a8f8 a8g8 a8h8"; - assert_moves_equal(&list.to_string(), expected); -} - -#[test] -fn test_rook_moves_completely_blocked_friendly() { - let fen = "8/8/8/1P6/PRP5/1P6/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_rook_moves(&board, &mut list); - // White rook at b4. - // Blocked by P(b5), P(b3), P(a4), P(c4). - // Should have 0 moves. - assert_moves_equal(&list.to_string(), ""); -} - -#[test] -fn test_rook_moves_ignores_absolute_pin() { - let fen = "r3k3/8/8/8/R3K3/8/8/8 w - - 0 1"; - let board = Board::from_fen(fen); - let mut list = MoveList::new(); - generate_rook_moves(&board, &mut list); - // White rook at a4 is absolutely pinned to King at e4 by Black rook at a8. - // A pseudo-legal generator should *ignore* the pin. - // It should generate vertical moves (including capture at a8) - // and horizontal moves (stopping before the friendly King at e4). - let expected = "a4a1 a4a2 a4a3 a4a5 a4a6 a4a7 a4a8 a4b4 a4c4 a4d4"; - assert_moves_equal(&list.to_string(), expected); -}