[board] Implement danger squares for the current player

This concept comes from [1]. Danger squares are the squares a king cannot move to
because it would permit the opposing player to capture the king on their next
turn.

[1]: https://peterellisjones.com/posts/generating-legal-chess-moves-efficiently/
This commit is contained in:
Eryn Wells 2024-01-28 00:25:53 -08:00
parent 654e4094d9
commit 164fe94bc0
6 changed files with 190 additions and 73 deletions

View file

@ -62,7 +62,7 @@ where
let to_square = mv.to_square(); let to_square = mv.to_square();
let sight = piece.sight_in_position(self.position); let sight = self.position.sight_of_piece(&piece);
if !sight.is_set(to_square) { if !sight.is_set(to_square) {
return Err(MakeMoveError::IllegalSquare(to_square)); return Err(MakeMoveError::IllegalSquare(to_square));
} }

View file

@ -1,13 +1,16 @@
// Eryn Wells <eryn@erynwells.me> // Eryn Wells <eryn@erynwells.me>
pub mod piece_sets;
mod builders; mod builders;
mod diagram_formatter; mod diagram_formatter;
mod flags; mod flags;
mod piece_sets;
mod pieces; mod pieces;
mod position; mod position;
pub use builders::{MoveBuilder, PositionBuilder}; pub use {
pub use diagram_formatter::DiagramFormatter; builders::{MoveBuilder, PositionBuilder},
pub use pieces::Pieces; diagram_formatter::DiagramFormatter,
pub use position::Position; pieces::Pieces,
position::Position,
};

View file

@ -1,7 +1,7 @@
// Eryn Wells <eryn@erynwells.me> // Eryn Wells <eryn@erynwells.me>
use chessfriend_bitboard::BitBoard; use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Piece, PlacedPiece, Square}; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum PlacePieceStrategy { pub enum PlacePieceStrategy {
@ -21,7 +21,7 @@ impl Default for PlacePieceStrategy {
} }
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub(super) struct PieceBitBoards { pub(crate) struct PieceBitBoards {
by_color: ByColor, by_color: ByColor,
by_color_and_shape: ByColorAndShape, by_color_and_shape: ByColorAndShape,
} }
@ -51,11 +51,20 @@ impl PieceBitBoards {
} }
} }
pub(super) fn all_pieces(&self) -> &BitBoard { pub(super) fn king(&self, color: Color) -> &BitBoard {
self.by_color_and_shape
.bitboard_for_piece(&Piece::new(color, Shape::King))
}
pub(crate) fn all_pieces(&self) -> &BitBoard {
self.by_color.all() self.by_color.all()
} }
pub(super) fn all_pieces_of_color(&self, color: Color) -> &BitBoard { pub(crate) fn empty_squares(&self) -> BitBoard {
!self.by_color.all()
}
pub(crate) fn all_pieces_of_color(&self, color: Color) -> &BitBoard {
self.by_color.bitboard(color) self.by_color.bitboard(color)
} }

View file

@ -177,12 +177,54 @@ impl Position {
self.en_passant_square self.en_passant_square
} }
/// A bitboard representing the squares the pieces of the given color can
/// see. This is synonymous with the squares attacked by the player's
/// pieces.
pub(crate) fn sight_of_player(&self, color: Color) -> BitBoard { pub(crate) fn sight_of_player(&self, color: Color) -> BitBoard {
*self.sight[color as usize].get_or_init(|| { *self.sight[color as usize].get_or_init(|| self._sight_of_player(color, &self.pieces))
self.pieces(color).fold(BitBoard::empty(), |acc, pp| { }
acc | pp.sight_in_position(&self)
fn _sight_of_player(&self, player: Color, pieces: &PieceBitBoards) -> BitBoard {
let en_passant_square = self.en_passant_square;
Shape::ALL
.iter()
.filter_map(|&shape| {
let piece = Piece::new(player, shape);
let bitboard = pieces.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(pieces, en_passant_square)
})
})
.fold(BitBoard::empty(), |acc, sight| acc | sight)
}
pub(crate) fn sight_of_piece(&self, piece: &PlacedPiece) -> BitBoard {
piece.sight(&self.pieces, self.en_passant_square)
}
/// 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 {
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),
);
cloned_pieces.remove_piece(&placed_king);
cloned_pieces
};
self._sight_of_player(self.color_to_move.other(), &pieces_without_king)
} }
pub(crate) fn is_king_in_check(&self) -> bool { pub(crate) fn is_king_in_check(&self) -> bool {
@ -266,7 +308,8 @@ impl fmt::Display for Position {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{position, test_position, Castle, Position}; use crate::{position, test_position, Castle, Position, PositionBuilder};
use chessfriend_bitboard::bitboard;
use chessfriend_core::{piece, Color, Square}; use chessfriend_core::{piece, Color, Square};
#[test] #[test]
@ -329,4 +372,23 @@ mod tests {
Some(piece!(White Rook on A1)) Some(piece!(White Rook on A1))
); );
} }
#[test]
fn danger_squares() {
let pos = PositionBuilder::new()
.place_piece(piece!(White King on E1))
.place_piece(piece!(Black King on E7))
.place_piece(piece!(White Rook on E4))
.to_move(Color::Black)
.build();
let danger_squares = pos.king_danger();
let expected =
bitboard![D1, F1, D2, E2, F2, E3, A4, B4, C4, D4, F4, G4, H4, E5, E6, E7, E8];
assert_eq!(
danger_squares, expected,
"Actual:\n{}\n\nExpected:\n{}",
danger_squares, expected
);
}
} }

View file

@ -1,42 +1,56 @@
// Eryn Wells <eryn@erynwells.me> // Eryn Wells <eryn@erynwells.me>
use crate::position::piece_sets::PieceBitBoards;
use crate::Position; use crate::Position;
use chessfriend_bitboard::BitBoard; use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Direction, PlacedPiece, Shape}; use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square};
pub(crate) trait SightExt { pub(crate) trait SightExt {
fn sight_in_position(&self, position: &Position) -> BitBoard; fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option<Square>) -> BitBoard;
fn white_pawn_sight_in_position(&self, position: &Position) -> BitBoard; fn white_pawn_sight(
fn black_pawn_sight_in_position(&self, position: &Position) -> BitBoard; &self,
fn knight_sight_in_position(&self, position: &Position) -> BitBoard; pieces: &PieceBitBoards,
fn bishop_sight_in_position(&self, position: &Position) -> BitBoard; en_passant_square: Option<Square>,
fn rook_sight_in_position(&self, position: &Position) -> BitBoard; ) -> BitBoard;
fn queen_sight_in_position(&self, position: &Position) -> BitBoard; fn black_pawn_sight(
fn king_sight_in_position(&self, position: &Position) -> BitBoard; &self,
pieces: &PieceBitBoards,
en_passant_square: Option<Square>,
) -> BitBoard;
fn knight_sight(&self, pieces: &PieceBitBoards) -> BitBoard;
fn bishop_sight(&self, pieces: &PieceBitBoards) -> BitBoard;
fn rook_sight(&self, pieces: &PieceBitBoards) -> BitBoard;
fn queen_sight(&self, pieces: &PieceBitBoards) -> BitBoard;
fn king_sight(&self, pieces: &PieceBitBoards) -> BitBoard;
} }
impl SightExt for PlacedPiece { impl SightExt for PlacedPiece {
fn sight_in_position(&self, position: &Position) -> BitBoard { fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option<Square>) -> BitBoard {
match self.shape() { match self.shape() {
Shape::Pawn => match self.color() { Shape::Pawn => match self.color() {
Color::White => self.white_pawn_sight_in_position(position), Color::White => self.white_pawn_sight(pieces, en_passant_square),
Color::Black => self.black_pawn_sight_in_position(position), Color::Black => self.black_pawn_sight(pieces, en_passant_square),
}, },
Shape::Knight => self.knight_sight_in_position(position), Shape::Knight => self.knight_sight(pieces),
Shape::Bishop => self.bishop_sight_in_position(position), Shape::Bishop => self.bishop_sight(pieces),
Shape::Rook => self.rook_sight_in_position(position), Shape::Rook => self.rook_sight(pieces),
Shape::Queen => self.queen_sight_in_position(position), Shape::Queen => self.queen_sight(pieces),
Shape::King => self.king_sight_in_position(position), Shape::King => self.king_sight(pieces),
} }
} }
fn white_pawn_sight_in_position(&self, position: &Position) -> BitBoard { fn white_pawn_sight(
&self,
pieces: &PieceBitBoards,
en_passant_square: Option<Square>,
) -> BitBoard {
let opponent = self.color().other();
let pawn: BitBoard = self.square().into(); let pawn: BitBoard = self.square().into();
let pawn = pawn.shift_north_west_one() | pawn.shift_north_east_one(); let pawn = pawn.shift_north_west_one() | pawn.shift_north_east_one();
let mut possible_squares = position.empty_squares() | position.opposing_pieces(); let mut possible_squares = pieces.empty_squares() | pieces.all_pieces_of_color(opponent);
if let Some(en_passant) = position.en_passant_square() { if let Some(en_passant) = en_passant_square {
let en_passant_bitboard: BitBoard = en_passant.into(); let en_passant_bitboard: BitBoard = en_passant.into();
possible_squares |= en_passant_bitboard; possible_squares |= en_passant_bitboard;
} }
@ -44,28 +58,34 @@ impl SightExt for PlacedPiece {
pawn & possible_squares pawn & possible_squares
} }
fn black_pawn_sight_in_position(&self, position: &Position) -> BitBoard { fn black_pawn_sight(
&self,
pieces: &PieceBitBoards,
en_passant_square: Option<Square>,
) -> BitBoard {
let opponent = self.color().other();
let pawn: BitBoard = self.square().into(); let pawn: BitBoard = self.square().into();
let pawn = pawn.shift_south_west_one() | pawn.shift_south_east_one(); let pawn = pawn.shift_south_west_one() | pawn.shift_south_east_one();
let mut possible_squares = position.empty_squares() | position.opposing_pieces(); let mut possible_squares = pieces.empty_squares() | pieces.all_pieces_of_color(opponent);
if let Some(en_passant) = position.en_passant_square() { if let Some(en_passant) = en_passant_square {
possible_squares |= &en_passant.into(); possible_squares |= &en_passant.into();
} }
pawn & possible_squares pawn & possible_squares
} }
fn knight_sight_in_position(&self, position: &Position) -> BitBoard { fn knight_sight(&self, pieces: &PieceBitBoards) -> BitBoard {
BitBoard::knight_moves(self.square()) & !position.friendly_pieces() BitBoard::knight_moves(self.square()) & !pieces.all_pieces_of_color(self.color())
} }
fn bishop_sight_in_position(&self, position: &Position) -> BitBoard { fn bishop_sight(&self, pieces: &PieceBitBoards) -> BitBoard {
let square = self.square(); let square = self.square();
let mut sight = BitBoard::empty(); let mut sight = BitBoard::empty();
let blockers = position.occupied_squares(); let blockers = pieces.all_pieces();
macro_rules! update_moves_with_ray { macro_rules! update_moves_with_ray {
($direction:ident, $occupied_squares:tt) => { ($direction:ident, $occupied_squares:tt) => {
@ -87,15 +107,16 @@ impl SightExt for PlacedPiece {
update_moves_with_ray!(SouthEast, occupied_squares); update_moves_with_ray!(SouthEast, occupied_squares);
update_moves_with_ray!(SouthWest, occupied_squares); update_moves_with_ray!(SouthWest, occupied_squares);
sight let friendly_pieces = pieces.all_pieces_of_color(self.color());
sight & !friendly_pieces
} }
fn rook_sight_in_position(&self, position: &Position) -> BitBoard { fn rook_sight(&self, pieces: &PieceBitBoards) -> BitBoard {
let square = self.square(); let square = self.square();
let mut sight = BitBoard::empty(); let mut sight = BitBoard::empty();
let blockers = position.occupied_squares(); let blockers = pieces.all_pieces();
macro_rules! update_moves_with_ray { macro_rules! update_moves_with_ray {
($direction:ident, $occupied_squares:tt) => { ($direction:ident, $occupied_squares:tt) => {
@ -117,15 +138,16 @@ impl SightExt for PlacedPiece {
update_moves_with_ray!(South, occupied_squares); update_moves_with_ray!(South, occupied_squares);
update_moves_with_ray!(West, occupied_squares); update_moves_with_ray!(West, occupied_squares);
sight let friendly_pieces = pieces.all_pieces_of_color(self.color());
sight & !friendly_pieces
} }
fn queen_sight_in_position(&self, position: &Position) -> BitBoard { fn queen_sight(&self, pieces: &PieceBitBoards) -> BitBoard {
let square = self.square(); let square = self.square();
let mut sight = BitBoard::empty(); let mut sight = BitBoard::empty();
let blockers = position.occupied_squares(); let blockers = pieces.all_pieces();
macro_rules! update_moves_with_ray { macro_rules! update_moves_with_ray {
($direction:ident, $occupied_squares:tt) => { ($direction:ident, $occupied_squares:tt) => {
@ -151,11 +173,12 @@ impl SightExt for PlacedPiece {
update_moves_with_ray!(SouthWest, occupied_squares); update_moves_with_ray!(SouthWest, occupied_squares);
update_moves_with_ray!(West, occupied_squares); update_moves_with_ray!(West, occupied_squares);
sight let friendly_pieces = pieces.all_pieces_of_color(self.color());
sight & !friendly_pieces
} }
fn king_sight_in_position(&self, position: &Position) -> BitBoard { fn king_sight(&self, pieces: &PieceBitBoards) -> BitBoard {
BitBoard::king_moves(self.square()) & !position.friendly_pieces() BitBoard::king_moves(self.square()) & !pieces.all_pieces_of_color(self.color())
} }
} }
@ -166,8 +189,8 @@ mod tests {
#[test] #[test]
fn $test_name() { fn $test_name() {
let pos = $position; let pos = $position;
let pp = $piece; let piece = $piece;
let sight = pp.sight_in_position(&pos); let sight = pos.sight_of_piece(&piece);
assert_eq!(sight, $bitboard); assert_eq!(sight, $bitboard);
} }
@ -178,7 +201,7 @@ mod tests {
} }
mod pawn { mod pawn {
use crate::{sight::SightExt, test_position}; use crate::test_position;
use chessfriend_bitboard::{bitboard, BitBoard}; use chessfriend_bitboard::{bitboard, BitBoard};
use chessfriend_core::{piece, Square}; use chessfriend_core::{piece, Square};
@ -202,8 +225,8 @@ mod tests {
White Pawn on E4, White Pawn on E4,
); );
let pp = piece!(White Pawn on E4); let piece = piece!(White Pawn on E4);
let sight = pp.sight_in_position(&pos); let sight = pos.sight_of_piece(&piece);
assert_eq!(sight, BitBoard::empty()); assert_eq!(sight, BitBoard::empty());
} }
@ -216,8 +239,8 @@ mod tests {
White Pawn on E4, White Pawn on E4,
); );
let pp = piece!(White Pawn on E4); let piece = piece!(White Pawn on E4);
let sight = pp.sight_in_position(&pos); let sight = pos.sight_of_piece(&piece);
assert_eq!(sight, bitboard!(D5)); assert_eq!(sight, bitboard!(D5));
} }
@ -229,8 +252,8 @@ mod tests {
Black Pawn on D5, Black Pawn on D5,
); );
pos.test_set_en_passant_square(Square::D6); pos.test_set_en_passant_square(Square::D6);
let pp = piece!(White Pawn on E5); let piece = piece!(White Pawn on E5);
let sight = pp.sight_in_position(&pos); let sight = pos.sight_of_piece(&piece);
assert_eq!(sight, bitboard!(D6, F6)); assert_eq!(sight, bitboard!(D6, F6));
} }
@ -238,7 +261,6 @@ mod tests {
#[macro_use] #[macro_use]
mod knight { mod knight {
use crate::sight::SightExt;
use chessfriend_bitboard::bitboard; use chessfriend_bitboard::bitboard;
use chessfriend_core::piece; use chessfriend_core::piece;
@ -250,7 +272,6 @@ mod tests {
} }
mod bishop { mod bishop {
use crate::sight::SightExt;
use chessfriend_bitboard::bitboard; use chessfriend_bitboard::bitboard;
use chessfriend_core::piece; use chessfriend_core::piece;
@ -262,7 +283,7 @@ mod tests {
} }
mod rook { mod rook {
use crate::sight::SightExt; use crate::test_position;
use chessfriend_bitboard::bitboard; use chessfriend_bitboard::bitboard;
use chessfriend_core::piece; use chessfriend_core::piece;
@ -271,5 +292,27 @@ mod tests {
piece!(White Rook on G3), piece!(White Rook on G3),
bitboard!(G1, G2, G4, G5, G6, G7, G8, A3, B3, C3, D3, E3, F3, H3) bitboard!(G1, G2, G4, G5, G6, G7, G8, A3, B3, C3, D3, E3, F3, H3)
); );
sight_test!(
e4_rook_with_e1_white_king_e7_black_king,
test_position![
White Rook on E4,
White King on E1,
Black King on E7,
],
piece!(White Rook on E4),
bitboard!(A4, B4, C4, D4, F4, G4, H4, E2, E3, E5, E6, E7)
);
}
mod king {
use chessfriend_bitboard::bitboard;
use chessfriend_core::piece;
sight_test!(
e1_king,
piece!(White King on E1),
bitboard![D1, D2, E2, F2, F1]
);
} }
} }

View file

@ -14,17 +14,17 @@ pub enum Shape {
} }
impl Shape { impl Shape {
pub fn iter() -> Iter<'static, Shape> { pub const ALL: [Shape; 6] = [
const ALL_SHAPES: [Shape; 6] = [ Shape::Pawn,
Shape::Pawn, Shape::Knight,
Shape::Knight, Shape::Bishop,
Shape::Bishop, Shape::Rook,
Shape::Rook, Shape::Queen,
Shape::Queen, Shape::King,
Shape::King, ];
];
ALL_SHAPES.iter() pub fn iter() -> Iter<'static, Shape> {
Shape::ALL.iter()
} }
pub fn promotable() -> Iter<'static, Shape> { pub fn promotable() -> Iter<'static, Shape> {