Merge branch 'checking-pieces-struct'

This commit is contained in:
Eryn Wells 2024-02-02 08:06:17 -08:00
commit efcc65d8d6
14 changed files with 407 additions and 129 deletions

39
Cargo.lock generated
View file

@ -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",

View file

@ -1,7 +1,7 @@
// Eryn Wells <eryn@erynwells.me>
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<Item = Square> {
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<Square> {
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<Square> {
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));
}
}

View file

@ -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 {

97
position/src/check.rs Normal file
View file

@ -0,0 +1,97 @@
// Eryn Wells <eryn@erynwells.me>
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]);
}
}

View file

@ -2,6 +2,7 @@
pub mod fen;
mod check;
mod display;
mod r#move;
mod move_generator;

View file

@ -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,

View file

@ -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(

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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(

View file

@ -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(

View file

@ -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,
}
}
}

View file

@ -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<Square>) -> 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<BitBoard>;
}
impl SightExt for PlacedPiece {
fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option<Square>) -> 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<BitBoard> {
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 {