[bitboard, core, position] Implement proper castle move generation

Add a field to MoveSet called special that flags special moves that should be
generated in the iter() method. This field is a u8. It only tracks castles in
the first and second bits (kingside and queenside, respectively). The move iterator
chains two maps over Option<()> that produce the kingside and queenside castle
moves.

With that done, finish the implementation of Position::player_can_castle by
adding checks for whether the squares between the rook and king are clear, and
that the king would not pass through a check. This is done with BitBoards!

Finally, implement some logic in PositionBuilder that updates the position's
castling flags based on the positions of king and rooks.

Supporting changes:
- Add Color:ALL and iterate on that slice
- Add Castle::ALL and iterator on that slice
- Add a CastlingParameters struct that contains BitBoard properties that describe
  squares that should be clear of pieces and squares that should not be attacked.
This commit is contained in:
Eryn Wells 2024-01-29 14:44:48 -08:00
parent 83a4e47e56
commit 1d7dada987
6 changed files with 234 additions and 60 deletions

View file

@ -61,9 +61,11 @@ impl Builder {
pub fn place_piece(&mut self, piece: PlacedPiece) -> &mut Self {
let square = piece.square();
let shape = piece.shape();
if piece.shape() == Shape::King {
let color_index: usize = piece.color() as usize;
if shape == Shape::King {
let color = piece.color();
let color_index: usize = color as usize;
self.pieces.remove(&self.kings[color_index]);
self.kings[color_index] = square;
}
@ -81,9 +83,27 @@ impl Builder {
.filter(Self::is_piece_placement_valid),
);
let mut flags = self.flags;
for color in Color::ALL {
for castle in Castle::ALL {
let starting_squares = castle.starting_squares(color);
let has_rook_on_starting_square = self
.pieces
.get(&starting_squares.rook)
.is_some_and(|piece| piece.shape() == Shape::Rook);
let king_is_on_starting_square =
self.kings[color as usize] == starting_squares.king;
if !king_is_on_starting_square || !has_rook_on_starting_square {
flags.clear_player_has_right_to_castle_flag(color, castle);
}
}
}
Position::new(
self.player_to_move,
self.flags,
flags,
pieces,
None,
self.ply_counter,

View file

@ -107,7 +107,17 @@ impl Position {
return false;
}
// TODO: Perform a real check that the player can castle.
let castling_parameters = castle.parameters();
let all_pieces = self.occupied_squares();
if !(all_pieces & castling_parameters.clear_squares()).is_empty() {
return false;
}
let danger_squares = self.king_danger(player);
if !(danger_squares & castling_parameters.check_squares()).is_empty() {
return false;
}
true
}
@ -159,7 +169,7 @@ impl Position {
pub fn piece_on_square(&self, sq: Square) -> Option<PlacedPiece> {
for color in Color::iter() {
for shape in Shape::iter() {
let piece = Piece::new(color, *shape);
let piece = Piece::new(*color, *shape);
if self.pieces.bitboard_for_piece(&piece).is_set(sq) {
return Some(PlacedPiece::new(piece, sq));
}
@ -212,19 +222,16 @@ impl Position {
/// A bitboard representing the squares where a king of the given color will
/// be in danger. The king cannot move to these squares.
pub(crate) fn king_danger(&self) -> BitBoard {
pub(crate) fn king_danger(&self, color: Color) -> BitBoard {
let pieces_without_king = {
let mut cloned_pieces = self.pieces.clone();
let placed_king = PlacedPiece::new(
Piece::king(self.color_to_move),
self.king_square(self.color_to_move),
);
let placed_king = PlacedPiece::new(Piece::king(color), self.king_square(color));
cloned_pieces.remove_piece(&placed_king);
cloned_pieces
};
self._sight_of_player(self.color_to_move.other(), &pieces_without_king)
self._sight_of_player(color.other(), &pieces_without_king)
}
pub(crate) fn is_king_in_check(&self) -> bool {
@ -363,6 +370,25 @@ mod tests {
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
);
assert!(pos.player_can_castle(Color::White, Castle::KingSide));
assert!(pos.player_can_castle(Color::White, Castle::QueenSide));
}
#[test]
fn rook_for_castle() {
let pos = position![
@ -390,7 +416,7 @@ mod tests {
.to_move(Color::Black)
.build();
let danger_squares = pos.king_danger();
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!(