diff --git a/board/src/position/builders/move_builder.rs b/board/src/position/builders/move_builder.rs index ff1ce5b..23229e7 100644 --- a/board/src/position/builders/move_builder.rs +++ b/board/src/position/builders/move_builder.rs @@ -62,7 +62,7 @@ where 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) { return Err(MakeMoveError::IllegalSquare(to_square)); } diff --git a/board/src/position/mod.rs b/board/src/position/mod.rs index a03e392..8ece091 100644 --- a/board/src/position/mod.rs +++ b/board/src/position/mod.rs @@ -1,13 +1,16 @@ // Eryn Wells +pub mod piece_sets; + mod builders; mod diagram_formatter; mod flags; -mod piece_sets; mod pieces; mod position; -pub use builders::{MoveBuilder, PositionBuilder}; -pub use diagram_formatter::DiagramFormatter; -pub use pieces::Pieces; -pub use position::Position; +pub use { + builders::{MoveBuilder, PositionBuilder}, + diagram_formatter::DiagramFormatter, + pieces::Pieces, + position::Position, +}; diff --git a/board/src/position/piece_sets.rs b/board/src/position/piece_sets.rs index 0ebbd90..095d184 100644 --- a/board/src/position/piece_sets.rs +++ b/board/src/position/piece_sets.rs @@ -1,7 +1,7 @@ // Eryn Wells use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece, Square}; +use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; #[derive(Debug, Eq, PartialEq)] pub enum PlacePieceStrategy { @@ -21,7 +21,7 @@ impl Default for PlacePieceStrategy { } #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] -pub(super) struct PieceBitBoards { +pub(crate) struct PieceBitBoards { by_color: ByColor, 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() } - 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) } diff --git a/board/src/position/position.rs b/board/src/position/position.rs index 326371f..6f3af39 100644 --- a/board/src/position/position.rs +++ b/board/src/position/position.rs @@ -177,12 +177,54 @@ impl Position { 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 { - *self.sight[color as usize].get_or_init(|| { - self.pieces(color).fold(BitBoard::empty(), |acc, pp| { - acc | pp.sight_in_position(&self) + *self.sight[color as usize].get_or_init(|| self._sight_of_player(color, &self.pieces)) + } + + 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 { @@ -266,7 +308,8 @@ impl fmt::Display for Position { #[cfg(test)] 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}; #[test] @@ -329,4 +372,23 @@ mod tests { 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 + ); + } } diff --git a/board/src/sight.rs b/board/src/sight.rs index 0e1dc8b..eead56e 100644 --- a/board/src/sight.rs +++ b/board/src/sight.rs @@ -1,42 +1,56 @@ // Eryn Wells +use crate::position::piece_sets::PieceBitBoards; use crate::Position; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Direction, PlacedPiece, Shape}; +use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square}; pub(crate) trait SightExt { - fn sight_in_position(&self, position: &Position) -> BitBoard; + fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option) -> BitBoard; - fn white_pawn_sight_in_position(&self, position: &Position) -> BitBoard; - fn black_pawn_sight_in_position(&self, position: &Position) -> BitBoard; - fn knight_sight_in_position(&self, position: &Position) -> BitBoard; - fn bishop_sight_in_position(&self, position: &Position) -> BitBoard; - fn rook_sight_in_position(&self, position: &Position) -> BitBoard; - fn queen_sight_in_position(&self, position: &Position) -> BitBoard; - fn king_sight_in_position(&self, position: &Position) -> BitBoard; + fn white_pawn_sight( + &self, + pieces: &PieceBitBoards, + en_passant_square: Option, + ) -> BitBoard; + fn black_pawn_sight( + &self, + pieces: &PieceBitBoards, + en_passant_square: Option, + ) -> 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 { - fn sight_in_position(&self, position: &Position) -> BitBoard { + fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option) -> BitBoard { match self.shape() { Shape::Pawn => match self.color() { - Color::White => self.white_pawn_sight_in_position(position), - Color::Black => self.black_pawn_sight_in_position(position), + Color::White => self.white_pawn_sight(pieces, en_passant_square), + Color::Black => self.black_pawn_sight(pieces, en_passant_square), }, - Shape::Knight => self.knight_sight_in_position(position), - Shape::Bishop => self.bishop_sight_in_position(position), - Shape::Rook => self.rook_sight_in_position(position), - Shape::Queen => self.queen_sight_in_position(position), - Shape::King => self.king_sight_in_position(position), + Shape::Knight => self.knight_sight(pieces), + Shape::Bishop => self.bishop_sight(pieces), + Shape::Rook => self.rook_sight(pieces), + Shape::Queen => self.queen_sight(pieces), + 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, + ) -> BitBoard { + let opponent = self.color().other(); let pawn: BitBoard = self.square().into(); let pawn = pawn.shift_north_west_one() | pawn.shift_north_east_one(); - let mut possible_squares = position.empty_squares() | position.opposing_pieces(); - if let Some(en_passant) = position.en_passant_square() { + let mut possible_squares = pieces.empty_squares() | pieces.all_pieces_of_color(opponent); + if let Some(en_passant) = en_passant_square { let en_passant_bitboard: BitBoard = en_passant.into(); possible_squares |= en_passant_bitboard; } @@ -44,28 +58,34 @@ impl SightExt for PlacedPiece { pawn & possible_squares } - fn black_pawn_sight_in_position(&self, position: &Position) -> BitBoard { + fn black_pawn_sight( + &self, + pieces: &PieceBitBoards, + en_passant_square: Option, + ) -> BitBoard { + let opponent = self.color().other(); + let pawn: BitBoard = self.square().into(); let pawn = pawn.shift_south_west_one() | pawn.shift_south_east_one(); - let mut possible_squares = position.empty_squares() | position.opposing_pieces(); - if let Some(en_passant) = position.en_passant_square() { + let mut possible_squares = pieces.empty_squares() | pieces.all_pieces_of_color(opponent); + if let Some(en_passant) = en_passant_square { possible_squares |= &en_passant.into(); } pawn & possible_squares } - fn knight_sight_in_position(&self, position: &Position) -> BitBoard { - BitBoard::knight_moves(self.square()) & !position.friendly_pieces() + fn knight_sight(&self, pieces: &PieceBitBoards) -> BitBoard { + 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 mut sight = BitBoard::empty(); - let blockers = position.occupied_squares(); + let blockers = pieces.all_pieces(); macro_rules! update_moves_with_ray { ($direction:ident, $occupied_squares:tt) => { @@ -87,15 +107,16 @@ impl SightExt for PlacedPiece { update_moves_with_ray!(SouthEast, 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 mut sight = BitBoard::empty(); - let blockers = position.occupied_squares(); + let blockers = pieces.all_pieces(); macro_rules! update_moves_with_ray { ($direction:ident, $occupied_squares:tt) => { @@ -117,15 +138,16 @@ impl SightExt for PlacedPiece { update_moves_with_ray!(South, 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 mut sight = BitBoard::empty(); - let blockers = position.occupied_squares(); + let blockers = pieces.all_pieces(); macro_rules! update_moves_with_ray { ($direction:ident, $occupied_squares:tt) => { @@ -151,11 +173,12 @@ impl SightExt for PlacedPiece { update_moves_with_ray!(SouthWest, 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 { - BitBoard::king_moves(self.square()) & !position.friendly_pieces() + fn king_sight(&self, pieces: &PieceBitBoards) -> BitBoard { + BitBoard::king_moves(self.square()) & !pieces.all_pieces_of_color(self.color()) } } @@ -166,8 +189,8 @@ mod tests { #[test] fn $test_name() { let pos = $position; - let pp = $piece; - let sight = pp.sight_in_position(&pos); + let piece = $piece; + let sight = pos.sight_of_piece(&piece); assert_eq!(sight, $bitboard); } @@ -178,7 +201,7 @@ mod tests { } mod pawn { - use crate::{sight::SightExt, test_position}; + use crate::test_position; use chessfriend_bitboard::{bitboard, BitBoard}; use chessfriend_core::{piece, Square}; @@ -202,8 +225,8 @@ mod tests { White Pawn on E4, ); - let pp = piece!(White Pawn on E4); - let sight = pp.sight_in_position(&pos); + let piece = piece!(White Pawn on E4); + let sight = pos.sight_of_piece(&piece); assert_eq!(sight, BitBoard::empty()); } @@ -216,8 +239,8 @@ mod tests { White Pawn on E4, ); - let pp = piece!(White Pawn on E4); - let sight = pp.sight_in_position(&pos); + let piece = piece!(White Pawn on E4); + let sight = pos.sight_of_piece(&piece); assert_eq!(sight, bitboard!(D5)); } @@ -229,8 +252,8 @@ mod tests { Black Pawn on D5, ); pos.test_set_en_passant_square(Square::D6); - let pp = piece!(White Pawn on E5); - let sight = pp.sight_in_position(&pos); + let piece = piece!(White Pawn on E5); + let sight = pos.sight_of_piece(&piece); assert_eq!(sight, bitboard!(D6, F6)); } @@ -238,7 +261,6 @@ mod tests { #[macro_use] mod knight { - use crate::sight::SightExt; use chessfriend_bitboard::bitboard; use chessfriend_core::piece; @@ -250,7 +272,6 @@ mod tests { } mod bishop { - use crate::sight::SightExt; use chessfriend_bitboard::bitboard; use chessfriend_core::piece; @@ -262,7 +283,7 @@ mod tests { } mod rook { - use crate::sight::SightExt; + use crate::test_position; use chessfriend_bitboard::bitboard; use chessfriend_core::piece; @@ -271,5 +292,27 @@ mod tests { piece!(White Rook on G3), 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] + ); } } diff --git a/core/src/pieces.rs b/core/src/pieces.rs index 782fc47..b49db6d 100644 --- a/core/src/pieces.rs +++ b/core/src/pieces.rs @@ -14,17 +14,17 @@ pub enum Shape { } impl Shape { - pub fn iter() -> Iter<'static, Shape> { - const ALL_SHAPES: [Shape; 6] = [ - Shape::Pawn, - Shape::Knight, - Shape::Bishop, - Shape::Rook, - Shape::Queen, - Shape::King, - ]; + pub const ALL: [Shape; 6] = [ + Shape::Pawn, + Shape::Knight, + Shape::Bishop, + Shape::Rook, + Shape::Queen, + Shape::King, + ]; - ALL_SHAPES.iter() + pub fn iter() -> Iter<'static, Shape> { + Shape::ALL.iter() } pub fn promotable() -> Iter<'static, Shape> {