diff --git a/board/src/board.rs b/board/src/board.rs index e5e0cbc..4746e95 100644 --- a/board/src/board.rs +++ b/board/src/board.rs @@ -7,7 +7,7 @@ use crate::{ PieceSet, }; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, Shape, Square, Wing}; +use chessfriend_core::{Color, Piece, Shape, Square}; pub type HalfMoveClock = u32; pub type FullMoveClock = u32; @@ -131,12 +131,9 @@ impl Board { pub fn queens(&self, color: Color) -> BitBoard { self.find_pieces(Piece::queen(color)) } -} -impl Board { - #[must_use] - pub fn castling_parameters(&self, wing: Wing) -> &'static castle::Parameters { - &castle::Parameters::BY_COLOR[self.active_color as usize][wing as usize] + pub fn kings(&self, color: Color) -> BitBoard { + self.find_pieces(Piece::king(color)) } } diff --git a/board/src/castle.rs b/board/src/castle.rs index 6581676..ef3a901 100644 --- a/board/src/castle.rs +++ b/board/src/castle.rs @@ -6,7 +6,7 @@ mod rights; pub use parameters::Parameters; pub use rights::Rights; -use crate::Board; +use crate::{Board, CastleParameters}; use chessfriend_core::{Color, Piece, Square, Wing}; use thiserror::Error; @@ -25,26 +25,32 @@ pub enum CastleEvaluationError { } 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 active_color_can_castle(&self, wing: Wing) -> Result<(), CastleEvaluationError> { + 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 active_color = self.active_color; + let color = self.unwrap_color(color); - if !self.castling_rights.color_has_right(active_color, wing) { - return Err(CastleEvaluationError::NoRights { - color: active_color, - wing, - }); + if !self.castling_rights.color_has_right(color, wing) { + return Err(CastleEvaluationError::NoRights { color, wing }); } - let parameters = self.castling_parameters(wing); + let parameters = Self::castling_parameters(wing, color); if self.castling_king(parameters.origin.king).is_none() { return Err(CastleEvaluationError::NoKing); @@ -61,14 +67,14 @@ impl Board { } // King cannot pass through check. - let opposing_sight = self.opposing_sight(active_color); + 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(()) + Ok(parameters) } pub(crate) fn castling_king(&self, square: Square) -> Option { @@ -111,13 +117,13 @@ mod tests { White Rook on A1, ]; - let kingside_parameters = pos.castling_parameters(Wing::KingSide); + 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 = pos.castling_parameters(Wing::QueenSide); + let queenside_parameters = Board::castling_parameters(Wing::QueenSide, Color::White); assert_eq!( pos.castling_king(queenside_parameters.origin.king), Some(piece!(White King)) @@ -131,7 +137,7 @@ mod tests { White Rook on H1, ]; - let kingside_parameters = pos.castling_parameters(Wing::KingSide); + let kingside_parameters = Board::castling_parameters(Wing::KingSide, Color::White); assert_eq!( pos.castling_rook(kingside_parameters.origin.rook), Some(piece!(White Rook)) @@ -142,7 +148,7 @@ mod tests { White Rook on A1, ]; - let queenside_parameters = pos.castling_parameters(Wing::QueenSide); + let queenside_parameters = Board::castling_parameters(Wing::QueenSide, Color::White); assert_eq!( pos.castling_rook(queenside_parameters.origin.rook), Some(piece!(White Rook)) @@ -150,15 +156,17 @@ mod tests { } #[test] - fn white_can_castle() { + fn white_can_castle() -> Result<(), CastleEvaluationError> { let pos = test_board![ 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(())); + pos.color_can_castle(Wing::KingSide, None)?; + pos.color_can_castle(Wing::QueenSide, None)?; + + Ok(()) } #[test] @@ -170,11 +178,11 @@ mod tests { ]; assert_eq!( - pos.active_color_can_castle(Wing::KingSide), + pos.color_can_castle(Wing::KingSide, None), Err(CastleEvaluationError::NoKing) ); assert_eq!( - pos.active_color_can_castle(Wing::QueenSide), + pos.color_can_castle(Wing::QueenSide, None), Err(CastleEvaluationError::NoKing) ); } @@ -187,7 +195,7 @@ mod tests { ]; assert_eq!( - pos.active_color_can_castle(Wing::KingSide), + pos.color_can_castle(Wing::KingSide, None), Err(CastleEvaluationError::NoRook) ); @@ -197,7 +205,7 @@ mod tests { ]; assert_eq!( - pos.active_color_can_castle(Wing::QueenSide), + pos.color_can_castle(Wing::QueenSide, None), Err(CastleEvaluationError::NoRook) ); } @@ -212,10 +220,10 @@ mod tests { ]; assert_eq!( - pos.active_color_can_castle(Wing::KingSide), + pos.color_can_castle(Wing::KingSide, None), Err(CastleEvaluationError::ObstructingPieces) ); - assert_eq!(pos.active_color_can_castle(Wing::QueenSide), Ok(())); + assert!(pos.color_can_castle(Wing::QueenSide, None).is_ok()); } #[test] @@ -227,9 +235,9 @@ mod tests { Black Queen on C6, ]; - assert_eq!(pos.active_color_can_castle(Wing::KingSide), Ok(())); + assert!(pos.color_can_castle(Wing::KingSide, None).is_ok()); assert_eq!( - pos.active_color_can_castle(Wing::QueenSide), + pos.color_can_castle(Wing::QueenSide, None), Err(CastleEvaluationError::CheckingPieces) ); } diff --git a/board/src/castle/parameters.rs b/board/src/castle/parameters.rs index bc4e238..54c3512 100644 --- a/board/src/castle/parameters.rs +++ b/board/src/castle/parameters.rs @@ -1,7 +1,7 @@ use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Square, Wing}; -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct Parameters { /// Origin squares of the king and rook. pub origin: Squares, @@ -18,7 +18,7 @@ pub struct Parameters { pub check: BitBoard, } -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct Squares { pub king: Square, pub rook: Square, diff --git a/board/src/movement.rs b/board/src/movement.rs index a1387db..f386579 100644 --- a/board/src/movement.rs +++ b/board/src/movement.rs @@ -24,7 +24,8 @@ pub trait Movement { impl Movement for Piece { fn movement(&self, square: Square, board: &Board) -> BitBoard { - let opposing_occupancy = board.opposing_occupancy(self.color); + let color = self.color; + let opposing_occupancy = board.opposing_occupancy(color); match self.shape { Shape::Pawn => { @@ -36,20 +37,22 @@ impl Movement for Piece { } Shape::King => { let kingside_target_square = - if board.active_color_can_castle(Wing::KingSide).is_ok() { - let parameters = board.castling_parameters(Wing::KingSide); + if board.color_can_castle(Wing::KingSide, Some(color)).is_ok() { + let parameters = Board::castling_parameters(Wing::KingSide, color); parameters.target.king.into() } else { BitBoard::empty() }; - let queenside_target_square = - if board.active_color_can_castle(Wing::QueenSide).is_ok() { - let parameters = board.castling_parameters(Wing::QueenSide); - parameters.target.king.into() - } else { - BitBoard::empty() - }; + let queenside_target_square = if board + .color_can_castle(Wing::QueenSide, Some(self.color)) + .is_ok() + { + let parameters = Board::castling_parameters(Wing::QueenSide, color); + parameters.target.king.into() + } else { + BitBoard::empty() + }; self.sight(square, board) | kingside_target_square | queenside_target_square } diff --git a/board/src/sight.rs b/board/src/sight.rs index 8865097..8518300 100644 --- a/board/src/sight.rs +++ b/board/src/sight.rs @@ -114,6 +114,7 @@ impl Board { sight_method!(bishop_sight); sight_method!(rook_sight); sight_method!(queen_sight); + sight_method!(king_sight); } struct SightInfo { diff --git a/moves/src/generators.rs b/moves/src/generators.rs index d5c9e37..a3b6fcf 100644 --- a/moves/src/generators.rs +++ b/moves/src/generators.rs @@ -1,5 +1,6 @@ // Eryn Wells +mod king; mod knight; mod pawn; mod slider; @@ -7,6 +8,7 @@ mod slider; #[cfg(test)] mod testing; +pub use king::KingMoveGenerator; pub use knight::KnightMoveGenerator; pub use pawn::PawnMoveGenerator; pub use slider::{BishopMoveGenerator, QueenMoveGenerator, RookMoveGenerator}; diff --git a/moves/src/generators/king.rs b/moves/src/generators/king.rs new file mode 100644 index 0000000..b091b3d --- /dev/null +++ b/moves/src/generators/king.rs @@ -0,0 +1,361 @@ +// Eryn Wells + +use crate::{GeneratedMove, Move}; +use chessfriend_bitboard::{bit_scanner::TrailingBitScanner, BitBoard}; +use chessfriend_board::{Board, CastleParameters}; +use chessfriend_core::{Color, Square, Wing}; + +#[must_use] +pub struct KingMoveGenerator { + kings: Vec, + next_kings_index: usize, + current_king: Option, + castle_iterator: CastleIterator, + friends: BitBoard, + enemies: BitBoard, +} + +impl KingMoveGenerator { + pub fn new(board: &Board, color: Option) -> Self { + let color = board.unwrap_color(color); + + let friends = board.friendly_occupancy(color); + let enemies = board.enemies(color); + + let kings: Vec = board + .kings(color) + .occupied_squares_trailing() + .map(|king| KingIterator { + origin: king, + moves: board + .king_sight(king, Some(color)) + .occupied_squares_trailing(), + }) + .collect(); + + Self { + kings, + next_kings_index: 0, + current_king: None, + castle_iterator: CastleIterator::new(board, color), + friends, + enemies, + } + } +} + +impl Iterator for KingMoveGenerator { + type Item = GeneratedMove; + + fn next(&mut self) -> Option { + loop { + if self.current_king.is_none() { + if self.next_kings_index < self.kings.len() { + self.current_king = Some(self.kings[self.next_kings_index].clone()); + self.next_kings_index += 1; + } else { + break; + } + } + + if let Some(current_king) = self.current_king.as_mut() { + if let Some(target) = current_king.next() { + let target_bitboard: BitBoard = target.into(); + + let is_targeting_friendly_piece = + (target_bitboard & self.friends).is_populated(); + if is_targeting_friendly_piece { + continue; + } + + let is_targeting_enemy_piece = (target_bitboard & self.enemies).is_populated(); + if is_targeting_enemy_piece { + return Some(Move::capture(current_king.origin, target).into()); + } + + return Some(Move::quiet(current_king.origin, target).into()); + } + + self.current_king = None; + } + } + + self.castle_iterator.next() + } +} + +#[derive(Clone, Debug)] +struct KingIterator { + origin: Square, + moves: TrailingBitScanner, +} + +impl Iterator for KingIterator { + type Item = Square; + + fn next(&mut self) -> Option { + self.moves.next() + } +} + +#[derive(Clone, Debug)] +struct CastleIterator { + kingside: Option<&'static CastleParameters>, + queenside: Option<&'static CastleParameters>, +} + +impl CastleIterator { + fn new(board: &Board, color: Color) -> Self { + let kingside = board.color_can_castle(Wing::KingSide, Some(color)).ok(); + let queenside = board.color_can_castle(Wing::QueenSide, Some(color)).ok(); + + Self { + kingside, + queenside, + } + } +} + +impl Iterator for CastleIterator { + type Item = GeneratedMove; + + fn next(&mut self) -> Option { + if let Some(_parameters) = self.kingside.take() { + return Some(Move::castle(Wing::KingSide).into()); + } + + if let Some(_parameters) = self.queenside.take() { + return Some(Move::castle(Wing::QueenSide).into()); + } + + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{assert_move_list, ply}; + use chessfriend_board::test_board; + + #[test] + fn white_king_center_square_ai_claude() { + let board = test_board!(White King on E4); + assert_move_list!( + KingMoveGenerator::new(&board, None), + [ + // All 8 adjacent squares + ply!(E4 - D3), // Southwest + ply!(E4 - D4), // West + ply!(E4 - D5), // Northwest + ply!(E4 - E3), // South + ply!(E4 - E5), // North + ply!(E4 - F3), // Southeast + ply!(E4 - F4), // East + ply!(E4 - F5), // Northeast + ] + ); + } + + #[test] + fn white_king_corner_square_ai_claude() { + let board = test_board!(White King on A1); + assert_move_list!( + KingMoveGenerator::new(&board, None), + [ + // Only 3 possible moves from corner + ply!(A1 - A2), // North + ply!(A1 - B1), // East + ply!(A1 - B2), // Northeast + ] + ); + } + + #[test] + fn white_king_edge_square_ai_claude() { + let board = test_board!(White King on E1); + assert_move_list!( + KingMoveGenerator::new(&board, None), + [ + // 5 possible moves from edge + ply!(E1 - D1), // West + ply!(E1 - D2), // Northwest + ply!(E1 - E2), // North + ply!(E1 - F1), // East + ply!(E1 - F2), // Northeast + ] + ); + } + + #[test] + fn white_king_with_captures_ai_claude() { + let board = test_board!( + White King on D4, + Black Pawn on C5, // Can capture + Black Knight on E5, // Can capture + Black Bishop on D3 // Can capture + ); + assert_move_list!( + KingMoveGenerator::new(&board, None), + [ + // Regular moves + ply!(D4 - C3), + ply!(D4 - C4), + ply!(D4 - D5), + ply!(D4 - E3), + ply!(D4 - E4), + // Captures + ply!(D4 x C5), // Capture pawn + ply!(D4 x E5), // Capture knight + ply!(D4 x D3), // Capture bishop + ] + ); + } + + #[test] + fn white_king_blocked_by_friendly_pieces_ai_claude() { + let board = test_board!( + White King on D4, + White Pawn on C4, // Blocks west + White Knight on D5, // Blocks north + White Bishop on E3 // Blocks southeast + ); + assert_move_list!( + KingMoveGenerator::new(&board, None), + [ + ply!(D4 - C3), + ply!(D4 - C5), + ply!(D4 - D3), + ply!(D4 - E4), + ply!(D4 - E5), + // Cannot move to C4, D5, E3 (friendly pieces) + ] + ); + } + + #[test] + fn white_king_castling_kingside_ai_claude() { + let board = test_board!( + White King on E1, + White Rook on H1 + // Assuming squares F1, G1 are empty and king/rook haven't moved + ); + assert_move_list!( + KingMoveGenerator::new(&board, None), + [ + // Regular king moves + ply!(E1 - D1), + ply!(E1 - D2), + ply!(E1 - E2), + ply!(E1 - F1), + ply!(E1 - F2), + ply!(0 - 0), + ] + ); + } + + #[test] + fn white_king_castling_queenside_ai_claude() { + let board = test_board!( + White King on E1, + White Rook on A1 + // Assuming squares B1, C1, D1 are empty and king/rook haven't moved + ); + assert_move_list!( + KingMoveGenerator::new(&board, None), + [ + // Regular king moves + ply!(E1 - D1), + ply!(E1 - D2), + ply!(E1 - E2), + ply!(E1 - F1), + ply!(E1 - F2), + ply!(0 - 0 - 0), + ] + ); + } + + #[test] + fn white_king_no_castling_through_check_ai_claude() { + let board = test_board!( + White King on E1, + White Rook on H1, + Black Rook on F8 // Attacks F1, preventing kingside castling + ); + assert_move_list!( + KingMoveGenerator::new(&board, None), + [ + ply!(E1 - D1), + ply!(E1 - D2), + ply!(E1 - E2), + ply!(E1 - F1), + ply!(E1 - F2), + // No castling moves - cannot castle through check + ] + ); + } + + #[test] + fn white_king_castling_blocked_by_pieces_ai_claude() { + let board = test_board!( + White King on E1, + White Rook on H1, + White Knight on G1, // Blocks kingside castling + ); + assert_move_list!( + KingMoveGenerator::new(&board, None), + [ + ply!(E1 - D1), + ply!(E1 - D2), + ply!(E1 - E2), + ply!(E1 - F1), + ply!(E1 - F2), + // No castling - path blocked by knight + ] + ); + } + + #[test] + fn black_king_movement_ai_claude() { + let board = test_board!(Black King on E8); + assert_move_list!( + KingMoveGenerator::new(&board, Some(Color::Black)), + [ + ply!(E8 - D7), + ply!(E8 - D8), + ply!(E8 - E7), + ply!(E8 - F7), + ply!(E8 - F8), + ] + ); + } + + #[test] + fn white_king_surrounded_by_enemies_ai_claude() { + let board = test_board!( + White King on E4, + Black Pawn on D3, + Black Pawn on D4, + Black Pawn on D5, + Black Pawn on E3, + Black Pawn on E5, + Black Pawn on F3, + Black Pawn on F4, + Black Pawn on F5 + ); + assert_move_list!( + KingMoveGenerator::new(&board, None), + [ + // Can capture all surrounding enemy pieces + ply!(E4 x D3), + ply!(E4 x D4), + ply!(E4 x D5), + ply!(E4 x E3), + ply!(E4 x E5), + ply!(E4 x F3), + ply!(E4 x F4), + ply!(E4 x F5), + ] + ); + } +} diff --git a/moves/src/moves.rs b/moves/src/moves.rs index d3fd940..59ac237 100644 --- a/moves/src/moves.rs +++ b/moves/src/moves.rs @@ -108,12 +108,12 @@ impl Move { } #[must_use] - pub fn castle(origin: Square, target: Square, wing: Wing) -> Self { + pub fn castle(wing: Wing) -> Self { let flag_bits = match wing { Wing::KingSide => Kind::KingSideCastle, Wing::QueenSide => Kind::QueenSideCastle, } as u16; - Move(origin_bits(origin) | target_bits(target) | flag_bits) + Move(flag_bits) } } diff --git a/position/src/position/make_move.rs b/position/src/position/make_move.rs index 3c3ad3b..e944e3f 100644 --- a/position/src/position/make_move.rs +++ b/position/src/position/make_move.rs @@ -2,7 +2,7 @@ use crate::{move_record::MoveRecord, Position}; use chessfriend_board::{ - castle::CastleEvaluationError, movement::Movement, PlacePieceError, PlacePieceStrategy, + castle::CastleEvaluationError, movement::Movement, Board, PlacePieceError, PlacePieceStrategy, }; use chessfriend_core::{Color, Piece, Rank, Square, Wing}; use chessfriend_moves::Move; @@ -79,17 +79,18 @@ impl Position { ply: Move, validate: ValidateMove, ) -> MakeMoveResult { - self.validate_move(ply, validate)?; - if ply.is_quiet() { + self.validate_move(ply, validate)?; return self.make_quiet_move(ply); } if ply.is_double_push() { + self.validate_move(ply, validate)?; return self.make_double_push_move(ply); } if ply.is_capture() { + self.validate_move(ply, validate)?; return self.make_capture_move(ply); } @@ -98,6 +99,7 @@ impl Position { } if ply.is_promotion() { + self.validate_move(ply, validate)?; return self.make_promotion_move(ply); } @@ -189,10 +191,10 @@ impl Position { } fn make_castle_move(&mut self, ply: Move, wing: Wing) -> MakeMoveResult { - self.board.active_color_can_castle(wing)?; + self.board.color_can_castle(wing, None)?; let active_color = self.board.active_color; - let parameters = self.board.castling_parameters(wing); + let parameters = Board::castling_parameters(wing, active_color); let king = self.board.remove_piece(parameters.origin.king).unwrap(); self.place_piece(king, parameters.target.king, PlacePieceStrategy::default())?; @@ -490,7 +492,7 @@ mod tests { White King on E1, ]; - let ply = Move::castle(Square::E1, Square::G1, Wing::KingSide); + let ply = Move::castle(Wing::KingSide); pos.make_move(ply, ValidateMove::Yes)?; assert_eq!(pos.board.active_color, Color::Black); @@ -513,7 +515,7 @@ mod tests { White Rook on A1, ]; - let ply = Move::castle(Square::E1, Square::C1, Wing::QueenSide); + let ply = Move::castle(Wing::QueenSide); pos.make_move(ply, ValidateMove::Yes)?; assert_eq!(pos.board.active_color, Color::Black);