diff --git a/src/board.rs b/src/board.rs index 7d9cba0..098cd55 100644 --- a/src/board.rs +++ b/src/board.rs @@ -11,7 +11,7 @@ use crate::movegen::{ }; use crate::r#move::{Move, MoveType, Promote}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Board { pub white_pieces: [Piece; 6], pub black_pieces: [Piece; 6], @@ -146,6 +146,128 @@ impl Board { Kind::King => king_pseudo_moves(pieces, all_occupancies, own_occupancies, self, color), } } + + pub fn make_move_and_reset(&mut self, mv: Move, color: Color) -> Vec { + let mut board_variants = vec![]; + let original_board = self.clone(); + if self.make_move(mv, color) { + board_variants.push(self.clone()); + } + *self = original_board; + board_variants + } + + pub fn make_move(&mut self, mv: Move, color: Color) -> bool { + self.update_board_state(&mv, color); + self.state.update_castling_state(mv.source as u8, color); + self.state.next_turn(); + + let pieces = match color { + Color::White => &self.white_pieces, + Color::Black => &self.black_pieces, + }; + + let own_king_square = pieces[Kind::King.idx()].bitboard.trailing_zeros() as usize; + self.is_move_legit(own_king_square, Color::opponent_color(color)) + } + + pub fn update_board_state(&mut self, mv: &Move, color: Color) { + let (own_pieces, opponent_pieces) = match color { + 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 => { + Board::move_piece(mv.source as u8, mv.target as u8, own_pieces); + } + MoveType::Capture => { + Board::move_piece(mv.source as u8, mv.target as u8, own_pieces); + Board::remove_piece(mv.target as u8, opponent_pieces); + } + MoveType::EnPassant => { + Board::move_piece(mv.source as u8, mv.target as u8, own_pieces); + Board::remove_pawn_enpassant(mv.target as u8, opponent_pieces, color); + self.state.set_en_passant_target_square(None); + } + MoveType::DoublePush => { + Board::move_piece(mv.source as u8, mv.target as u8, own_pieces); + let en_passant = Some(mv.source as u8 + 8); + self.state.set_en_passant_target_square(en_passant); + } + MoveType::Promotion(promote) => { + Board::remove_piece(mv.source as u8, own_pieces); + Board::promote_piece(mv.target as u8, own_pieces, *promote); + } + MoveType::PromotionCapture(promote) => { + Board::remove_piece(mv.source as u8, own_pieces); + Board::remove_piece(mv.target as u8, opponent_pieces); + Board::promote_piece(mv.target as u8, own_pieces, *promote); + } + MoveType::Castle => { + Board::move_piece(mv.source as u8, mv.target as u8, own_pieces); + Board::move_rook_castle(mv.target as u8, own_pieces); + } + } + } + + fn move_piece(source: u8, target: u8, pieces: &mut [Piece; 6]) { + for p in pieces.iter_mut() { + if p.bitboard & (1_u64 << source) != 0 { + p.bitboard &= !(1_u64 << source); + p.bitboard |= 1_u64 << target; + break; + } + } + } + + fn move_rook_castle(square: u8, pieces: &mut [Piece; 6]) { + let (rook_source, rook_target) = match square { + 2 | 58 => (square - 2, square + 1), + 6 | 62 => (square + 1, square - 1), + _ => return, + }; + + for p in pieces.iter_mut() { + if p.bitboard & 1_u64 << rook_source != 0 { + p.bitboard &= !(1_u64 << rook_source); + p.bitboard |= 1_u64 << rook_target; + break; + } + } + } + + fn promote_piece(square: u8, pieces: &mut [Piece; 6], promote: Promote) { + match promote { + Promote::Knight => pieces[Kind::Knight.idx()].bitboard |= 1_u64 << square, + Promote::Bishop => pieces[Kind::Bishop.idx()].bitboard |= 1_u64 << square, + Promote::Rook => pieces[Kind::Rook.idx()].bitboard |= 1_u64 << square, + Promote::Queen => pieces[Kind::Queen.idx()].bitboard |= 1_u64 << square, + }; + } + + fn remove_piece(square: u8, pieces: &mut [Piece; 6]) { + for p in pieces.iter_mut() { + if p.bitboard & (1_u64 << square) != 0 { + p.bitboard &= !(1_u64 << square); + break; + } + } + } + + fn remove_pawn_enpassant(square: u8, pieces: &mut [Piece; 6], color: Color) { + let piece_to_remove = match color { + Color::White => square - 8, + Color::Black => square + 8, + }; + + for p in pieces.iter_mut() { + if p.bitboard & (1_u64 << piece_to_remove) != 0 { + p.bitboard &= !(1_u64 << piece_to_remove); + break; + } + } + } } impl Default for Board { @@ -187,12 +309,21 @@ impl Kind { } } -#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Color { White, Black, } +impl Color { + pub const fn opponent_color(color: Color) -> Color { + match color { + Color::White => Color::Black, + Color::Black => Color::White, + } + } +} + #[cfg(test)] mod tests { use crate::{attack::init_attacks, fen::from_fen}; @@ -202,10 +333,11 @@ mod tests { const FEN_EXAMPLE: [&str; 1] = ["8/6P1/4n2b/1p6/1Kp5/6n1/4b3/1k6 w - - 0 1"]; #[test] - fn test_get_all_occupancies() -> Result<(), String> { + fn test_get_occupancies() -> Result<(), String> { let new_game = from_fen(FEN_EXAMPLE[0])?; + assert_eq!(new_game.board.get_white_occupancies(), 0x40000002000000); + assert_eq!(new_game.board.get_black_occupancies(), 0x900204401002); assert_eq!(new_game.board.get_all_occupancies(), 0x40900206401002); - Ok(()) } @@ -217,4 +349,120 @@ mod tests { Ok(()) } + + #[test] + fn test_fifty_move_draw() -> Result<(), String> { + //TODO: make a MoveHistory/BoardHistory/GameHistory struct + Ok(()) + } + + #[test] + fn test_threefold_repetition() -> Result<(), String> { + //TODO: make a MoveHistory/BoardHistory/GameHistory struct + Ok(()) + } + + const FEN_QUIET: [&str; 2] = [ + "r3k2r/2p1p1qp/2npb3/1p3p2/p3P1pP/P1PB1Q2/1P1PNPP1/R3K2R w KQkq - 0 1", + "r3k2r/2p1p1qp/2npb3/1p3p2/pP2P1pP/P1PB1Q2/3PNPP1/R3K2R b KQkq b3 0 1", + ]; + + #[test] + fn test_make_move_quiet() -> Result<(), String> { + let mut game = from_fen(FEN_QUIET[0])?; + let mv = Move::new_with_type(9, 25, MoveType::DoublePush); + game.board.make_move(mv, Color::White); + assert_eq!(game, from_fen(FEN_QUIET[1])?); + + Ok(()) + } + + const FEN_CAPTURE: [&str; 2] = [ + "r3k2r/2p1p1qp/2npb3/1p3p2/p3P1pP/P1PB1Q2/1P1PNPP1/R3K2R w KQkq - 0 1", + "r3k2r/2p1p1qp/2npb3/1p3Q2/p3P1pP/P1PB4/1P1PNPP1/R3K2R b KQkq - 0 1", + ]; + + #[test] + fn test_make_move_capture() -> Result<(), String> { + let mut game = from_fen(FEN_CAPTURE[0])?; + let mv = Move::new_with_type(21, 37, MoveType::Capture); + game.board.make_move(mv, Color::White); + assert_eq!(game, from_fen(FEN_CAPTURE[1])?); + + Ok(()) + } + + const FEN_EN_PASSANT: [&str; 2] = [ + "r3k2r/2p1p1qp/2npb3/1p3p2/p3P1pP/P1PB1Q2/1P1PNPP1/R3K2R b KQkq h3 0 1", + "r3k2r/2p1p1qp/2npb3/1p3p2/p3P3/P1PB1Q1p/1P1PNPP1/R3K2R w KQkq - 0 1", + ]; + + #[test] + fn test_make_move_en_passant() -> Result<(), String> { + let mut game = from_fen(FEN_EN_PASSANT[0])?; + let mv = Move::new_with_type(30, 23, MoveType::EnPassant); + game.board.make_move(mv, Color::Black); + assert_eq!(game, from_fen(FEN_EN_PASSANT[1])?); + + Ok(()) + } + + const FEN_DOUBLE_PUSH: [&str; 2] = [ + "rnbqkbnr/p1pppppp/8/8/1p5P/8/PPPPPPP1/RNBQKBNR w KQkq - 0 1", + "rnbqkbnr/p1pppppp/8/8/1pP4P/8/PP1PPPP1/RNBQKBNR b KQkq c3 0 1", + ]; + + #[test] + fn test_make_move_double_push() -> Result<(), String> { + let mut game = from_fen(FEN_DOUBLE_PUSH[0])?; + let mv = Move::new_with_type(10, 26, MoveType::DoublePush); + game.board.make_move(mv, Color::White); + assert_eq!(game, from_fen(FEN_DOUBLE_PUSH[1])?); + Ok(()) + } + + const FEN_PROMOTION: [&str; 2] = [ + "8/6P1/4n2b/1p6/1Kp5/6n1/4b3/1k6 w - - 0 1", + "6Q1/8/4n2b/1p6/1Kp5/6n1/4b3/1k6 b - - 0 1", + ]; + + #[test] + fn test_make_move_promotion() -> Result<(), String> { + let mut game = from_fen(FEN_PROMOTION[0])?; + let mv = Move::new_with_type(54, 62, MoveType::Promotion(Promote::Queen)); + game.board.make_move(mv, Color::White); + assert_eq!(game, from_fen(FEN_PROMOTION[1])?); + + Ok(()) + } + + const FEN_PROMOTION_CAPTURE: [&str; 2] = [ + "5n2/6P1/7b/1p6/1Kp5/6n1/4b3/1k6 w - - 0 1", + "5Q2/8/7b/1p6/1Kp5/6n1/4b3/1k6 b - - 0 1", + ]; + + #[test] + fn test_make_move_promotion_capture() -> Result<(), String> { + let mut game = from_fen(FEN_PROMOTION_CAPTURE[0])?; + let mv = Move::new_with_type(54, 61, MoveType::PromotionCapture(Promote::Queen)); + game.board.make_move(mv, Color::White); + assert_eq!(game, from_fen(FEN_PROMOTION_CAPTURE[1])?); + + Ok(()) + } + + const FEN_CASTLE: [&str; 2] = [ + "r3k2r/2p1p1qp/2npb3/1p3p2/p3P1pP/P1PB1Q2/1P1PNPP1/R3K2R w KQkq - 0 1", + "r3k2r/2p1p1qp/2npb3/1p3p2/p3P1pP/P1PB1Q2/1P1PNPP1/R4RK1 b kq - 0 1", + ]; + + #[test] + fn test_make_move_castle() -> Result<(), String> { + let mut game = from_fen(FEN_CASTLE[0])?; + let mv = Move::new_with_type(4, 6, MoveType::Castle); + game.board.make_move(mv, Color::White); + assert_eq!(game, from_fen(FEN_CASTLE[1])?); + + Ok(()) + } }