diff --git a/src/board/history.rs b/src/board/history.rs new file mode 100644 index 0000000..24de7b3 --- /dev/null +++ b/src/board/history.rs @@ -0,0 +1,205 @@ +use crate::movegen::r#move::{Move, MoveType, Promote}; + +use super::{ + board::{Board, Color, PieceType}, + state::{Castle, State}, +}; + +pub struct History { + pub move_parameters: Vec, +} + +impl History { + pub const fn new() -> Self { + Self { + move_parameters: Vec::new(), + } + } + + pub fn push_move_parameters(&mut self, move_parameters: MoveParameters) { + self.move_parameters.push(move_parameters) + } + + pub fn pop_move_parameters(&mut self) -> Option { + self.move_parameters.pop() + } +} + +pub struct MoveParameters { + pub mv: Option, + pub captured_piece: Option, + pub promoted_piece: Option, + pub castling_ability: Option<[Castle; 2]>, + pub en_passant_target_square: Option, + pub halfmove_clock: Option, +} + +impl MoveParameters { + pub const fn new() -> Self { + Self { + mv: None, + captured_piece: None, + promoted_piece: None, + castling_ability: None, + en_passant_target_square: None, + halfmove_clock: None, + } + } + + pub fn add_move(&mut self, mv: Move) { + self.mv = Some(mv) + } + + pub fn add_captured_piece(&mut self, board: &Board, dst: usize, color: Color) { + self.captured_piece = board.piece_type_at(dst, color); + } + + pub fn add_promoted_piece(&mut self, piece_type: Promote) { + self.promoted_piece = Some(piece_type.into_piece_type()) + } + + pub fn add_irreversible_parameters(&mut self, state: State) { + self.castling_ability = Some(state.castling_ability); + self.en_passant_target_square = state.en_passant_target_square; + self.halfmove_clock = Some(state.halfmove_clock) + } + + pub fn add_capture_and_promotion_piece(&mut self, board: &Board, mv: Move, color: Color) { + match mv.move_type { + MoveType::Capture => { + self.add_captured_piece(&board, mv.dst, Color::opponent_color(color)) + } + MoveType::Promotion(piece) => self.add_promoted_piece(piece), + MoveType::PromotionCapture(piece) => { + self.add_promoted_piece(piece); + self.add_captured_piece(&board, mv.dst, Color::opponent_color(color)); + } + _ => (), + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + board::{fen::from_fen, history::MoveParameters, square::Square}, + movegen::r#move::{Move, MoveType, Promote}, + }; + + const FEN: &str = "1r2k2r/2P1p1qp/2npb3/1p3p2/p3P1pP/P2B1Q2/1P1PNPP1/R3K2R w KQk - 0 1"; + + #[test] + fn test_unmake_quiet_and_double_push() -> Result<(), String> { + let mut game = from_fen(FEN)?; + let board_before_make = game.board.clone(); + + let mv = Move::new_with_type(Square::B2, Square::B3, MoveType::Quiet); + let mut move_parameters = MoveParameters::new(); + move_parameters.add_move(mv); + move_parameters.add_irreversible_parameters(game.board.state); + move_parameters.add_capture_and_promotion_piece(&game.board, mv, game.current_player()); + + game.board.make_move(&mv); + game.board.unmake_move(move_parameters); + + assert_eq!(board_before_make, game.board); + + let mv = Move::new_with_type(Square::B2, Square::B4, MoveType::DoublePush); + let mut move_parameters = MoveParameters::new(); + move_parameters.add_move(mv); + move_parameters.add_irreversible_parameters(game.board.state); + move_parameters.add_capture_and_promotion_piece(&game.board, mv, game.current_player()); + + game.board.make_move(&mv); + game.board.unmake_move(move_parameters); + + assert_eq!(board_before_make, game.board); + + Ok(()) + } + + #[test] + fn test_unmake_capture_and_promotion() -> Result<(), String> { + let mut game = from_fen(FEN)?; + let board_before_make = game.board.clone(); + + let mv = Move::new_with_type(Square::D3, Square::B5, MoveType::Capture); + let mut move_parameters = MoveParameters::new(); + move_parameters.add_move(mv); + move_parameters.add_irreversible_parameters(game.board.state); + move_parameters.add_capture_and_promotion_piece(&game.board, mv, game.current_player()); + + game.board.make_move(&mv); + game.board.unmake_move(move_parameters); + + assert_eq!(board_before_make, game.board); + + let mv = Move::new_with_type(Square::C7, Square::C8, MoveType::Promotion(Promote::Queen)); + let mut move_parameters = MoveParameters::new(); + move_parameters.add_move(mv); + move_parameters.add_irreversible_parameters(game.board.state); + move_parameters.add_capture_and_promotion_piece(&game.board, mv, game.current_player()); + + game.board.make_move(&mv); + game.board.unmake_move(move_parameters); + + assert_eq!(board_before_make, game.board); + + let mv = Move::new_with_type( + Square::C7, + Square::B8, + MoveType::PromotionCapture(Promote::Queen), + ); + let mut move_parameters = MoveParameters::new(); + move_parameters.add_move(mv); + move_parameters.add_irreversible_parameters(game.board.state); + move_parameters.add_capture_and_promotion_piece(&game.board, mv, game.current_player()); + + game.board.make_move(&mv); + game.board.unmake_move(move_parameters); + + assert_eq!(board_before_make, game.board); + + Ok(()) + } + + #[test] + fn test_unmake_en_passant() -> Result<(), String> { + let mut game = from_fen(FEN)?; + let mv = Move::new_with_type(Square::B2, Square::B4, MoveType::DoublePush); + game.board.make_move(&mv); + let board_before_make = game.board.clone(); + + let mv = Move::new_with_type(Square::A4, Square::B3, MoveType::EnPassant); + let mut move_parameters = MoveParameters::new(); + move_parameters.add_move(mv); + move_parameters.add_irreversible_parameters(game.board.state); + move_parameters.add_capture_and_promotion_piece(&game.board, mv, game.current_player()); + + game.board.make_move(&mv); + game.board.unmake_move(move_parameters); + + assert_eq!(board_before_make, game.board); + + Ok(()) + } + + #[test] + fn test_unmake_castle() -> Result<(), String> { + let mut game = from_fen(FEN)?; + let board_before_make = game.board.clone(); + + let mv = Move::new_with_type(Square::E1, Square::C1, MoveType::Castle); + let mut move_parameters = MoveParameters::new(); + move_parameters.add_move(mv); + move_parameters.add_irreversible_parameters(game.board.state); + move_parameters.add_capture_and_promotion_piece(&game.board, mv, game.current_player()); + + game.board.make_move(&mv); + game.board.unmake_move(move_parameters); + + assert_eq!(board_before_make, game.board); + + Ok(()) + } +} diff --git a/src/board/mod.rs b/src/board/mod.rs index 1d7dcc8..e06049c 100644 --- a/src/board/mod.rs +++ b/src/board/mod.rs @@ -2,5 +2,6 @@ pub mod bitboard; pub mod board; pub mod fen; pub mod game; +pub mod history; pub mod square; pub mod state; diff --git a/src/movegen/move.rs b/src/movegen/move.rs index e56efc5..6b1a982 100644 --- a/src/movegen/move.rs +++ b/src/movegen/move.rs @@ -3,6 +3,7 @@ use core::fmt; use crate::board::{ bitboard::{have_common_bit, square_to_bitboard}, board::{Board, Color, Piece, PieceType}, + history::MoveParameters, square::{coords_to_square, square_to_algebraic, Square}, state::{Castle, State}, }; @@ -170,6 +171,66 @@ impl Board { } } + pub fn unmake_move(&mut self, move_parameters: MoveParameters) { + let color_before_move = self.state.change_side(); + self.state.revert_full_move(color_before_move); + self.state.en_passant_target_square = move_parameters.en_passant_target_square; + + if let Some(new_castling_ability) = move_parameters.castling_ability { + self.state.castling_ability = new_castling_ability; + } + + if let Some(new_halfmove_clock) = move_parameters.halfmove_clock { + self.state.halfmove_clock = new_halfmove_clock; + } + + let mv = move_parameters.mv.unwrap(); + let (own_pieces, opponent_pieces) = match color_before_move { + Color::White => (&mut self.white_pieces, &mut self.black_pieces), + Color::Black => (&mut self.black_pieces, &mut self.white_pieces), + }; + + match mv.move_type { + MoveType::Quiet | MoveType::DoublePush => { + Self::move_piece(mv.dst, mv.src, own_pieces); + } + MoveType::Capture | MoveType::Promotion(_) | MoveType::PromotionCapture(_) => { + if let (Some(captured_piece_type), Some(promoted_piece_type)) = ( + move_parameters.captured_piece, + move_parameters.promoted_piece, + ) { + opponent_pieces[captured_piece_type].bitboard |= square_to_bitboard(mv.dst); + own_pieces[promoted_piece_type].bitboard &= !square_to_bitboard(mv.dst); + own_pieces[PieceType::Pawn].bitboard |= square_to_bitboard(mv.src); + } else if let Some(captured_piece_type) = move_parameters.captured_piece { + Self::move_piece(mv.dst, mv.src, own_pieces); + opponent_pieces[captured_piece_type].bitboard |= square_to_bitboard(mv.dst); + } else if let Some(promoted_piece_type) = move_parameters.promoted_piece { + own_pieces[promoted_piece_type].bitboard &= !square_to_bitboard(mv.dst); + own_pieces[PieceType::Pawn].bitboard |= square_to_bitboard(mv.src); + } + } + MoveType::EnPassant => { + Self::move_piece(mv.dst, mv.src, own_pieces); + let enemy_pawn_square = match color_before_move { + Color::White => mv.dst - 8, + Color::Black => mv.dst + 8, + }; + opponent_pieces[PieceType::Pawn].bitboard |= square_to_bitboard(enemy_pawn_square) + } + MoveType::Castle => { + Self::move_piece(mv.dst, mv.src, own_pieces); + 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, + }; + own_pieces[PieceType::Rook].bitboard &= !square_to_bitboard(rook_dst); + own_pieces[PieceType::Rook].bitboard |= square_to_bitboard(rook_src); + } + } + } + fn update_game_state(state: &mut State, mv: &Move, color: Color, pawn_move: bool) { state.set_en_passant_target_square(None); state.update_castling_state_quiet(mv.src, color);