// Eryn Wells mod parameters; mod rights; pub use parameters::Parameters; pub use rights::Rights; use crate::{Board, CastleParameters}; 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 Board { #[must_use] pub fn castling_parameters(wing: Wing, color: Color) -> &'static CastleParameters { &CastleParameters::BY_COLOR[color as usize][wing as usize] } /// 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 color_can_castle( &self, wing: Wing, color: Option, ) -> Result<&'static CastleParameters, CastleEvaluationError> { // TODO: Cache this result. It's expensive! // TODO: Does this actually need to rely on internal state, i.e. active_color? let color = self.unwrap_color(color); if !self.color_has_castling_right_unwrapped(color, wing) { return Err(CastleEvaluationError::NoRights { color, wing }); } let parameters = Self::castling_parameters(wing, color); 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.occupancy() & parameters.clear).is_populated(); if has_obstructing_pieces { return Err(CastleEvaluationError::ObstructingPieces); } // King cannot pass through check. let opposing_sight = self.opposing_sight(color); 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(parameters) } pub(crate) fn castling_king(&self, square: Square) -> Option { let active_color = self.active_color(); self.get_piece(square) .filter(|piece| piece.color == active_color && piece.is_king()) } pub(crate) fn castling_rook(&self, square: Square) -> Option { let active_color = self.active_color(); self.get_piece(square) .filter(|piece| piece.color == active_color && piece.is_rook()) } } #[cfg(test)] mod tests { use super::*; use crate::test_board; use chessfriend_core::{Color, Wing, piece}; #[test] fn king_on_starting_square_can_castle() { let board = test_board!( White King on E1, White Rook on A1, White Rook on H1 ); assert!(board.color_has_castling_right_unwrapped(Color::White, Wing::KingSide)); assert!(board.color_has_castling_right_unwrapped(Color::White, Wing::QueenSide)); } #[test] fn king_for_castle() { let pos = test_board![ White King on E1, White Rook on H1, White Rook on A1, ]; let kingside_parameters = Board::castling_parameters(Wing::KingSide, Color::White); assert_eq!( pos.castling_king(kingside_parameters.origin.king), Some(piece!(White King)) ); let queenside_parameters = Board::castling_parameters(Wing::QueenSide, Color::White); assert_eq!( pos.castling_king(queenside_parameters.origin.king), Some(piece!(White King)) ); } #[test] fn rook_for_castle() { let pos = test_board![ White King on E1, White Rook on H1, ]; let kingside_parameters = Board::castling_parameters(Wing::KingSide, Color::White); assert_eq!( pos.castling_rook(kingside_parameters.origin.rook), Some(piece!(White Rook)) ); let pos = test_board![ White King on E1, White Rook on A1, ]; let queenside_parameters = Board::castling_parameters(Wing::QueenSide, Color::White); assert_eq!( pos.castling_rook(queenside_parameters.origin.rook), Some(piece!(White Rook)) ); } #[test] fn white_can_castle() -> Result<(), CastleEvaluationError> { let pos = test_board![ White King on E1, White Rook on H1, White Rook on A1, ]; pos.color_can_castle(Wing::KingSide, None)?; pos.color_can_castle(Wing::QueenSide, None)?; Ok(()) } #[test] fn white_cannot_castle_missing_king() { let pos = test_board![ White King on E2, White Rook on H1, White Rook on A1, ]; assert_eq!( pos.color_can_castle(Wing::KingSide, None), Err(CastleEvaluationError::NoKing) ); assert_eq!( pos.color_can_castle(Wing::QueenSide, None), Err(CastleEvaluationError::NoKing) ); } #[test] fn white_cannot_castle_missing_rook() { let pos = test_board![ White King on E1, White Rook on A1, ]; assert_eq!( pos.color_can_castle(Wing::KingSide, None), Err(CastleEvaluationError::NoRook) ); let pos = test_board![ White King on E1, White Rook on H1, ]; assert_eq!( pos.color_can_castle(Wing::QueenSide, None), Err(CastleEvaluationError::NoRook) ); } #[test] fn white_cannot_castle_obstructing_piece() { let pos = test_board![ White King on E1, White Bishop on F1, White Rook on H1, White Rook on A1, ]; assert_eq!( pos.color_can_castle(Wing::KingSide, None), Err(CastleEvaluationError::ObstructingPieces) ); assert!(pos.color_can_castle(Wing::QueenSide, None).is_ok()); } #[test] fn white_cannot_castle_checking_pieces() { let pos = test_board![ White King on E1, White Rook on H1, White Rook on A1, Black Queen on C6, ]; assert!(pos.color_can_castle(Wing::KingSide, None).is_ok()); assert_eq!( pos.color_can_castle(Wing::QueenSide, None), Err(CastleEvaluationError::CheckingPieces) ); } }