// Eryn Wells mod parameters; mod rights; pub use parameters::Parameters; pub use rights::{CastleRightsOption, Rights}; use crate::{Board, CastleParameters}; use chessfriend_core::{Color, 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.has_castling_right_unwrapped(color, wing.into()) { 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) } } impl Board { #[must_use] pub fn has_castling_right(&self, color: Option, wing: Wing) -> bool { self.has_castling_right_unwrapped(self.unwrap_color(color), wing) } #[must_use] pub fn has_castling_right_active(&self, wing: Wing) -> bool { self.has_castling_right_unwrapped(self.active_color(), wing) } #[must_use] pub fn has_castling_right_unwrapped(&self, color: Color, wing: Wing) -> bool { self.castling_rights().get(color, wing.into()) } } impl Board { pub fn grant_castling_rights(&mut self, color: Option, rights: CastleRightsOption) { let color = self.unwrap_color(color); self.grant_castling_rights_unwrapped(color, rights); } pub fn grant_castling_rights_active(&mut self, rights: CastleRightsOption) { self.grant_castling_rights_unwrapped(self.active_color(), rights); } pub fn grant_castling_rights_unwrapped(&mut self, color: Color, rights: CastleRightsOption) { let old_rights = *self.castling_rights(); self.castling_rights_mut().grant(color, rights); self.update_zobrist_hash_castling_rights(old_rights); } } impl Board { pub fn revoke_all_castling_rights(&mut self) { self.castling_rights_mut().revoke_all(); } pub fn revoke_castling_rights(&mut self, color: Option, rights: CastleRightsOption) { let color = self.unwrap_color(color); self.revoke_castling_rights_unwrapped(color, rights); } pub fn revoke_castling_rights_active(&mut self, rights: CastleRightsOption) { self.revoke_castling_rights_unwrapped(self.active_color(), rights); } pub fn revoke_castling_rights_unwrapped(&mut self, color: Color, rights: CastleRightsOption) { let old_rights = *self.castling_rights(); self.castling_rights_mut().revoke(color, rights); self.update_zobrist_hash_castling_rights(old_rights); } } #[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.has_castling_right_unwrapped(Color::White, Wing::KingSide)); assert!(board.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) ); } }