Reinitialize repository and add working move generation for all pieces

This commit is contained in:
Moritz Eigenauer 2025-11-12 17:01:12 +01:00
commit 951a8bbec6
28 changed files with 3373 additions and 0 deletions

View file

@ -0,0 +1,85 @@
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);
}

View file

@ -0,0 +1,76 @@
use chess_engine::board::*;
#[test]
fn test_fen_roundtrip_standard() {
let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
assert_eq!(Board::from_fen(fen_standard).to_fen(), fen_standard);
}
#[test]
fn test_fen_roundtrip_kiwipete() {
let fen_kiwipete = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1";
assert_eq!(Board::from_fen(fen_kiwipete).to_fen(), fen_kiwipete);
}
#[test]
fn test_fen_roundtrip_en_passant() {
let fen_en_passant = "rnbqkbnr/pppppp1p/8/8/p7/4P3/PPPP1PPP/RNBQKBNR w KQkq e3 0 1";
assert_eq!(Board::from_fen(fen_en_passant).to_fen(), fen_en_passant);
}
#[test]
fn test_fen_roundtrip_castle() {
let fen_castle = "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2QK2R b - - 0 1";
assert_eq!(Board::from_fen(fen_castle).to_fen(), fen_castle);
}
#[test]
fn test_fen_roundtrip_just_kings() {
let fen_just_kings = "8/k7/8/8/8/8/7K/8 w - - 0 1";
assert_eq!(Board::from_fen(fen_just_kings).to_fen(), fen_just_kings);
}
#[test]
fn test_fen_roundtrip_high_move_values() {
let fen_high_move_values = "8/P1k5/K7/8/8/8/8/8 w - - 0 78";
assert_eq!(Board::from_fen(fen_high_move_values).to_fen(), fen_high_move_values);
}
#[test]
fn test_fen_roundtrip_empty_count1() {
let fen_empty_count1 = "1n6/8/8/8/8/8/8/8 w - - 0 1";
assert_eq!(Board::from_fen(fen_empty_count1).to_fen(), fen_empty_count1);
}
#[test]
fn test_fen_roundtrip_empty_count2() {
let fen_empty_count2 = "6n1/8/8/8/8/8/8/8 w - - 0 1";
assert_eq!(Board::from_fen(fen_empty_count2).to_fen(), fen_empty_count2);
}
#[test]
fn test_board_fen_state() {
let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
let board = Board::from_fen(fen_standard);
assert_eq!(board.pieces[PieceType::Pawn as usize][Color::White as usize], 65280);
assert_eq!(board.pieces[PieceType::Pawn as usize][Color::Black as usize], 71776119061217280);
assert_eq!(board.pieces[PieceType::Knight as usize][Color::White as usize], 66);
assert_eq!(board.pieces[PieceType::Knight as usize][Color::Black as usize], 4755801206503243776);
assert_eq!(board.pieces[PieceType::Bishop as usize][Color::White as usize], 36);
assert_eq!(board.pieces[PieceType::Bishop as usize][Color::Black as usize], 2594073385365405696);
assert_eq!(board.pieces[PieceType::Rook as usize][Color::White as usize], 129);
assert_eq!(board.pieces[PieceType::Rook as usize][Color::Black as usize], 9295429630892703744);
assert_eq!(board.pieces[PieceType::Queen as usize][Color::White as usize], 8);
assert_eq!(board.pieces[PieceType::Queen as usize][Color::Black as usize], 576460752303423488);
assert_eq!(board.pieces[PieceType::King as usize][Color::White as usize], 16);
assert_eq!(board.pieces[PieceType::King as usize][Color::Black as usize], 1152921504606846976);
assert_eq!(board.occupied[0], 65535);
assert_eq!(board.occupied[1], 18446462598732840960);
assert_eq!(board.all_occupied, 18446462598732906495);
assert_eq!(board.castling_rights, 15);
assert_eq!(board.en_passant_target, None);
assert_eq!(board.halfmove_clock, 0);
assert_eq!(board.fullmove_number, 1);
}

View file

@ -0,0 +1,145 @@
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(), "");
}

View file

@ -0,0 +1,96 @@
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(), "");
}

View file

@ -0,0 +1,92 @@
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_CAP_B);
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_CAP_N);
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");
}

View file

@ -0,0 +1,142 @@
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");
}

12
tests/perft.rs Normal file
View file

@ -0,0 +1,12 @@
use chess_engine::board::Board;
use chess_engine::movegen::generate_pseudo_legal_moves;
use chess_engine::r#move::MoveList;
// "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" 7 3195901860 "false"
// "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -" 5 193690690 "false"
// "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -" 7 178633661 "false"
// "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" 6 706045033 "false"
// "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8" 5 89941194 "false"
// "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10" 5 164075551 "false"
// "r7/4p3/5p1q/3P4/4pQ2/4pP2/6pp/R3K1kr w Q - 1 3" 5 11609488 "false"

View file

@ -0,0 +1,93 @@
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);
}

View file

@ -0,0 +1,101 @@
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);
}