use crate::{ board::fen::from_fen, movegen::r#move::{Move, MoveType}, search::{transposition_table::TranspositionTable, MAX_TT_SIZE}, }; use String as FenError; use super::{ board::{Board, Color, PieceType}, history::{History, MoveParameters}, mailbox::Mailbox, square::Square, state::Castle, zobrist::{zobrist_keys, ZobristHash}, }; impl PartialEq for Game { fn eq(&self, other: &Self) -> bool { self.board.pieces == other.board.pieces && self.board.color == other.board.color && self.board.state == other.board.state } } #[derive(Debug, Clone, Eq)] pub struct Game { pub board: Board, pub history: History, pub mailbox: Mailbox, pub hash: ZobristHash, pub tt: TranspositionTable, } impl Game { pub fn new() -> Self { Self { board: Board::startpos(), history: History::new(), mailbox: Mailbox::from_board(&Board::startpos()), hash: zobrist_keys().calculate_hash(&Board::startpos()), tt: TranspositionTable::new(MAX_TT_SIZE), } } pub fn from_fen(fen: &str) -> Result { from_fen(fen) } pub const fn current_player(&self) -> Color { self.board.state.current_player() } pub const fn next_player(&self) -> Color { self.board.state.next_player() } pub fn make_move(&mut self, mv: &Move) { self.history .push_move_parameters(MoveParameters::build(self, mv)); let board = &mut self.board; let hash = &mut self.hash; let mailbox = &mut self.mailbox; let color = board.state.current_player(); let pawn_move = board.is_pawn_move(mv.src); let mut en_passant_square = None; let ep_capture = match color { Color::White => mv.dst.saturating_sub(8), Color::Black => mv.dst + 8, }; let old_castling_ability = board.state.castling_ability; let piece_at_src = mailbox .piece_at(mv.src) .unwrap_or_else(|| panic!("Expected piece at: {}", mv.src)); let piece_at_dst = mailbox.piece_at(mv.dst); match &mv.move_type { MoveType::Quiet => { board.move_piece(mv.src, mv.dst, piece_at_src.0); hash.update_quiet(mv.src, mv.dst, piece_at_src.0, color); hash.drop_en_passant_hash(board.state.en_passant_square()); mailbox.set_piece_at(mv.dst, Some(piece_at_src)); } MoveType::Capture => { let piece_at_dst = piece_at_dst.expect("Expected piece at: {mv.dst}"); board.remove_opponent_piece(mv.dst, piece_at_dst.0); hash.update_capture(mv.src, mv.dst, piece_at_src.0, piece_at_dst.0, color); board.move_piece(mv.src, mv.dst, piece_at_src.0); 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, PieceType::Pawn); board.remove_opponent_piece(ep_capture, PieceType::Pawn); hash.update_en_passant(mv.src, mv.dst, ep_capture, color); hash.drop_en_passant_hash(board.state.en_passant_square()); mailbox.set_piece_at(mv.dst, Some((PieceType::Pawn, color))); mailbox.set_piece_at(ep_capture, None); } MoveType::DoublePush => { board.move_piece(mv.src, mv.dst, piece_at_src.0); en_passant_square = match color { Color::White => Some(mv.src + 8), Color::Black => Some(mv.src.saturating_sub(8)), }; 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.0); 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(), color))); } MoveType::PromotionCapture(promote) => { let piece_at_dst = piece_at_dst.expect("Expected piece at dst: {mv.dst}"); board.remove_own_piece(mv.src, piece_at_src.0); board.remove_opponent_piece(mv.dst, piece_at_dst.0); board.promote_piece(mv.dst, promote); hash.update_promotion_capture(mv.src, mv.dst, piece_at_dst.0, promote, color); hash.drop_en_passant_hash(board.state.en_passant_square()); mailbox.set_piece_at(mv.dst, Some((promote.into_piece_type(), color))); } MoveType::Castle => { board.move_piece(mv.src, mv.dst, piece_at_src.0); let (rook_src, rook_dst) = match mv.dst { Square::C1 | Square::C8 => (mv.dst - 2, mv.dst + 1), Square::G1 | Square::G8 => (mv.dst + 1, mv.dst - 1), _ => return, }; 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.0, 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, color))); } } mailbox.set_piece_at(mv.src, None); board .state .update_game_state(mv, color, pawn_move, en_passant_square); hash.update_side_to_move_key(); hash.update_castling_ability_keys(old_castling_ability, board.state.castling_ability); } pub fn unmake_move(&mut self) { let board = &mut self.board; let mailbox = &mut self.mailbox; let move_parameters = &mut self .history .pop_move_parameters() .expect("History stack is empty"); let color_before_move = board.state.change_side(); board.state.revert_full_move(color_before_move); board.state.en_passant_square = move_parameters.en_passant_square; if let Some(hash) = move_parameters.zobrist_hash { self.hash = hash; } if let Some(new_castling_ability) = move_parameters.castling_ability { board.state.castling_ability = new_castling_ability; } if let Some(new_halfmove_clock) = move_parameters.halfmove_clock { board.state.halfmove_clock = new_halfmove_clock; } let mv = move_parameters .mv .expect("Expected move parameters from history stack"); let piece_at_dst = mailbox.piece_at(mv.dst).expect("Expected set piece"); match &mv.move_type { MoveType::Quiet | MoveType::DoublePush => { board.move_piece(mv.dst, mv.src, piece_at_dst.0); mailbox.set_piece_at(mv.src, mailbox.piece_at(mv.dst)); mailbox.set_piece_at(mv.dst, None); } MoveType::Capture => { let captured_piece = move_parameters .captured_piece .expect("Expected captured piece to unmake Capture"); board.move_piece(mv.dst, mv.src, piece_at_dst.0); board.insert_opponent_piece(mv.dst, captured_piece.0); mailbox.set_piece_at(mv.src, move_parameters.moving_piece); mailbox.set_piece_at(mv.dst, Some(captured_piece)); } MoveType::Promotion(_) => { let promoted_piece = move_parameters .promoted_piece .expect("Expected promoted piece to unmake Promotion"); board.remove_own_piece(mv.dst, promoted_piece.0); board.insert_own_piece(mv.src, PieceType::Pawn); mailbox.set_piece_at(mv.src, Some((PieceType::Pawn, color_before_move))); mailbox.set_piece_at(mv.dst, None); } MoveType::PromotionCapture(_) => { let promoted_piece = move_parameters .promoted_piece .expect("Expected promoted piece to unmake PromotionCapture"); let captured_piece = move_parameters .captured_piece .expect("Expected captured piece to unmake PromotionCapture"); board.remove_own_piece(mv.dst, promoted_piece.0); board.insert_opponent_piece(mv.dst, captured_piece.0); board.insert_own_piece(mv.src, PieceType::Pawn); mailbox.set_piece_at(mv.src, Some((PieceType::Pawn, color_before_move))); mailbox.set_piece_at(mv.dst, Some(captured_piece)); } MoveType::EnPassant => { let enemy_pawn_square = match color_before_move { Color::White => mv.dst - 8, Color::Black => mv.dst + 8, }; board.move_piece(mv.dst, mv.src, piece_at_dst.0); board.insert_opponent_piece(enemy_pawn_square, PieceType::Pawn); mailbox.set_piece_at(mv.src, Some((PieceType::Pawn, color_before_move))); mailbox.set_piece_at( enemy_pawn_square, Some((PieceType::Pawn, color_before_move.opponent())), ); } MoveType::Castle => { let (rook_src, rook_dst) = match mv.dst { Square::C1 | Square::C8 => (mv.dst - 2, mv.dst + 1), Square::G1 | Square::G8 => (mv.dst + 1, mv.dst - 1), _ => return, }; board.move_piece(mv.dst, mv.src, piece_at_dst.0); board.remove_own_piece(rook_dst, PieceType::Rook); board.insert_own_piece(rook_src, PieceType::Rook); mailbox.set_piece_at(mv.src, mailbox.piece_at(mv.dst)); mailbox.set_piece_at(mv.dst, None); mailbox.set_piece_at(rook_src, Some((PieceType::Rook, color_before_move))); mailbox.set_piece_at(rook_dst, None); } } } } impl Default for Game { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use crate::{ board::{fen::from_fen, square::Square}, movegen::r#move::{MoveType, Promote}, }; use super::*; const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1"; const FEN_QUIET: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2BQ3/1P1PNPP1/R3K2R b KQk - 1 1"; #[test] fn test_make_move_quiet() -> Result<(), String> { let mut game = from_fen(FEN)?; let f3e3 = Move::new(Square::F3, Square::E3); game.make_move(&f3e3); assert_eq!(game, from_fen(FEN_QUIET)?); Ok(()) } const FEN_CAPTURE: &str = "1r2k2r/2P1pq1p/2npb3/1p3QpP/p3P3/P2B4/1P1PNPP1/R3K2R b KQk - 0 1"; #[test] fn test_make_move_capture() -> Result<(), String> { let mut game = from_fen(FEN)?; let f3f5 = Move::with_type(Square::F3, Square::F5, MoveType::Capture); game.make_move(&f3f5); assert_eq!(game, from_fen(FEN_CAPTURE)?); Ok(()) } const FEN_EN_PASSANT: &str = "1r2k2r/2P1pq1p/2npb1P1/1p3p2/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1"; #[test] fn test_make_move_en_passant() -> Result<(), String> { let mut game = from_fen(FEN)?; let h5g6 = Move::with_type(Square::H5, Square::G6, MoveType::EnPassant); game.make_move(&h5g6); assert_eq!(game, from_fen(FEN_EN_PASSANT)?); Ok(()) } const FEN_DOUBLE_PUSH: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/pP2P3/P2B1Q2/3PNPP1/R3K2R b KQk b3 0 1"; #[test] fn test_make_move_double_push() -> Result<(), String> { let mut game = from_fen(FEN)?; let b2b4 = Move::with_type(Square::B2, Square::B4, MoveType::DoublePush); game.make_move(&b2b4); assert_eq!(game, from_fen(FEN_DOUBLE_PUSH)?); Ok(()) } const FEN_PROMOTION: &str = "1rQ1k2r/4pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1"; #[test] fn test_make_move_promotion() -> Result<(), String> { let mut game = from_fen(FEN)?; let c7c8 = Move::with_type(Square::C7, Square::C8, MoveType::Promotion(Promote::Queen)); game.make_move(&c7c8); assert_eq!(game, from_fen(FEN_PROMOTION)?); Ok(()) } const FEN_PROMOTION_CAPTURE: &str = "1Q2k2r/4pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1"; #[test] fn test_make_move_promotion_capture() -> Result<(), String> { let mut game = from_fen(FEN)?; let c7b8 = Move::with_type( Square::C7, Square::B8, MoveType::PromotionCapture(Promote::Queen), ); game.make_move(&c7b8); assert_eq!(game, from_fen(FEN_PROMOTION_CAPTURE)?); Ok(()) } const FEN_CASTLE: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R4RK1 b k - 1 1"; #[test] fn test_make_move_castle() -> Result<(), String> { let mut game = from_fen(FEN)?; let e1g1 = Move::with_type(Square::E1, Square::G1, MoveType::Castle); game.make_move(&e1g1); assert_eq!(game, from_fen(FEN_CASTLE)?); Ok(()) } const FEN_1: &str = "1r2k2r/2P1p1qp/2npb3/1p3p2/p3P1pP/P2B1Q2/1P1PNPP1/R3K2R w KQk - 0 1"; const FEN_2: &str = "1r2k2r/2P1p1qp/2npb3/1p3p2/pP2P1pP/P2B1Q2/3PNPP1/R3K2R b KQk b3 0 1"; #[test] fn test_unmake_quiet_and_double_push() -> Result<(), String> { let mut game = from_fen(FEN_1)?; let game_before_make = game.clone(); let mv = Move::with_type(Square::B2, Square::B3, MoveType::Quiet); game.make_move(&mv); game.unmake_move(); assert_eq!(game_before_make, game); let mv = Move::with_type(Square::B2, Square::B4, MoveType::DoublePush); game.make_move(&mv); game.unmake_move(); assert_eq!(game_before_make, game); Ok(()) } #[test] fn test_unmake_capture_and_promotion() -> Result<(), String> { let mut game = from_fen(FEN_1)?; let game_before_make = game.clone(); let mv = Move::with_type(Square::D3, Square::B5, MoveType::Capture); game.make_move(&mv); game.unmake_move(); assert_eq!(game_before_make, game); let mv = Move::with_type(Square::C7, Square::C8, MoveType::Promotion(Promote::Queen)); game.make_move(&mv); game.unmake_move(); assert_eq!(game_before_make, game); let mv = Move::with_type( Square::C7, Square::B8, MoveType::PromotionCapture(Promote::Queen), ); game.make_move(&mv); game.unmake_move(); assert_eq!(game_before_make, game); Ok(()) } #[test] fn test_unmake_en_passant() -> Result<(), String> { let mut game = from_fen(FEN_2)?; let game_before_make = game.clone(); let mv = Move::with_type(Square::A4, Square::B3, MoveType::EnPassant); game.make_move(&mv); game.unmake_move(); assert_eq!(game_before_make, game); Ok(()) } #[test] fn test_unmake_castle() -> Result<(), String> { let mut game = from_fen(FEN_1)?; let game_before_make = game.clone(); let mv = Move::with_type(Square::E1, Square::C1, MoveType::Castle); game.make_move(&mv); game.unmake_move(); assert_eq!(game_before_make, game); Ok(()) } }