// Eryn Wells use crate::{ position::flags::Flags, r#move::Castle, sight::SightExt, MakeMoveError, Move, Position, }; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square}; /// A position builder that builds a new position by making a move. #[derive(Clone)] pub struct Builder<'p, M: MoveToMake> { position: &'p Position, move_to_make: M, } pub trait MoveToMake {} pub struct NoMove; pub enum ValidatedMove { RegularMove { from_square: Square, to_square: Square, moving_piece: PlacedPiece, captured_piece: Option, promotion: Option, flags: Flags, en_passant_square: Option, increment_ply: bool, }, Castle { castle: Castle, king: PlacedPiece, rook: PlacedPiece, flags: Flags, }, } impl MoveToMake for NoMove {} impl MoveToMake for ValidatedMove {} impl<'p> Builder<'p, NoMove> { pub fn new(position: &'p Position) -> Builder<'p, NoMove> { Builder { position, move_to_make: NoMove, } } } impl<'p, M> Builder<'p, M> where M: MoveToMake, { pub fn make(self, mv: &Move) -> Result, MakeMoveError> { let from_square = mv.from_square(); let piece = self .position .piece_on_square(from_square) .ok_or(MakeMoveError::NoPiece)?; let to_square = mv.to_square(); let sight = self.position.sight_of_piece(&piece); if !sight.is_set(to_square) { return Err(MakeMoveError::IllegalSquare(to_square)); } let player = self.position.player_to_move(); let captured_piece: Option = if mv.is_en_passant() { // En passant captures the pawn directly ahead (in the player's direction) of the en passant square. let capture_square = match player { Color::White => to_square.neighbor(Direction::South), Color::Black => to_square.neighbor(Direction::North), } .ok_or(MakeMoveError::NoCapturedPiece)?; Some( self.position .piece_on_square(capture_square) .ok_or(MakeMoveError::NoCapturedPiece)?, ) } else if mv.is_capture() { Some( self.position .piece_on_square(to_square) .ok_or(MakeMoveError::NoCapturedPiece)?, ) } else { None }; // TODO: Check whether the move is legal. let piece_is_king = piece.is_king(); let mut flags = self.position.flags().clone(); if piece_is_king { flags.clear_player_has_right_to_castle_flag(player, Castle::KingSide); flags.clear_player_has_right_to_castle_flag(player, Castle::QueenSide); } else if piece.is_kingside_rook() { flags.clear_player_has_right_to_castle_flag(player, Castle::KingSide); } else if piece.is_queenside_rook() { flags.clear_player_has_right_to_castle_flag(player, Castle::QueenSide); } if let Some(castle) = mv.castle() { println!("piece is king: {}", piece_is_king); if !piece_is_king || !self.position.player_can_castle(player, castle) { return Err(MakeMoveError::IllegalCastle); } let rook = self .position .rook_for_castle(player, castle) .ok_or(MakeMoveError::NoPiece)?; Ok(Builder { position: self.position, move_to_make: ValidatedMove::Castle { castle, king: piece, rook, flags, }, }) } else { let en_passant_square: Option = if mv.is_double_push() { match piece.color() { Color::White => to_square.neighbor(Direction::South), Color::Black => to_square.neighbor(Direction::North), } } else { None }; Ok(Builder { position: self.position, move_to_make: ValidatedMove::RegularMove { from_square, to_square, moving_piece: piece, captured_piece, promotion: mv.promotion(), flags, en_passant_square, increment_ply: !(mv.is_capture() || piece.is_pawn()), }, }) } } } impl<'p> Builder<'p, ValidatedMove> { pub fn build(&self) -> Position { let player = self.position.player_to_move(); let updated_move_number = self.position.move_number() + if player == Color::Black { 1 } else { 0 }; match self.move_to_make { ValidatedMove::RegularMove { from_square, to_square, moving_piece, captured_piece, promotion, flags, en_passant_square, increment_ply, } => { let mut pieces = self.position.piece_bitboards().clone(); if let Some(captured_piece) = captured_piece { pieces.remove_piece(&captured_piece); } if let Some(promotion) = promotion { pieces.remove_piece(&moving_piece); let _ = pieces .place_piece(&PlacedPiece::new(Piece::new(player, promotion), to_square)); } else { pieces.move_piece(moving_piece.piece(), from_square, to_square); } let ply = if increment_ply { self.position.ply_counter() + 1 } else { 0 }; Position::new( self.position.player_to_move().other(), flags, pieces, en_passant_square, ply, updated_move_number, ) } ValidatedMove::Castle { castle, king, rook, flags, } => { let mut pieces = self.position.piece_bitboards().clone(); let target_squares = castle.target_squares(player); let king_from: BitBoard = king.square().into(); let king_to: BitBoard = target_squares.king.into(); *pieces.bitboard_for_piece_mut(king.piece()) ^= king_from | king_to; let rook_from: BitBoard = rook.square().into(); let rook_to: BitBoard = target_squares.rook.into(); *pieces.bitboard_for_piece_mut(rook.piece()) ^= rook_from | rook_to; *pieces.bitboard_for_color_mut(player) &= !(king_from | rook_from) | (king_to | rook_to); Position::new( player.other(), flags, pieces, None, self.position.ply_counter() + 1, updated_move_number, ) } } } } impl<'p> From<&'p Position> for Builder<'p, NoMove> { fn from(position: &'p Position) -> Self { Self { position, move_to_make: NoMove, } } } #[cfg(test)] mod tests { use super::*; use crate::{position, r#move::Castle, MoveBuilder, PositionBuilder}; use chessfriend_core::piece; #[test] fn move_white_pawn_one_square() -> Result<(), MakeMoveError> { let pos = position![White Pawn on E2]; let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build(); let new_position = Builder::::new(&pos).make(&mv)?.build(); println!("{}", &new_position); assert_eq!( new_position.piece_on_square(Square::E3), Some(piece!(White Pawn on E3)) ); Ok(()) } #[test] fn move_white_pawn_two_squares() -> Result<(), MakeMoveError> { let pos = position![White Pawn on E2]; let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build(); let new_position = Builder::::new(&pos).make(&mv)?.build(); println!("{}", &new_position); assert_eq!( new_position.piece_on_square(Square::E4), Some(piece!(White Pawn on E4)) ); assert_eq!(new_position.en_passant_square(), Some(Square::E3)); Ok(()) } #[test] fn white_kingside_castle() -> Result<(), MakeMoveError> { let pos = position![ White King on E1, White Rook on H1, White Pawn on E2, White Pawn on F2, White Pawn on G2, White Pawn on H2 ]; println!("{}", &pos); let mv = MoveBuilder::new(piece!(White King), Square::E1, Square::G1) .castle(Castle::KingSide) .build(); let new_position = Builder::::new(&pos).make(&mv)?.build(); println!("{}", &new_position); assert_eq!( new_position.piece_on_square(Square::G1), Some(piece!(White King on G1)) ); assert_eq!( new_position.piece_on_square(Square::F1), Some(piece!(White Rook on F1)) ); Ok(()) } #[test] fn en_passant_capture() -> Result<(), MakeMoveError> { let pos = PositionBuilder::new() .place_piece(piece!(White Pawn on B5)) .place_piece(piece!(Black Pawn on A7)) .to_move(Color::Black) .build(); println!("{pos}"); let black_pawn_move = MoveBuilder::new(piece!(Black Pawn), Square::A7, Square::A5).build(); assert!(black_pawn_move.is_double_push()); assert!(!black_pawn_move.is_en_passant()); let en_passant_position = Builder::::new(&pos).make(&black_pawn_move)?.build(); println!("{en_passant_position}"); assert_eq!( en_passant_position.piece_on_square(Square::A5), Some(piece!(Black Pawn on A5)) ); assert_eq!( en_passant_position.piece_on_square(Square::B5), Some(piece!(White Pawn on B5)) ); let white_pawn_capture = MoveBuilder::new(piece!(White Pawn), Square::B5, Square::A6) .capturing_en_passant(piece!(Black Pawn on A5)) .build(); let en_passant_capture = Builder::::new(&en_passant_position) .make(&white_pawn_capture)? .build(); println!("{en_passant_capture}"); assert_eq!(en_passant_capture.piece_on_square(Square::A5), None); assert_eq!(en_passant_capture.piece_on_square(Square::B5), None); assert_eq!( en_passant_capture.piece_on_square(Square::A6), Some(piece!(White Pawn on A6)) ); Ok(()) } }