diff --git a/position/src/position.rs b/position/src/position.rs index f5f4213..86b2297 100644 --- a/position/src/position.rs +++ b/position/src/position.rs @@ -1,9 +1,11 @@ // Eryn Wells +mod castle; mod make_move; mod position; pub use { + castle::CastleEvaluationError, make_move::{MakeMoveError, ValidateMove}, - position::{CastleEvaluationError, Position}, + position::Position, }; diff --git a/position/src/position/castle.rs b/position/src/position/castle.rs new file mode 100644 index 0000000..717cc14 --- /dev/null +++ b/position/src/position/castle.rs @@ -0,0 +1,241 @@ +// Eryn Wells + +use crate::Position; +use chessfriend_core::{Color, Piece, Square, Wing}; +use thiserror::Error; + +#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)] +pub enum CastleEvaluationError { + #[error("{color} does not have the right to castle {wing}")] + NoRights { color: Color, wing: Wing }, + #[error("no king")] + NoKing, + #[error("no rook")] + NoRook, + #[error("castling path is not clear")] + ObstructingPieces, + #[error("opposing pieces check castling path")] + CheckingPieces, +} + +impl Position { + /// Evaluates whether the active color can castle toward the given wing of the board in the + /// current position. + /// + /// ## Errors + /// + /// Returns an error indicating why the active color cannot castle. + pub fn active_color_can_castle(&self, wing: Wing) -> Result<(), CastleEvaluationError> { + // TODO: Cache this result. It's expensive! + + let active_color = self.board.active_color; + + if !self + .board + .castling_rights + .color_has_right(active_color, wing) + { + return Err(CastleEvaluationError::NoRights { + color: active_color, + wing, + }); + } + + let parameters = self.board.castling_parameters(wing); + + if self.castling_king(parameters.origin.king).is_none() { + return Err(CastleEvaluationError::NoKing); + } + + if self.castling_rook(parameters.origin.rook).is_none() { + return Err(CastleEvaluationError::NoRook); + } + + // All squares must be clear. + let has_obstructing_pieces = (self.board.occupancy() & parameters.clear).is_populated(); + if has_obstructing_pieces { + return Err(CastleEvaluationError::ObstructingPieces); + } + + // King cannot pass through check. + let opposing_sight = self.opposing_sight(); + let opposing_pieces_can_see_castling_path = + (parameters.check & opposing_sight).is_populated(); + if opposing_pieces_can_see_castling_path { + return Err(CastleEvaluationError::CheckingPieces); + } + + Ok(()) + } + + pub(crate) fn castling_king(&self, square: Square) -> Option { + self.get_piece(square).and_then(|piece| { + if piece.color == self.board.active_color && piece.is_king() { + Some(piece) + } else { + None + } + }) + } + + pub(crate) fn castling_rook(&self, square: Square) -> Option { + self.get_piece(square).and_then(|piece| { + if piece.color == self.board.active_color && piece.is_rook() { + Some(piece) + } else { + None + } + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_position; + use chessfriend_core::{piece, Color, Wing}; + + #[test] + fn king_on_starting_square_can_castle() { + let pos = test_position!( + White King on E1, + White Rook on A1, + White Rook on H1 + ); + + let rights = pos.board.castling_rights; + assert!(rights.color_has_right(Color::White, Wing::KingSide)); + assert!(rights.color_has_right(Color::White, Wing::QueenSide)); + } + + #[test] + fn king_for_castle() { + let pos = test_position![ + White King on E1, + White Rook on H1, + White Rook on A1, + ]; + + let kingside_parameters = pos.board.castling_parameters(Wing::KingSide); + assert_eq!( + pos.castling_king(kingside_parameters.origin.king), + Some(piece!(White King)) + ); + + let queenside_parameters = pos.board.castling_parameters(Wing::QueenSide); + assert_eq!( + pos.castling_king(queenside_parameters.origin.king), + Some(piece!(White King)) + ); + } + + #[test] + fn rook_for_castle() { + let pos = test_position![ + White King on E1, + White Rook on H1, + ]; + + let kingside_parameters = pos.board.castling_parameters(Wing::KingSide); + assert_eq!( + pos.castling_rook(kingside_parameters.origin.rook), + Some(piece!(White Rook)) + ); + + let pos = test_position![ + White King on E1, + White Rook on A1, + ]; + + let queenside_parameters = pos.board.castling_parameters(Wing::QueenSide); + assert_eq!( + pos.castling_rook(queenside_parameters.origin.rook), + Some(piece!(White Rook)) + ); + } + + #[test] + fn white_can_castle() { + let pos = test_position![ + White King on E1, + White Rook on H1, + White Rook on A1, + ]; + + assert_eq!(pos.active_color_can_castle(Wing::KingSide), Ok(())); + assert_eq!(pos.active_color_can_castle(Wing::QueenSide), Ok(())); + } + + #[test] + fn white_cannot_castle_missing_king() { + let pos = test_position![ + White King on E2, + White Rook on H1, + White Rook on A1, + ]; + + assert_eq!( + pos.active_color_can_castle(Wing::KingSide), + Err(CastleEvaluationError::NoKing) + ); + assert_eq!( + pos.active_color_can_castle(Wing::QueenSide), + Err(CastleEvaluationError::NoKing) + ); + } + + #[test] + fn white_cannot_castle_missing_rook() { + let pos = test_position![ + White King on E1, + White Rook on A1, + ]; + + assert_eq!( + pos.active_color_can_castle(Wing::KingSide), + Err(CastleEvaluationError::NoRook) + ); + + let pos = test_position![ + White King on E1, + White Rook on H1, + ]; + + assert_eq!( + pos.active_color_can_castle(Wing::QueenSide), + Err(CastleEvaluationError::NoRook) + ); + } + + #[test] + fn white_cannot_castle_obstructing_piece() { + let pos = test_position![ + White King on E1, + White Bishop on F1, + White Rook on H1, + White Rook on A1, + ]; + + assert_eq!( + pos.active_color_can_castle(Wing::KingSide), + Err(CastleEvaluationError::ObstructingPieces) + ); + assert_eq!(pos.active_color_can_castle(Wing::QueenSide), Ok(())); + } + + #[test] + fn white_cannot_castle_checking_pieces() { + let pos = test_position![ + White King on E1, + White Rook on H1, + White Rook on A1, + Black Queen on C6, + ]; + + assert_eq!(pos.active_color_can_castle(Wing::KingSide), Ok(())); + assert_eq!( + pos.active_color_can_castle(Wing::QueenSide), + Err(CastleEvaluationError::CheckingPieces) + ); + } +} diff --git a/position/src/position/position.rs b/position/src/position/position.rs index 7ab96dd..9095aa7 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -113,91 +113,6 @@ impl Position { } } -#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)] -pub enum CastleEvaluationError { - #[error("{color} does not have the right to castle {wing}")] - NoRights { color: Color, wing: Wing }, - #[error("no king")] - NoKing, - #[error("no rook")] - NoRook, - #[error("castling path is not clear")] - ObstructingPieces, - #[error("opposing pieces check castling path")] - CheckingPieces, -} - -impl Position { - /// Evaluates whether the active color can castle toward the given wing of the board in the - /// current position. - /// - /// ## Errors - /// - /// Returns an error indicating why the active color cannot castle. - pub fn active_color_can_castle(&self, wing: Wing) -> Result<(), CastleEvaluationError> { - // TODO: Cache this result. It's expensive! - - let active_color = self.board.active_color; - - if !self - .board - .castling_rights - .color_has_right(active_color, wing) - { - return Err(CastleEvaluationError::NoRights { - color: active_color, - wing, - }); - } - - let parameters = self.board.castling_parameters(wing); - - if self.castling_king(parameters.origin.king).is_none() { - return Err(CastleEvaluationError::NoKing); - } - - if self.castling_rook(parameters.origin.rook).is_none() { - return Err(CastleEvaluationError::NoRook); - } - - // All squares must be clear. - let has_obstructing_pieces = (self.board.occupancy() & parameters.clear).is_populated(); - if has_obstructing_pieces { - return Err(CastleEvaluationError::ObstructingPieces); - } - - // King cannot pass through check. - let opposing_sight = self.opposing_sight(); - let opposing_pieces_can_see_castling_path = - (parameters.check & opposing_sight).is_populated(); - if opposing_pieces_can_see_castling_path { - return Err(CastleEvaluationError::CheckingPieces); - } - - Ok(()) - } - - fn castling_king(&self, square: Square) -> Option { - self.get_piece(square).and_then(|piece| { - if piece.color == self.board.active_color && piece.is_king() { - Some(piece) - } else { - None - } - }) - } - - fn castling_rook(&self, square: Square) -> Option { - self.get_piece(square).and_then(|piece| { - if piece.color == self.board.active_color && piece.is_rook() { - Some(piece) - } else { - None - } - }) - } -} - /* impl Position { pub fn moves(&self) -> &Moves { @@ -242,28 +157,6 @@ impl Position { self.board.en_passant().map(EnPassant::target_square) } - fn _sight_of_player(&self, player: Color, board: &Board) -> BitBoard { - let en_passant_target_square = self._en_passant_target_square(); - - Shape::ALL - .iter() - .filter_map(|&shape| { - let piece = Piece::new(player, shape); - let bitboard = board.bitboard_for_piece(piece); - if !bitboard.is_empty() { - Some((piece, bitboard)) - } else { - None - } - }) - .flat_map(|(piece, bitboard)| { - bitboard.occupied_squares().map(move |square| { - PlacedPiece::new(piece, square).sight(board, en_passant_target_square) - }) - }) - .fold(BitBoard::empty(), |acc, sight| acc | sight) - } - pub(crate) fn moves_for_piece(&self, piece: &PlacedPiece) -> Option<&MoveSet> { self.moves().moves_for_piece(piece) } @@ -378,43 +271,31 @@ mod tests { assert_eq!(pos.board.get_piece(Square::A8), Some(piece!(Black Rook))); } - #[test] - fn king_is_in_check() { - let pos = position![ - White King on E1, - Black Rook on E8, - ]; - assert!(pos.is_king_in_check()); - } + // #[test] + // fn king_is_in_check() { + // let pos = position![ + // White King on E1, + // Black Rook on E8, + // ]; + // assert!(pos.is_king_in_check()); + // } - #[test] - fn king_is_not_in_check() { - let pos = position![ - White King on F1, - Black Rook on E8, - ]; - assert!(!pos.is_king_in_check()); - } + // #[test] + // fn king_is_not_in_check() { + // let pos = position![ + // White King on F1, + // Black Rook on E8, + // ]; + // assert!(!pos.is_king_in_check()); + // } - #[test] - fn king_not_on_starting_square_cannot_castle() { - let pos = test_position!(White King on E4); - assert!(!pos.player_can_castle(Color::White, Castle::KingSide)); - assert!(!pos.player_can_castle(Color::White, Castle::QueenSide)); - } - - #[test] - fn king_on_starting_square_can_castle() { - let pos = test_position!( - White King on E1, - White Rook on A1, - White Rook on H1 - ); - - let rights = pos.board.castling_rights; - assert!(rights.color_has_right(Color::White, Wing::KingSide)); - assert!(rights.color_has_right(Color::White, Wing::QueenSide)); - } + // #[test] + // fn king_not_on_starting_square_cannot_castle() { + // let pos = test_position!(White King on E4); + // let rights = pos.board.castling_rights; + // assert!(!rights.color_has_right(Color::White, Castle::KingSide)); + // assert!(!rights.color_has_right(Color::White, Castle::QueenSide)); + // } #[test] fn friendly_sight() { @@ -437,134 +318,16 @@ mod tests { assert_eq!(sight, bitboard![A7 B7 C7 D7 F7 G7 H7 E8 E6 E5 E4]); } - #[test] - fn king_for_castle() { - let pos = test_position![ - White King on E1, - White Rook on H1, - White Rook on A1, - ]; + // #[test] + // fn danger_squares() { + // let pos = test_position!(Black, [ + // White King on E1, + // Black King on E7, + // White Rook on E4, + // ]); - let kingside_parameters = pos.board.castling_parameters(Wing::KingSide); - assert_eq!( - pos.castling_king(kingside_parameters.origin.king), - Some(piece!(White King)) - ); - - let queenside_parameters = pos.board.castling_parameters(Wing::QueenSide); - assert_eq!( - pos.castling_king(queenside_parameters.origin.king), - Some(piece!(White King)) - ); - } - - #[test] - fn rook_for_castle() { - let pos = test_position![ - White King on E1, - White Rook on H1, - ]; - - let kingside_parameters = pos.board.castling_parameters(Wing::KingSide); - assert_eq!( - pos.castling_rook(kingside_parameters.origin.rook), - Some(piece!(White Rook)) - ); - - let pos = test_position![ - White King on E1, - White Rook on A1, - ]; - - let queenside_parameters = pos.board.castling_parameters(Wing::QueenSide); - assert_eq!( - pos.castling_rook(queenside_parameters.origin.rook), - Some(piece!(White Rook)) - ); - } - - #[test] - fn white_can_castle() { - let pos = test_position![ - White King on E1, - White Rook on H1, - White Rook on A1, - ]; - - assert_eq!(pos.active_color_can_castle(Wing::KingSide), Ok(())); - assert_eq!(pos.active_color_can_castle(Wing::QueenSide), Ok(())); - } - - #[test] - fn white_cannot_castle_missing_king() { - let pos = test_position![ - White King on E2, - White Rook on H1, - White Rook on A1, - ]; - - assert_eq!( - pos.active_color_can_castle(Wing::KingSide), - Err(CastleEvaluationError::NoKing) - ); - assert_eq!( - pos.active_color_can_castle(Wing::QueenSide), - Err(CastleEvaluationError::NoKing) - ); - } - - #[test] - fn white_cannot_castle_missing_rook() { - let pos = test_position![ - White King on E1, - White Rook on A1, - ]; - - assert_eq!( - pos.active_color_can_castle(Wing::KingSide), - Err(CastleEvaluationError::NoRook) - ); - - let pos = test_position![ - White King on E1, - White Rook on H1, - ]; - - assert_eq!( - pos.active_color_can_castle(Wing::QueenSide), - Err(CastleEvaluationError::NoRook) - ); - } - - #[test] - fn white_cannot_castle_obstructing_piece() { - let pos = test_position![ - White King on E1, - White Bishop on F1, - White Rook on H1, - White Rook on A1, - ]; - - assert_eq!( - pos.active_color_can_castle(Wing::KingSide), - Err(CastleEvaluationError::ObstructingPieces) - ); - assert_eq!(pos.active_color_can_castle(Wing::QueenSide), Ok(())); - } - - #[test] - fn white_cannot_castle_checking_pieces() { - let pos = test_position![ - White King on E1, - White Rook on H1, - White Rook on A1, - Black Queen on C6, - ]; - - assert_eq!(pos.active_color_can_castle(Wing::KingSide), Ok(())); - assert_eq!( - pos.active_color_can_castle(Wing::QueenSide), - Err(CastleEvaluationError::CheckingPieces) - ); - } + // let danger_squares = pos.king_danger(Color::Black); + // let expected = bitboard![D1 F1 D2 E2 F2 E3 A4 B4 C4 D4 F4 G4 H4 E5 E6 E7 E8]; + // assert_eq_bitboards!(danger_squares, expected); + // } }