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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "board"
version = "0.1.0"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 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]] [[package]]
name = "clap" name = "clap"
version = "4.4.18" version = "4.4.18"
@ -121,10 +145,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "core"
version = "0.1.0"
[[package]] [[package]]
name = "endian-type" name = "endian-type"
version = "0.1.2" version = "0.1.2"
@ -151,7 +171,8 @@ checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
name = "explorer" name = "explorer"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"board", "chessfriend_core",
"chessfriend_position",
"clap", "clap",
"rustyline", "rustyline",
"shlex", "shlex",

View file

@ -1,7 +1,7 @@
// Eryn Wells <eryn@erynwells.me> // Eryn Wells <eryn@erynwells.me>
use crate::library; use crate::library;
use crate::LeadingBitScanner; use crate::{LeadingBitScanner, TrailingBitScanner};
use chessfriend_core::{Color, Direction, Square}; use chessfriend_core::{Color, Direction, Square};
use std::fmt; use std::fmt;
use std::ops::Not; use std::ops::Not;
@ -39,7 +39,7 @@ impl BitBoard {
library::FILES[*file as usize] 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) library::library().ray(sq, dir)
} }
@ -80,6 +80,20 @@ impl BitBoard {
!(self & square_bitboard).is_empty() !(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) { pub fn set_square(&mut self, sq: Square) {
let sq_bb: BitBoard = sq.into(); let sq_bb: BitBoard = sq.into();
*self |= sq_bb *self |= sq_bb
@ -105,13 +119,31 @@ impl BitBoard {
/// Return an Iterator over the occupied squares, starting from the trailing /// Return an Iterator over the occupied squares, starting from the trailing
/// (least-significant bit) end of the field. /// (least-significant bit) end of the field.
pub fn occupied_squares_trailing(&self) -> impl Iterator<Item = Square> { 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 { impl Default for BitBoard {
fn default() -> Self { 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::A1), BitBoard(0b1));
assert_eq!(BitBoard::from(Square::H8), BitBoard(1 << 63)); 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 ray
} }
pub(super) fn ray(&self, sq: Square, dir: Direction) -> BitBoard { pub(super) fn ray(&self, sq: Square, dir: Direction) -> &BitBoard {
self.rays[sq as usize][dir as usize] &self.rays[sq as usize][dir as usize]
} }
pub(super) fn pawn_pushes(&self, sq: Square, color: Color) -> BitBoard { 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; pub mod fen;
mod check;
mod display; mod display;
mod r#move; mod r#move;
mod move_generator; mod move_generator;

View file

@ -51,10 +51,15 @@ macro_rules! move_generator_declaration {
capture_mask: chessfriend_bitboard::BitBoard, capture_mask: chessfriend_bitboard::BitBoard,
push_mask: chessfriend_bitboard::BitBoard, push_mask: chessfriend_bitboard::BitBoard,
) -> $name { ) -> $name {
$name { let move_sets = if Self::shape() == chessfriend_core::Shape::King
color, || (!capture_mask.is_empty() && !push_mask.is_empty())
move_sets: Self::move_sets(position, color, capture_mask, push_mask), {
} 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; pub(self) use move_generator_declaration;
trait MoveGeneratorInternal { trait MoveGeneratorInternal {
fn piece(color: Color) -> Piece; fn shape() -> Shape;
fn piece(color: Color) -> Piece {
Piece::new(color, Self::shape())
}
fn move_sets( fn move_sets(
position: &Position, position: &Position,

View file

@ -3,13 +3,13 @@
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use crate::Position; use crate::Position;
use chessfriend_bitboard::BitBoard; use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Direction, Piece, PlacedPiece}; use chessfriend_core::{Direction, PlacedPiece, Shape};
move_generator_declaration!(ClassicalMoveGenerator); move_generator_declaration!(ClassicalMoveGenerator);
impl MoveGeneratorInternal for ClassicalMoveGenerator { impl MoveGeneratorInternal for ClassicalMoveGenerator {
fn piece(color: Color) -> Piece { fn shape() -> Shape {
Piece::bishop(color) Shape::Bishop
} }
fn move_set_for_piece( fn move_set_for_piece(

View file

@ -6,22 +6,22 @@
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use crate::{r#move::Castle, Position}; use crate::{r#move::Castle, Position};
use chessfriend_bitboard::BitBoard; use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Piece, PlacedPiece}; use chessfriend_core::{PlacedPiece, Shape};
move_generator_declaration!(KingMoveGenerator, struct); move_generator_declaration!(KingMoveGenerator, struct);
move_generator_declaration!(KingMoveGenerator, new); move_generator_declaration!(KingMoveGenerator, new);
move_generator_declaration!(KingMoveGenerator, getters); move_generator_declaration!(KingMoveGenerator, getters);
impl MoveGeneratorInternal for KingMoveGenerator { impl MoveGeneratorInternal for KingMoveGenerator {
fn piece(color: Color) -> Piece { fn shape() -> Shape {
Piece::king(color) Shape::King
} }
fn move_set_for_piece( fn move_set_for_piece(
position: &Position, position: &Position,
placed_piece: PlacedPiece, placed_piece: PlacedPiece,
capture_mask: BitBoard, _capture_mask: BitBoard,
push_mask: BitBoard, _push_mask: BitBoard,
) -> MoveSet { ) -> MoveSet {
let piece = placed_piece.piece(); let piece = placed_piece.piece();
let color = piece.color(); let color = piece.color();
@ -59,7 +59,7 @@ mod tests {
use super::*; use super::*;
use crate::{assert_move_list, position, test_position, Move, MoveBuilder, PositionBuilder}; use crate::{assert_move_list, position, test_position, Move, MoveBuilder, PositionBuilder};
use chessfriend_bitboard::bitboard; use chessfriend_bitboard::bitboard;
use chessfriend_core::{piece, Square}; use chessfriend_core::{piece, Color, Square};
use std::collections::HashSet; use std::collections::HashSet;
#[test] #[test]

View file

@ -3,13 +3,13 @@
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use crate::Position; use crate::Position;
use chessfriend_bitboard::BitBoard; use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Piece, PlacedPiece}; use chessfriend_core::{PlacedPiece, Shape};
move_generator_declaration!(KnightMoveGenerator); move_generator_declaration!(KnightMoveGenerator);
impl MoveGeneratorInternal for KnightMoveGenerator { impl MoveGeneratorInternal for KnightMoveGenerator {
fn piece(color: Color) -> Piece { fn shape() -> Shape {
Piece::knight(color) Shape::Knight
} }
fn move_set_for_piece( fn move_set_for_piece(
@ -35,7 +35,7 @@ impl MoveGeneratorInternal for KnightMoveGenerator {
mod tests { mod tests {
use super::*; use super::*;
use crate::{position, Move, MoveBuilder}; use crate::{position, Move, MoveBuilder};
use chessfriend_core::{piece, Square}; use chessfriend_core::{piece, Color, Square};
use std::collections::HashSet; use std::collections::HashSet;
#[test] #[test]

View file

@ -3,7 +3,7 @@
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use crate::Position; use crate::Position;
use chessfriend_bitboard::BitBoard; use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Piece, PlacedPiece, Rank, Square}; use chessfriend_core::{Piece, PlacedPiece, Rank, Shape, Square};
#[derive(Debug)] #[derive(Debug)]
struct MoveIterator(usize, usize); struct MoveIterator(usize, usize);
@ -11,8 +11,8 @@ struct MoveIterator(usize, usize);
move_generator_declaration!(PawnMoveGenerator); move_generator_declaration!(PawnMoveGenerator);
impl MoveGeneratorInternal for PawnMoveGenerator { impl MoveGeneratorInternal for PawnMoveGenerator {
fn piece(color: Color) -> Piece { fn shape() -> Shape {
Piece::pawn(color) Shape::Pawn
} }
fn move_set_for_piece( fn move_set_for_piece(
@ -63,7 +63,7 @@ impl PawnMoveGenerator {
mod tests { mod tests {
use super::*; use super::*;
use crate::{assert_move_list, position::DiagramFormatter, test_position, Move, MoveBuilder}; 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; use std::collections::HashSet;
#[test] #[test]

View file

@ -3,13 +3,13 @@
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use crate::Position; use crate::Position;
use chessfriend_bitboard::BitBoard; use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Direction, Piece, PlacedPiece}; use chessfriend_core::{Direction, PlacedPiece, Shape};
move_generator_declaration!(ClassicalMoveGenerator); move_generator_declaration!(ClassicalMoveGenerator);
impl MoveGeneratorInternal for ClassicalMoveGenerator { impl MoveGeneratorInternal for ClassicalMoveGenerator {
fn piece(color: Color) -> Piece { fn shape() -> Shape {
Piece::queen(color) Shape::Queen
} }
fn move_set_for_piece( fn move_set_for_piece(

View file

@ -3,13 +3,13 @@
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use crate::Position; use crate::Position;
use chessfriend_bitboard::BitBoard; use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Direction, Piece, PlacedPiece}; use chessfriend_core::{Direction, PlacedPiece, Shape};
move_generator_declaration!(ClassicalMoveGenerator); move_generator_declaration!(ClassicalMoveGenerator);
impl MoveGeneratorInternal for ClassicalMoveGenerator { impl MoveGeneratorInternal for ClassicalMoveGenerator {
fn piece(color: Color) -> Piece { fn shape() -> Shape {
Piece::rook(color) Shape::Rook
} }
fn move_set_for_piece( fn move_set_for_piece(

View file

@ -2,11 +2,11 @@
use super::{flags::Flags, piece_sets::PieceBitBoards, Pieces}; use super::{flags::Flags, piece_sets::PieceBitBoards, Pieces};
use crate::{ use crate::{
move_generator::{MoveSet, Moves}, check::{self, CheckingPieces},
move_generator::Moves,
position::DiagramFormatter, position::DiagramFormatter,
r#move::Castle, r#move::Castle,
sight::SightExt, sight::SightExt,
Move,
}; };
use chessfriend_bitboard::BitBoard; use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
@ -26,16 +26,7 @@ pub struct Position {
impl Position { impl Position {
pub fn empty() -> Position { pub fn empty() -> Position {
Position { Default::default()
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,
}
} }
/// Return a starting position. /// Return a starting position.
@ -60,13 +51,8 @@ impl Position {
Self { Self {
color_to_move: Color::White, color_to_move: Color::White,
flags: Flags::default(),
pieces: PieceBitBoards::new([white_pieces, black_pieces]), pieces: PieceBitBoards::new([white_pieces, black_pieces]),
en_passant_square: None, ..Default::default()
sight: [OnceCell::new(), OnceCell::new()],
moves: OnceCell::new(),
half_move_counter: 0,
full_move_number: 1,
} }
} }
@ -135,8 +121,22 @@ impl Position {
} }
pub fn moves(&self) -> &Moves { pub fn moves(&self) -> &Moves {
self.moves self.moves.get_or_init(|| {
.get_or_init(|| Moves::new(self, self.color_to_move, BitBoard::FULL, BitBoard::FULL)) 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. /// Return a BitBoard representing the set of squares containing a piece.
@ -249,6 +249,44 @@ impl Position {
.next() .next()
.unwrap() .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 // crate::position methods
@ -302,7 +340,16 @@ impl Position {
impl Default for Position { impl Default for Position {
fn default() -> Self { 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_bitboard::BitBoard;
use chessfriend_core::{Color, Direction, PlacedPiece, Shape, Square}; 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 { pub(crate) trait SightExt {
fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option<Square>) -> BitBoard; 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; 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 { impl SightExt for PlacedPiece {
fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option<Square>) -> BitBoard { fn sight(&self, pieces: &PieceBitBoards, en_passant_square: Option<Square>) -> BitBoard {
match self.shape() { match self.shape() {
@ -88,25 +106,10 @@ impl SightExt for PlacedPiece {
let blockers = pieces.all_pieces(); let blockers = pieces.all_pieces();
macro_rules! update_moves_with_ray { sight |= ray_in_direction!(square, blockers, NorthEast, occupied_squares_trailing);
($direction:ident, $occupied_squares:tt) => { sight |= ray_in_direction!(square, blockers, NorthWest, occupied_squares_trailing);
let ray = BitBoard::ray(square, Direction::$direction); sight |= ray_in_direction!(square, blockers, SouthEast, occupied_squares);
if let Some(first_occupied_square) = sight |= ray_in_direction!(square, blockers, SouthWest, occupied_squares);
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);
let friendly_pieces = pieces.all_pieces_of_color(self.color()); let friendly_pieces = pieces.all_pieces_of_color(self.color());
sight & !friendly_pieces sight & !friendly_pieces
@ -119,25 +122,10 @@ impl SightExt for PlacedPiece {
let blockers = pieces.all_pieces(); let blockers = pieces.all_pieces();
macro_rules! update_moves_with_ray { sight |= ray_in_direction!(square, blockers, North, occupied_squares_trailing);
($direction:ident, $occupied_squares:tt) => { sight |= ray_in_direction!(square, blockers, East, occupied_squares_trailing);
let ray = BitBoard::ray(square, Direction::$direction); sight |= ray_in_direction!(square, blockers, South, occupied_squares);
if let Some(first_occupied_square) = sight |= ray_in_direction!(square, blockers, West, occupied_squares);
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);
let friendly_pieces = pieces.all_pieces_of_color(self.color()); let friendly_pieces = pieces.all_pieces_of_color(self.color());
sight & !friendly_pieces sight & !friendly_pieces
@ -150,29 +138,14 @@ impl SightExt for PlacedPiece {
let blockers = pieces.all_pieces(); let blockers = pieces.all_pieces();
macro_rules! update_moves_with_ray { sight |= ray_in_direction!(square, blockers, NorthWest, occupied_squares_trailing);
($direction:ident, $occupied_squares:tt) => { sight |= ray_in_direction!(square, blockers, North, occupied_squares_trailing);
let ray = BitBoard::ray(square, Direction::$direction); sight |= ray_in_direction!(square, blockers, NorthEast, occupied_squares_trailing);
if let Some(first_occupied_square) = sight |= ray_in_direction!(square, blockers, East, occupied_squares_trailing);
BitBoard::$occupied_squares(&(ray & blockers)).next() sight |= ray_in_direction!(square, blockers, SouthEast, occupied_squares);
{ sight |= ray_in_direction!(square, blockers, South, occupied_squares);
let remainder = BitBoard::ray(first_occupied_square, Direction::$direction); sight |= ray_in_direction!(square, blockers, SouthWest, occupied_squares);
let attack_ray = ray & !remainder; sight |= ray_in_direction!(square, blockers, West, occupied_squares);
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);
let friendly_pieces = pieces.all_pieces_of_color(self.color()); let friendly_pieces = pieces.all_pieces_of_color(self.color());
sight & !friendly_pieces 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)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use chessfriend_bitboard::bitboard;
use chessfriend_core::{piece, Square};
macro_rules! sight_test { macro_rules! sight_test {
($test_name:ident, $position:expr, $piece:expr, $bitboard:expr) => { ($test_name:ident, $position:expr, $piece:expr, $bitboard:expr) => {
#[test] #[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 { mod pawn {
use crate::test_position; use crate::test_position;
use chessfriend_bitboard::{bitboard, BitBoard}; use chessfriend_bitboard::{bitboard, BitBoard};
@ -273,20 +310,25 @@ mod tests {
} }
mod bishop { mod bishop {
use chessfriend_bitboard::bitboard; use super::*;
use chessfriend_core::piece;
sight_test!( sight_test!(
c2_bishop, c2_bishop,
piece!(Black Bishop on C2), piece!(Black Bishop on C2),
bitboard!(D1, B3, A4, B1, D3, E4, F5, G6, H7) 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 { mod rook {
use super::*;
use crate::test_position; use crate::test_position;
use chessfriend_bitboard::bitboard;
use chessfriend_core::piece;
sight_test!( sight_test!(
g3_rook, g3_rook,
@ -304,6 +346,25 @@ mod tests {
piece!(White Rook on E4), piece!(White Rook on E4),
bitboard!(A4, B4, C4, D4, F4, G4, H4, E2, E3, E5, E6, E7) 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 { mod king {