diff --git a/src/board/game.rs b/src/board/game.rs index 50ed886..65e44d8 100644 --- a/src/board/game.rs +++ b/src/board/game.rs @@ -57,7 +57,7 @@ impl Game { let color = board.state.current_player(); let pawn_move = board.is_pawn_move(mv.src); let mut en_passant_square = None; - let capture_square = match color { + let capture_en_passant = match color { Color::White => mv.dst - 8, Color::Black => mv.dst + 8, }; @@ -71,6 +71,7 @@ impl Game { MoveType::Quiet => { board.move_piece(mv.src, mv.dst, piece_at_src); hash.update_quiet(mv.src, mv.dst, piece_at_src, color); + hash.drop_en_passant_hash(board.state.en_passant_square()); mailbox.set_piece_at(mv.dst, Some(piece_at_src)); } MoveType::Capture => { @@ -78,14 +79,16 @@ impl Game { board.move_piece(mv.src, mv.dst, piece_at_src); board.remove_opponent_piece(mv.dst, piece_at_dst); hash.update_capture(mv.src, mv.dst, piece_at_src, piece_at_dst, color); + hash.drop_en_passant_hash(board.state.en_passant_square()); mailbox.set_piece_at(mv.dst, Some(piece_at_src)); } MoveType::EnPassant => { - board.move_piece(mv.src, mv.dst, piece_at_src); - board.remove_opponent_piece(capture_square, PieceType::Pawn); - hash.update_en_passant(mv.src, mv.dst, piece_at_src, color); - mailbox.set_piece_at(mv.dst, Some(piece_at_src)); - mailbox.set_piece_at(capture_square, None); + board.move_piece(mv.src, mv.dst, PieceType::Pawn); + board.remove_opponent_piece(capture_en_passant, PieceType::Pawn); + hash.update_en_passant(mv.src, mv.dst, capture_en_passant, color); + hash.drop_en_passant_hash(board.state.en_passant_square()); + mailbox.set_piece_at(mv.dst, Some(PieceType::Pawn)); + mailbox.set_piece_at(capture_en_passant, None); } MoveType::DoublePush => { board.move_piece(mv.src, mv.dst, piece_at_src); @@ -93,13 +96,15 @@ impl Game { Color::White => Some(mv.src + 8), Color::Black => Some(mv.src.saturating_sub(8)), }; - hash.update_double_push(mv.src, mv.dst, color, board.state.en_passant_square); + hash.update_double_push(mv.src, mv.dst, color, en_passant_square); + hash.drop_en_passant_hash(board.state.en_passant_square()); mailbox.set_piece_at(mv.dst, Some(piece_at_src)); } MoveType::Promotion(promote) => { board.remove_own_piece(mv.src, piece_at_src); board.promote_piece(mv.dst, promote); hash.update_promotion(mv.src, mv.dst, promote, color); + hash.drop_en_passant_hash(board.state.en_passant_square()); mailbox.set_piece_at(mv.dst, Some(promote.into_piece_type())); } MoveType::PromotionCapture(promote) => { @@ -108,6 +113,7 @@ impl Game { board.remove_opponent_piece(mv.dst, piece_at_dst); board.promote_piece(mv.dst, promote); hash.update_promotion_capture(mv.src, mv.dst, piece_at_dst, promote, color); + hash.drop_en_passant_hash(board.state.en_passant_square()); mailbox.set_piece_at(mv.dst, Some(promote.into_piece_type())); } MoveType::Castle => { @@ -120,6 +126,7 @@ impl Game { board.move_piece(rook_src, rook_dst, PieceType::Rook); board.state.set_castling_ability(color, Castle::None); hash.update_castle(mv.src, mv.dst, piece_at_src, rook_src, rook_dst, color); + hash.drop_en_passant_hash(board.state.en_passant_square()); mailbox.set_piece_at(mv.dst, Some(piece_at_src)); mailbox.set_piece_at(rook_src, None); mailbox.set_piece_at(rook_dst, Some(PieceType::Rook)); @@ -132,11 +139,6 @@ impl Game { .update_game_state(mv, color, pawn_move, en_passant_square); hash.update_side_to_move_key(); - - if let Some(old_en_passant) = board.state.en_passant_square { - hash.update_en_passant_keys(old_en_passant); - } - hash.update_castling_ability_keys(old_castling_ability, board.state.castling_ability); } @@ -252,8 +254,8 @@ mod tests { #[test] fn test_make_move_quiet() -> Result<(), String> { let mut game = from_fen(FEN)?; - let b1c3 = Move::new(Square::F3, Square::E3); - game.make_move(&b1c3); + let f3e3 = Move::new(Square::F3, Square::E3); + game.make_move(&f3e3); assert_eq!(game, from_fen(FEN_QUIET)?); diff --git a/src/board/zobrist.rs b/src/board/zobrist.rs index 6281137..1216f56 100644 --- a/src/board/zobrist.rs +++ b/src/board/zobrist.rs @@ -2,8 +2,8 @@ use crate::movegen::r#move::Promote; use super::{ bitboard::lsb, - board::{self, Board, Color, PieceType}, - square::{self, to_file, Square}, + board::{Board, Color, PieceType}, + square::{self, Square}, state::Castle, }; use rand::{rngs::SmallRng, RngCore, SeedableRng}; @@ -24,7 +24,7 @@ pub struct ZobristKeys { impl ZobristKeys { pub fn new() -> Self { - let mut keys = ZobristKeys::default(); + let mut keys = Self::default(); let mut state = SmallRng::seed_from_u64(1804289383); for square in Square::A1..=Square::H8 { @@ -58,27 +58,27 @@ impl ZobristKeys { let white_pieces = &board.white_pieces; let black_pieces = &board.black_pieces; - for (idx, piece) in white_pieces.iter().enumerate() { + for piece in white_pieces.iter() { let mut bb = piece.bitboard; while bb != 0 { let square = lsb(bb); - hash ^= self.piece_square_color[square][idx][0]; + hash ^= self.piece_square_color[square][piece.piece_type.idx()][piece.color.idx()]; bb &= bb - 1; } } - for (idx, piece) in black_pieces.iter().enumerate() { + for piece in black_pieces.iter() { let mut bb = piece.bitboard; while bb != 0 { let square = lsb(bb); - hash ^= self.piece_square_color[square][idx][1]; + hash ^= self.piece_square_color[square][piece.piece_type.idx()][piece.color.idx()]; bb &= bb - 1; } } - if board.state.current_player().eq(&board::Color::Black) { + if board.state.current_player().eq(&Color::Black) { hash ^= self.side_to_move } @@ -86,7 +86,7 @@ impl ZobristKeys { hash ^= self.castling_ability[board.state.castling_ability[1].idx()][1]; if let Some(ep) = board.state.en_passant_square { - hash ^= self.en_passant[to_file(ep)]; + hash ^= self.en_passant[square::to_file(ep)]; } ZobristHash::new(hash) @@ -119,11 +119,6 @@ impl ZobristHash { self.hash ^= keys.side_to_move } - pub fn update_en_passant_keys(&mut self, old_en_passant: usize) { - let keys = zobrist_keys(); - self.hash ^= keys.en_passant[square::to_file(old_en_passant)] - } - pub fn update_castling_ability_keys( &mut self, old_castling_ability: [Castle; 2], @@ -160,13 +155,13 @@ impl ZobristHash { &mut self, src: usize, dst: usize, - piece_at_src: PieceType, + capture_en_passant: usize, color: Color, ) { let keys = zobrist_keys(); - self.hash ^= keys.piece_square_color[src][piece_at_src.idx()][color.idx()]; - self.hash ^= keys.piece_square_color[dst][piece_at_src.idx()][color.idx()]; - self.hash ^= keys.piece_square_color[dst][PieceType::Pawn.idx()][color.opponent().idx()] + self.hash ^= keys.piece_square_color[src][PieceType::Pawn.idx()][color.idx()]; + self.hash ^= keys.piece_square_color[dst][PieceType::Pawn.idx()][color.idx()]; + self.hash ^= keys.piece_square_color[capture_en_passant][PieceType::Pawn.idx()][color.opponent().idx()] } pub fn update_double_push( @@ -220,20 +215,141 @@ impl ZobristHash { self.hash ^= keys.piece_square_color[rook_src][PieceType::Rook.idx()][color.idx()]; self.hash ^= keys.piece_square_color[rook_dst][PieceType::Rook.idx()][color.idx()]; } + + pub fn drop_en_passant_hash(&mut self, en_passant: Option) { + let keys = zobrist_keys(); + if let Some(ep) = en_passant { + self.hash ^= keys.en_passant[square::to_file(ep)] + } + } } #[cfg(test)] mod tests { + use crate::{ + board::{fen::from_fen, square::Square}, + movegen::r#move::{Move, MoveType, Promote}, + }; + + const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1"; + #[test] - fn test_calculate_hash() -> Result<(), String> { + fn test_identical_boards() -> Result<(), String> { + let game1 = from_fen(FEN)?; + let game2 = from_fen(FEN)?; + + assert_eq!(game1, game2); + Ok(()) } - //TODO: how to test - // test if an incremental position is the same as if it would be calculated from scratch + const FEN_QUIET: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2BQ3/1P1PNPP1/R3K2R b KQk - 1 1"; #[test] - fn test_update_hash() -> Result<(), String> { + fn test_update_quiet() -> Result<(), String> { + let mut incremental = from_fen(FEN)?; + incremental.make_move(&Move::new(Square::F3, Square::E3)); + let from_scratch = from_fen(FEN_QUIET)?; + + assert_eq!(incremental.hash, from_scratch.hash); + + Ok(()) + } + + const FEN_CAPTURE: &str = "1r2k2r/2P1pq1p/2npb3/1p3QpP/p3P3/P2B4/1P1PNPP1/R3K2R b KQk - 0 1"; + + #[test] + fn test_update_capture() -> Result<(), String> { + let mut incremental = from_fen(FEN)?; + incremental.make_move(&Move::with_type(Square::F3, Square::F5, MoveType::Capture)); + let from_scratch = from_fen(FEN_CAPTURE)?; + + assert_eq!(incremental.hash, from_scratch.hash); + + Ok(()) + } + + const FEN_EN_PASSANT: &str = + "1r2k2r/2P1pq1p/2npb1P1/1p3p2/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1"; + + #[test] + fn test_update_en_passant() -> Result<(), String> { + let mut incremental = from_fen(FEN)?; + incremental.make_move(&Move::with_type( + Square::H5, + Square::G6, + MoveType::EnPassant, + )); + let from_scratch = from_fen(FEN_EN_PASSANT)?; + + assert_eq!(incremental.hash, from_scratch.hash); + + Ok(()) + } + + const FEN_DOUBLE_PUSH: &str = + "1r2k2r/2P1pq1p/2npb3/1p3ppP/pP2P3/P2B1Q2/3PNPP1/R3K2R b KQk b3 0 1"; + + #[test] + fn test_update_double_push() -> Result<(), String> { + let mut incremental = from_fen(FEN)?; + incremental.make_move(&Move::with_type( + Square::B2, + Square::B4, + MoveType::DoublePush, + )); + let from_scratch = from_fen(FEN_DOUBLE_PUSH)?; + + assert_eq!(incremental.hash, from_scratch.hash); + + Ok(()) + } + + const FEN_PROMOTION: &str = "1rQ1k2r/4pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1"; + + #[test] + fn test_update_promotion() -> Result<(), String> { + let mut incremental = from_fen(FEN)?; + incremental.make_move(&Move::with_type( + Square::C7, + Square::C8, + MoveType::Promotion(Promote::Queen), + )); + let from_scratch = from_fen(FEN_PROMOTION)?; + + assert_eq!(incremental.hash, from_scratch.hash); + + Ok(()) + } + + const FEN_PROMOTION_CAPTURE: &str = + "1Q2k2r/4pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1"; + + #[test] + fn test_update_promotion_capture() -> Result<(), String> { + let mut incremental = from_fen(FEN)?; + incremental.make_move(&Move::with_type( + Square::C7, + Square::B8, + MoveType::PromotionCapture(Promote::Queen), + )); + let from_scratch = from_fen(FEN_PROMOTION_CAPTURE)?; + + assert_eq!(incremental.hash, from_scratch.hash); + + Ok(()) + } + + const FEN_CASTLE: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R4RK1 b k - 1 1"; + + #[test] + fn test_update_castle() -> Result<(), String> { + let mut incremental = from_fen(FEN)?; + incremental.make_move(&Move::with_type(Square::E1, Square::G1, MoveType::Castle)); + let from_scratch = from_fen(FEN_CASTLE)?; + + assert_eq!(incremental.hash, from_scratch.hash); + Ok(()) } }