diff --git a/Cargo.lock b/Cargo.lock index 3ebe93a..6e580bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,16 +56,40 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" -[[package]] -name = "board" -version = "0.1.0" - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chessfriend_bitboard" +version = "0.1.0" +dependencies = [ + "chessfriend_core", +] + +[[package]] +name = "chessfriend_core" +version = "0.1.0" + +[[package]] +name = "chessfriend_move_generator" +version = "0.1.0" +dependencies = [ + "chessfriend_bitboard", + "chessfriend_core", + "chessfriend_position", +] + +[[package]] +name = "chessfriend_position" +version = "0.1.0" +dependencies = [ + "chessfriend_bitboard", + "chessfriend_core", +] + [[package]] name = "clap" version = "4.4.18" @@ -121,10 +145,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "core" -version = "0.1.0" - [[package]] name = "endian-type" version = "0.1.2" @@ -151,7 +171,8 @@ checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" name = "explorer" version = "0.1.0" dependencies = [ - "board", + "chessfriend_core", + "chessfriend_position", "clap", "rustyline", "shlex", diff --git a/bitboard/src/bitboard.rs b/bitboard/src/bitboard.rs index 2f9740d..2453cce 100644 --- a/bitboard/src/bitboard.rs +++ b/bitboard/src/bitboard.rs @@ -1,7 +1,7 @@ // Eryn Wells use crate::library; -use crate::LeadingBitScanner; +use crate::{LeadingBitScanner, TrailingBitScanner}; use chessfriend_core::{Color, Direction, Square}; use std::fmt; use std::ops::Not; @@ -39,7 +39,7 @@ impl BitBoard { library::FILES[*file as usize] } - pub fn ray(sq: Square, dir: Direction) -> BitBoard { + pub fn ray(sq: Square, dir: Direction) -> &'static BitBoard { library::library().ray(sq, dir) } @@ -80,6 +80,20 @@ impl BitBoard { !(self & square_bitboard).is_empty() } + /// The number of 1 bits in the BitBoard. + /// + /// ## Examples + /// + /// ``` + /// use chessfriend_bitboard::BitBoard; + /// assert_eq!(BitBoard::EMPTY.population_count(), 0); + /// assert_eq!(BitBoard::new(0b01011110010).population_count(), 6); + /// assert_eq!(BitBoard::FULL.population_count(), 64); + /// ``` + pub fn population_count(&self) -> u32 { + self.0.count_ones() + } + pub fn set_square(&mut self, sq: Square) { let sq_bb: BitBoard = sq.into(); *self |= sq_bb @@ -105,13 +119,31 @@ impl BitBoard { /// Return an Iterator over the occupied squares, starting from the trailing /// (least-significant bit) end of the field. pub fn occupied_squares_trailing(&self) -> impl Iterator { - LeadingBitScanner::new(self.0).map(|idx| unsafe { Square::from_index(idx as u8) }) + TrailingBitScanner::new(self.0).map(|idx| unsafe { Square::from_index(idx as u8) }) + } + + pub fn first_occupied_square(&self) -> Option { + let leading_zeros = self.0.leading_zeros() as u8; + if leading_zeros < Square::NUM as u8 { + unsafe { Some(Square::from_index(Square::NUM as u8 - leading_zeros - 1)) } + } else { + None + } + } + + pub fn first_occupied_square_trailing(&self) -> Option { + let trailing_zeros = self.0.trailing_zeros() as u8; + if trailing_zeros < Square::NUM as u8 { + unsafe { Some(Square::from_index(trailing_zeros)) } + } else { + None + } } } impl Default for BitBoard { fn default() -> Self { - BitBoard::empty() + BitBoard::EMPTY } } @@ -377,4 +409,14 @@ mod tests { assert_eq!(BitBoard::from(Square::A1), BitBoard(0b1)); assert_eq!(BitBoard::from(Square::H8), BitBoard(1 << 63)); } + + #[test] + fn first_occupied_squares() { + let bb = bitboard![A8, E1]; + assert_eq!(bb.first_occupied_square(), Some(Square::A8)); + assert_eq!(bb.first_occupied_square_trailing(), Some(Square::E1)); + + let bb = bitboard![D6, E7, F8]; + assert_eq!(bb.first_occupied_square_trailing(), Some(Square::D6)); + } } diff --git a/bitboard/src/library.rs b/bitboard/src/library.rs index 41951c7..4615caa 100644 --- a/bitboard/src/library.rs +++ b/bitboard/src/library.rs @@ -220,8 +220,8 @@ impl MoveLibrary { ray } - pub(super) fn ray(&self, sq: Square, dir: Direction) -> BitBoard { - self.rays[sq as usize][dir as usize] + pub(super) fn ray(&self, sq: Square, dir: Direction) -> &BitBoard { + &self.rays[sq as usize][dir as usize] } pub(super) fn pawn_pushes(&self, sq: Square, color: Color) -> BitBoard { diff --git a/position/src/check.rs b/position/src/check.rs new file mode 100644 index 0000000..df30749 --- /dev/null +++ b/position/src/check.rs @@ -0,0 +1,97 @@ +// Eryn Wells + +use chessfriend_bitboard::BitBoard; +use chessfriend_core::{Color, Direction, Shape, Square}; + +use crate::sight::SliderRayToSquareExt; + +pub struct CheckingPieces { + bitboards: [BitBoard; 5], +} + +impl CheckingPieces { + pub(crate) fn new( + pawn: BitBoard, + knight: BitBoard, + bishop: BitBoard, + rook: BitBoard, + queen: BitBoard, + ) -> CheckingPieces { + CheckingPieces { + bitboards: [pawn, knight, bishop, rook, queen], + } + } + + pub fn count(&self) -> u32 { + self.bitboards.iter().map(|b| b.population_count()).sum() + } + + /// A BitBoard representing the set of pieces that must be captured to + /// resolve check. + pub fn capture_mask(&self) -> BitBoard { + if self.count() == 0 { + BitBoard::FULL + } else { + self.bitboards + .iter() + .fold(BitBoard::EMPTY, std::ops::BitOr::bitor) + } + } + + /// A BitBoard representing the set of squares to which a player can move a + /// piece to block a checking piece. + pub fn push_mask(&self, king: &BitBoard) -> BitBoard { + let target = king.first_occupied_square().unwrap(); + + macro_rules! push_mask_for_shape { + ($push_mask:expr, $shape:ident, $king:expr) => {{ + let checking_pieces = self.bitboard_for_shape(Shape::$shape); + if !checking_pieces.is_empty() { + if let Some(checking_ray) = checking_pieces + .occupied_squares() + .flat_map(|sq| Shape::$shape.ray_to_square(sq, target).into_iter()) + .find(|bb| !(bb & $king).is_empty()) + { + $push_mask |= checking_ray & !$king + } + } + }}; + } + + let mut push_mask = BitBoard::EMPTY; + + push_mask_for_shape!(push_mask, Bishop, king); + push_mask_for_shape!(push_mask, Rook, king); + push_mask_for_shape!(push_mask, Queen, king); + + push_mask + } + + fn bitboard_for_shape(&self, shape: Shape) -> &BitBoard { + &self.bitboards[shape as usize] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use chessfriend_bitboard::{bitboard, BitBoard}; + + /// This is a test position from [this execellent blog post][1] about how to + /// efficiently generate legal chess moves. + /// + /// [1]: https://peterellisjones.com/posts/generating-legal-chess-moves-efficiently/ + #[test] + fn rook_push_mask() { + let checks = CheckingPieces::new( + BitBoard::EMPTY, + BitBoard::EMPTY, + BitBoard::EMPTY, + bitboard![E5], + BitBoard::EMPTY, + ); + + let push_mask = checks.push_mask(&bitboard![E8]); + assert_eq!(push_mask, bitboard![E6, E7]); + } +} diff --git a/position/src/lib.rs b/position/src/lib.rs index 9c58540..dde3e5d 100644 --- a/position/src/lib.rs +++ b/position/src/lib.rs @@ -2,6 +2,7 @@ pub mod fen; +mod check; mod display; mod r#move; mod move_generator; diff --git a/position/src/move_generator.rs b/position/src/move_generator.rs index 7e193c1..7f7d481 100644 --- a/position/src/move_generator.rs +++ b/position/src/move_generator.rs @@ -51,10 +51,15 @@ macro_rules! move_generator_declaration { capture_mask: chessfriend_bitboard::BitBoard, push_mask: chessfriend_bitboard::BitBoard, ) -> $name { - $name { - color, - move_sets: Self::move_sets(position, color, capture_mask, push_mask), - } + let move_sets = if Self::shape() == chessfriend_core::Shape::King + || (!capture_mask.is_empty() && !push_mask.is_empty()) + { + Self::move_sets(position, color, capture_mask, push_mask) + } else { + std::collections::BTreeMap::new() + }; + + $name { color, move_sets } } } }; @@ -84,7 +89,11 @@ macro_rules! move_generator_declaration { pub(self) use move_generator_declaration; trait MoveGeneratorInternal { - fn piece(color: Color) -> Piece; + fn shape() -> Shape; + + fn piece(color: Color) -> Piece { + Piece::new(color, Self::shape()) + } fn move_sets( position: &Position, diff --git a/position/src/move_generator/bishop.rs b/position/src/move_generator/bishop.rs index 5b72f78..2fae36f 100644 --- a/position/src/move_generator/bishop.rs +++ b/position/src/move_generator/bishop.rs @@ -3,13 +3,13 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use crate::Position; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Direction, Piece, PlacedPiece}; +use chessfriend_core::{Direction, PlacedPiece, Shape}; move_generator_declaration!(ClassicalMoveGenerator); impl MoveGeneratorInternal for ClassicalMoveGenerator { - fn piece(color: Color) -> Piece { - Piece::bishop(color) + fn shape() -> Shape { + Shape::Bishop } fn move_set_for_piece( diff --git a/position/src/move_generator/king.rs b/position/src/move_generator/king.rs index 7941bf3..c18692a 100644 --- a/position/src/move_generator/king.rs +++ b/position/src/move_generator/king.rs @@ -6,22 +6,22 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use crate::{r#move::Castle, Position}; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece}; +use chessfriend_core::{PlacedPiece, Shape}; move_generator_declaration!(KingMoveGenerator, struct); move_generator_declaration!(KingMoveGenerator, new); move_generator_declaration!(KingMoveGenerator, getters); impl MoveGeneratorInternal for KingMoveGenerator { - fn piece(color: Color) -> Piece { - Piece::king(color) + fn shape() -> Shape { + Shape::King } fn move_set_for_piece( position: &Position, placed_piece: PlacedPiece, - capture_mask: BitBoard, - push_mask: BitBoard, + _capture_mask: BitBoard, + _push_mask: BitBoard, ) -> MoveSet { let piece = placed_piece.piece(); let color = piece.color(); @@ -59,7 +59,7 @@ mod tests { use super::*; use crate::{assert_move_list, position, test_position, Move, MoveBuilder, PositionBuilder}; use chessfriend_bitboard::bitboard; - use chessfriend_core::{piece, Square}; + use chessfriend_core::{piece, Color, Square}; use std::collections::HashSet; #[test] diff --git a/position/src/move_generator/knight.rs b/position/src/move_generator/knight.rs index 66ee8ba..b4dc1bc 100644 --- a/position/src/move_generator/knight.rs +++ b/position/src/move_generator/knight.rs @@ -3,13 +3,13 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use crate::Position; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece}; +use chessfriend_core::{PlacedPiece, Shape}; move_generator_declaration!(KnightMoveGenerator); impl MoveGeneratorInternal for KnightMoveGenerator { - fn piece(color: Color) -> Piece { - Piece::knight(color) + fn shape() -> Shape { + Shape::Knight } fn move_set_for_piece( @@ -35,7 +35,7 @@ impl MoveGeneratorInternal for KnightMoveGenerator { mod tests { use super::*; use crate::{position, Move, MoveBuilder}; - use chessfriend_core::{piece, Square}; + use chessfriend_core::{piece, Color, Square}; use std::collections::HashSet; #[test] diff --git a/position/src/move_generator/pawn.rs b/position/src/move_generator/pawn.rs index f68baf4..c8b2e6c 100644 --- a/position/src/move_generator/pawn.rs +++ b/position/src/move_generator/pawn.rs @@ -3,7 +3,7 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use crate::Position; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece, Rank, Square}; +use chessfriend_core::{Piece, PlacedPiece, Rank, Shape, Square}; #[derive(Debug)] struct MoveIterator(usize, usize); @@ -11,8 +11,8 @@ struct MoveIterator(usize, usize); move_generator_declaration!(PawnMoveGenerator); impl MoveGeneratorInternal for PawnMoveGenerator { - fn piece(color: Color) -> Piece { - Piece::pawn(color) + fn shape() -> Shape { + Shape::Pawn } fn move_set_for_piece( @@ -63,7 +63,7 @@ impl PawnMoveGenerator { mod tests { use super::*; use crate::{assert_move_list, position::DiagramFormatter, test_position, Move, MoveBuilder}; - use chessfriend_core::{piece, Square}; + use chessfriend_core::{piece, Color, Square}; use std::collections::HashSet; #[test] diff --git a/position/src/move_generator/queen.rs b/position/src/move_generator/queen.rs index ad913fe..dc41769 100644 --- a/position/src/move_generator/queen.rs +++ b/position/src/move_generator/queen.rs @@ -3,13 +3,13 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use crate::Position; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Direction, Piece, PlacedPiece}; +use chessfriend_core::{Direction, PlacedPiece, Shape}; move_generator_declaration!(ClassicalMoveGenerator); impl MoveGeneratorInternal for ClassicalMoveGenerator { - fn piece(color: Color) -> Piece { - Piece::queen(color) + fn shape() -> Shape { + Shape::Queen } fn move_set_for_piece( diff --git a/position/src/move_generator/rook.rs b/position/src/move_generator/rook.rs index b440273..18129c8 100644 --- a/position/src/move_generator/rook.rs +++ b/position/src/move_generator/rook.rs @@ -3,13 +3,13 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use crate::Position; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Direction, Piece, PlacedPiece}; +use chessfriend_core::{Direction, PlacedPiece, Shape}; move_generator_declaration!(ClassicalMoveGenerator); impl MoveGeneratorInternal for ClassicalMoveGenerator { - fn piece(color: Color) -> Piece { - Piece::rook(color) + fn shape() -> Shape { + Shape::Rook } fn move_set_for_piece( diff --git a/position/src/position/position.rs b/position/src/position/position.rs index f2b6de6..f0f8414 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -2,11 +2,11 @@ use super::{flags::Flags, piece_sets::PieceBitBoards, Pieces}; use crate::{ - move_generator::{MoveSet, Moves}, + check::{self, CheckingPieces}, + move_generator::Moves, position::DiagramFormatter, r#move::Castle, sight::SightExt, - Move, }; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; @@ -26,16 +26,7 @@ pub struct Position { impl Position { pub fn empty() -> Position { - Position { - color_to_move: Color::White, - flags: Default::default(), - pieces: PieceBitBoards::default(), - en_passant_square: None, - sight: [OnceCell::new(), OnceCell::new()], - moves: OnceCell::new(), - half_move_counter: 0, - full_move_number: 1, - } + Default::default() } /// Return a starting position. @@ -60,13 +51,8 @@ impl Position { Self { color_to_move: Color::White, - flags: Flags::default(), pieces: PieceBitBoards::new([white_pieces, black_pieces]), - en_passant_square: None, - sight: [OnceCell::new(), OnceCell::new()], - moves: OnceCell::new(), - half_move_counter: 0, - full_move_number: 1, + ..Default::default() } } @@ -135,8 +121,22 @@ impl Position { } pub fn moves(&self) -> &Moves { - self.moves - .get_or_init(|| Moves::new(self, self.color_to_move, BitBoard::FULL, BitBoard::FULL)) + self.moves.get_or_init(|| { + let checking_pieces = self.checking_pieces(); + match checking_pieces.count() { + // Normal, unrestricted move generation + 0 => Moves::new(self, self.color_to_move, BitBoard::FULL, BitBoard::FULL), + 1 => { + // Calculate push and capture masks for checking piece. Moves are restricted to those that intersect those masks. + let capture_mask = checking_pieces.capture_mask(); + let push_mask = + checking_pieces.push_mask(self.king_bitboard(self.color_to_move)); + Moves::new(self, self.color_to_move, capture_mask, push_mask) + } + // With more than one checking piece, the only legal moves are king moves. + _ => Moves::new(self, self.color_to_move, BitBoard::EMPTY, BitBoard::EMPTY), + } + }) } /// Return a BitBoard representing the set of squares containing a piece. @@ -249,6 +249,44 @@ impl Position { .next() .unwrap() } + + pub(crate) fn checking_pieces(&self) -> CheckingPieces { + let opponent = self.color_to_move.other(); + let king_square = self.king_square(self.color_to_move); + + let checking_pawns = { + // The current player's pawn attack moves *from* this square are the + // same as the pawn moves for the opposing player attacking this square. + let pawn_moves_to_king_square = BitBoard::pawn_attacks(king_square, self.color_to_move); + let opposing_pawn = Piece::pawn(opponent); + let opposing_pawns = self.pieces.bitboard_for_piece(&opposing_pawn); + + pawn_moves_to_king_square & opposing_pawns + }; + + macro_rules! checking_piece { + ($moves_bb_fn:path, $piece_fn:ident) => {{ + let moves_from_opposing_square = $moves_bb_fn(king_square); + let piece = Piece::$piece_fn(opponent); + let opposing_pieces = self.pieces.bitboard_for_piece(&piece); + + moves_from_opposing_square & opposing_pieces + }}; + } + + let checking_knights = checking_piece!(BitBoard::knight_moves, knight); + let checking_bishops = checking_piece!(BitBoard::bishop_moves, bishop); + let checking_rooks = checking_piece!(BitBoard::rook_moves, rook); + let checking_queens = checking_piece!(BitBoard::queen_moves, queen); + + CheckingPieces::new( + checking_pawns, + checking_knights, + checking_bishops, + checking_rooks, + checking_queens, + ) + } } // crate::position methods @@ -302,7 +340,16 @@ impl Position { impl Default for Position { fn default() -> Self { - Self::empty() + Self { + color_to_move: Color::White, + flags: Flags::default(), + pieces: PieceBitBoards::default(), + en_passant_square: None, + sight: [OnceCell::new(), OnceCell::new()], + moves: OnceCell::new(), + half_move_counter: 0, + full_move_number: 1, + } } } diff --git a/position/src/sight.rs b/position/src/sight.rs index 76005a2..056ae45 100644 --- a/position/src/sight.rs +++ b/position/src/sight.rs @@ -4,6 +4,20 @@ use crate::position::piece_sets::PieceBitBoards; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Direction, PlacedPiece, Shape, Square}; +macro_rules! ray_in_direction { + ($square:expr, $blockers:expr, $direction:ident, $occupied_squares:tt) => {{ + let ray = BitBoard::ray($square, Direction::$direction); + if let Some(first_occupied_square) = BitBoard::$occupied_squares(&(ray & $blockers)).next() + { + let remainder = BitBoard::ray(first_occupied_square, Direction::$direction); + let attack_ray = ray & !remainder; + attack_ray + } else { + *ray + } + }}; +} + pub(crate) trait SightExt { fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option) -> BitBoard; @@ -26,6 +40,10 @@ pub(crate) trait SightExt { fn king_sight(&self, pieces: &PieceBitBoards) -> BitBoard; } +pub(crate) trait SliderRayToSquareExt { + fn ray_to_square(&self, origin: Square, target: Square) -> Option; +} + impl SightExt for PlacedPiece { fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option) -> BitBoard { match self.shape() { @@ -88,25 +106,10 @@ impl SightExt for PlacedPiece { let blockers = pieces.all_pieces(); - macro_rules! update_moves_with_ray { - ($direction:ident, $occupied_squares:tt) => { - let ray = BitBoard::ray(square, Direction::$direction); - if let Some(first_occupied_square) = - BitBoard::$occupied_squares(&(ray & blockers)).next() - { - let remainder = BitBoard::ray(first_occupied_square, Direction::$direction); - let attack_ray = ray & !remainder; - sight |= attack_ray; - } else { - sight |= ray; - } - }; - } - - update_moves_with_ray!(NorthEast, occupied_squares_trailing); - update_moves_with_ray!(NorthWest, occupied_squares_trailing); - update_moves_with_ray!(SouthEast, occupied_squares); - update_moves_with_ray!(SouthWest, occupied_squares); + sight |= ray_in_direction!(square, blockers, NorthEast, occupied_squares_trailing); + sight |= ray_in_direction!(square, blockers, NorthWest, occupied_squares_trailing); + sight |= ray_in_direction!(square, blockers, SouthEast, occupied_squares); + sight |= ray_in_direction!(square, blockers, SouthWest, occupied_squares); let friendly_pieces = pieces.all_pieces_of_color(self.color()); sight & !friendly_pieces @@ -119,25 +122,10 @@ impl SightExt for PlacedPiece { let blockers = pieces.all_pieces(); - macro_rules! update_moves_with_ray { - ($direction:ident, $occupied_squares:tt) => { - let ray = BitBoard::ray(square, Direction::$direction); - if let Some(first_occupied_square) = - BitBoard::$occupied_squares(&(ray & blockers)).next() - { - let remainder = BitBoard::ray(first_occupied_square, Direction::$direction); - let attack_ray = ray & !remainder; - sight |= attack_ray; - } else { - sight |= ray; - } - }; - } - - update_moves_with_ray!(North, occupied_squares_trailing); - update_moves_with_ray!(East, occupied_squares_trailing); - update_moves_with_ray!(South, occupied_squares); - update_moves_with_ray!(West, occupied_squares); + sight |= ray_in_direction!(square, blockers, North, occupied_squares_trailing); + sight |= ray_in_direction!(square, blockers, East, occupied_squares_trailing); + sight |= ray_in_direction!(square, blockers, South, occupied_squares); + sight |= ray_in_direction!(square, blockers, West, occupied_squares); let friendly_pieces = pieces.all_pieces_of_color(self.color()); sight & !friendly_pieces @@ -150,29 +138,14 @@ impl SightExt for PlacedPiece { let blockers = pieces.all_pieces(); - macro_rules! update_moves_with_ray { - ($direction:ident, $occupied_squares:tt) => { - let ray = BitBoard::ray(square, Direction::$direction); - if let Some(first_occupied_square) = - BitBoard::$occupied_squares(&(ray & blockers)).next() - { - let remainder = BitBoard::ray(first_occupied_square, Direction::$direction); - let attack_ray = ray & !remainder; - sight |= attack_ray; - } else { - sight |= ray; - } - }; - } - - update_moves_with_ray!(NorthWest, occupied_squares_trailing); - update_moves_with_ray!(North, occupied_squares_trailing); - update_moves_with_ray!(NorthEast, occupied_squares_trailing); - update_moves_with_ray!(East, occupied_squares_trailing); - update_moves_with_ray!(SouthEast, occupied_squares); - update_moves_with_ray!(South, occupied_squares); - update_moves_with_ray!(SouthWest, occupied_squares); - update_moves_with_ray!(West, occupied_squares); + sight |= ray_in_direction!(square, blockers, NorthWest, occupied_squares_trailing); + sight |= ray_in_direction!(square, blockers, North, occupied_squares_trailing); + sight |= ray_in_direction!(square, blockers, NorthEast, occupied_squares_trailing); + sight |= ray_in_direction!(square, blockers, East, occupied_squares_trailing); + sight |= ray_in_direction!(square, blockers, SouthEast, occupied_squares); + sight |= ray_in_direction!(square, blockers, South, occupied_squares); + sight |= ray_in_direction!(square, blockers, SouthWest, occupied_squares); + sight |= ray_in_direction!(square, blockers, West, occupied_squares); let friendly_pieces = pieces.all_pieces_of_color(self.color()); sight & !friendly_pieces @@ -183,8 +156,66 @@ impl SightExt for PlacedPiece { } } +impl SliderRayToSquareExt for Shape { + fn ray_to_square(&self, origin: Square, target: Square) -> Option { + macro_rules! ray { + ($square:expr, $direction:ident) => { + ( + BitBoard::ray($square, Direction::$direction), + Direction::$direction, + ) + }; + } + + let target_bitboard: BitBoard = target.into(); + + let ray_and_direction = match self { + Shape::Bishop => [ + ray!(origin, NorthEast), + ray!(origin, SouthEast), + ray!(origin, SouthWest), + ray!(origin, NorthWest), + ] + .into_iter() + .find(|(&ray, _)| !(target_bitboard & ray).is_empty()), + Shape::Rook => [ + ray!(origin, North), + ray!(origin, East), + ray!(origin, South), + ray!(origin, West), + ] + .into_iter() + .find(|(&ray, _)| !(target_bitboard & ray).is_empty()), + Shape::Queen => [ + ray!(origin, North), + ray!(origin, NorthEast), + ray!(origin, East), + ray!(origin, SouthEast), + ray!(origin, South), + ray!(origin, SouthWest), + ray!(origin, West), + ray!(origin, NorthWest), + ] + .into_iter() + .find(|(&ray, _)| !(target_bitboard & ray).is_empty()), + _ => None, + }; + + if let Some((ray, direction)) = ray_and_direction { + let remainder = BitBoard::ray(target, direction); + return Some(ray & !remainder); + } + + None + } +} + #[cfg(test)] mod tests { + use super::*; + use chessfriend_bitboard::bitboard; + use chessfriend_core::{piece, Square}; + macro_rules! sight_test { ($test_name:ident, $position:expr, $piece:expr, $bitboard:expr) => { #[test] @@ -201,6 +232,12 @@ mod tests { }; } + #[test] + fn pawns_and_knights_cannot_make_rays() { + assert_eq!(Shape::Pawn.ray_to_square(Square::F7, Square::E8), None); + assert_eq!(Shape::Knight.ray_to_square(Square::F6, Square::E8), None); + } + mod pawn { use crate::test_position; use chessfriend_bitboard::{bitboard, BitBoard}; @@ -273,20 +310,25 @@ mod tests { } mod bishop { - use chessfriend_bitboard::bitboard; - use chessfriend_core::piece; + use super::*; sight_test!( c2_bishop, piece!(Black Bishop on C2), bitboard!(D1, B3, A4, B1, D3, E4, F5, G6, H7) ); + + #[test] + fn ray_to_square() { + let generated_ray = Shape::Bishop.ray_to_square(Square::C5, Square::E7); + let expected_ray = bitboard![D6, E7]; + assert_eq!(generated_ray, Some(expected_ray)); + } } mod rook { + use super::*; use crate::test_position; - use chessfriend_bitboard::bitboard; - use chessfriend_core::piece; sight_test!( g3_rook, @@ -304,6 +346,25 @@ mod tests { piece!(White Rook on E4), bitboard!(A4, B4, C4, D4, F4, G4, H4, E2, E3, E5, E6, E7) ); + + #[test] + fn ray_to_square() { + let generated_ray = Shape::Rook.ray_to_square(Square::C2, Square::C6); + let expected_ray = bitboard![C3, C4, C5, C6]; + assert_eq!(generated_ray, Some(expected_ray)); + + let generated_ray = Shape::Rook.ray_to_square(Square::D2, Square::H2); + let expected_ray = bitboard![E2, F2, G2, H2]; + assert_eq!(generated_ray, Some(expected_ray)); + + let generated_ray = Shape::Rook.ray_to_square(Square::G6, Square::B6); + let expected_ray = bitboard![B6, C6, D6, E6, F6]; + assert_eq!(generated_ray, Some(expected_ray)); + + let generated_ray = Shape::Rook.ray_to_square(Square::A6, Square::A3); + let expected_ray = bitboard![A5, A4, A3]; + assert_eq!(generated_ray, Some(expected_ray)); + } } mod king {