diff --git a/board/src/bitboard/bitboard.rs b/board/src/bitboard/bitboard.rs index 6480e99..6aa4c20 100644 --- a/board/src/bitboard/bitboard.rs +++ b/board/src/bitboard/bitboard.rs @@ -61,7 +61,7 @@ impl BitBoard { *self |= sq_bb } - fn clear_square(&mut self, sq: Square) { + pub fn clear_square(&mut self, sq: Square) { let sq_bb: BitBoard = sq.into(); *self &= !sq_bb } diff --git a/board/src/move.rs b/board/src/move.rs index 424d7fb..45c3f7b 100644 --- a/board/src/move.rs +++ b/board/src/move.rs @@ -10,6 +10,14 @@ use std::fmt; pub(crate) use move_formatter::AlgebraicMoveFormatter; +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MakeMoveError { + PlayerOutOfTurn, + NoPiece, + NoCapturedPiece, + IllegalCastle, +} + #[repr(u16)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Castle { diff --git a/board/src/position/builders/mod.rs b/board/src/position/builders/mod.rs index a8886b3..9324249 100644 --- a/board/src/position/builders/mod.rs +++ b/board/src/position/builders/mod.rs @@ -1,5 +1,7 @@ // Eryn Wells +mod move_builder; mod position_builder; +pub use move_builder::Builder as MoveBuilder; pub use position_builder::Builder as PositionBuilder; diff --git a/board/src/position/builders/move_builder.rs b/board/src/position/builders/move_builder.rs new file mode 100644 index 0000000..1dac36c --- /dev/null +++ b/board/src/position/builders/move_builder.rs @@ -0,0 +1,273 @@ +// Eryn Wells + +use crate::{ + piece::{PlacedPiece, Shape}, + position::flags::Flags, + r#move::Castle, + square::Direction, + BitBoard, Color, MakeMoveError, Move, Piece, Position, 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, + }, + Castle { + castle: Castle, + king: PlacedPiece, + rook: PlacedPiece, + flags: Flags, + }, +} + +impl MoveToMake for NoMove {} +impl MoveToMake for ValidatedMove {} + +impl<'p, M> Builder<'p, M> +where + M: MoveToMake, +{ + pub fn new(position: &'p Position) -> Builder<'p, NoMove> { + Builder { + position, + move_to_make: NoMove, + } + } + + 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)?; + println!("{}", &piece); + + let to_square = mv.to_square(); + let player = self.position.player_to_move(); + + let captured_piece: Option = 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, + }, + }) + } + } +} + +impl<'p> Builder<'p, ValidatedMove> { + pub fn build(&self) -> Position { + let player = self.position.player_to_move(); + + match self.move_to_make { + ValidatedMove::RegularMove { + from_square, + to_square, + moving_piece, + captured_piece, + promotion, + flags, + en_passant_square, + } => { + 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); + } + + Position::new( + self.position.player_to_move().other(), + flags, + pieces, + en_passant_square, + ) + } + ValidatedMove::Castle { + castle, + king, + rook, + flags, + } => { + let mut pieces = self.position.piece_bitboards().clone(); + + let king_from: BitBoard = king.square().into(); + let king_to: BitBoard = Square::king_castle_target(player, castle).into(); + *pieces.bitboard_for_piece_mut(king.piece()) ^= king_from | king_to; + + let rook_from: BitBoard = rook.square().into(); + let rook_to: BitBoard = Square::rook_castle_target(player, castle).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) + } + } + } +} + +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::{r#move::Castle, MoveBuilder}; + + #[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(()) + } +} diff --git a/board/src/position/mod.rs b/board/src/position/mod.rs index 987c107..b6805e3 100644 --- a/board/src/position/mod.rs +++ b/board/src/position/mod.rs @@ -7,7 +7,7 @@ mod piece_sets; mod pieces; mod position; -pub use builders::Builder as PositionBuilder; +pub use builders::{MoveBuilder, PositionBuilder}; pub use diagram_formatter::DiagramFormatter; pub use pieces::Pieces; pub use position::Position; diff --git a/board/src/position/piece_sets.rs b/board/src/position/piece_sets.rs index cd8f26d..07a1f03 100644 --- a/board/src/position/piece_sets.rs +++ b/board/src/position/piece_sets.rs @@ -95,12 +95,28 @@ impl PieceBitBoards { } } + self.by_color_and_shape.set_square(square, piece.piece()); self.by_color.set_square(square, color); - self.bitboard_for_piece_mut(piece.piece()) - .set_square(square); Ok(()) } + + pub(super) fn remove_piece(&mut self, piece: &PlacedPiece) { + let color = piece.color(); + let square = piece.square(); + + self.by_color_and_shape.clear_square(square, piece.piece()); + self.by_color.clear_square(square, color); + } + + pub(super) fn move_piece(&mut self, piece: &Piece, from_square: Square, to_square: Square) { + let color = piece.color(); + + self.by_color_and_shape.clear_square(from_square, piece); + self.by_color.clear_square(from_square, color); + self.by_color_and_shape.set_square(to_square, piece); + self.by_color.set_square(to_square, color); + } } impl FromIterator for PieceBitBoards { @@ -132,6 +148,11 @@ impl ByColor { self.0.set_square(square); self.1[color as usize].set_square(square) } + + fn clear_square(&mut self, square: Square, color: Color) { + self.0.clear_square(square); + self.1[color as usize].clear_square(square); + } } impl ByColorAndShape { @@ -146,4 +167,8 @@ impl ByColorAndShape { fn set_square(&mut self, square: Square, piece: &Piece) { self.bitboard_for_piece_mut(piece).set_square(square); } + + fn clear_square(&mut self, square: Square, piece: &Piece) { + self.bitboard_for_piece_mut(piece).clear_square(square); + } } diff --git a/board/src/position/position.rs b/board/src/position/position.rs index 1c2831d..b547d09 100644 --- a/board/src/position/position.rs +++ b/board/src/position/position.rs @@ -109,6 +109,17 @@ impl Position { true } + pub(crate) fn rook_for_castle(&self, player: Color, castle: Castle) -> Option { + let square = match (player, castle) { + (Color::White, Castle::KingSide) => Square::H1, + (Color::White, Castle::QueenSide) => Square::A1, + (Color::Black, Castle::KingSide) => Square::H8, + (Color::Black, Castle::QueenSide) => Square::A8, + }; + + self.piece_on_square(square) + } + pub fn moves(&self) -> Moves { Moves::new(self, self.color_to_move) }