Implement a move generator that emits moves for the king(s) of a particular color.
There will, of course, only ever be one king per side in any valid board, but
this iterator can (in theory) handle multiple kings on the board. This iterator
is almost entirely copypasta of the SliderMoveGenerator. The major difference is
castling.
Castle moves are emitted by a helper CastleIterator type. This struct collects
information about whether the given color can castle on each side of the board
and then emits moves for each side, if indicated.
Do some light refactoring of the castle-related methods on Board to accommodate
this move generator. Remove the dependency on internal state and rename the
"can_castle" method to color_can_castle.
In order to facilitate creating castling moves without relying on Board, remove
the origin and target squares from the encoded castling move. Code that makes
a castling move already looks up castling parameters to move the king and rook to
the right squares, so encoding those squares was redundant. This change
necessitated some updates to position.
Lastly, bring in a handful of unit tests courtesy of Claude. Apparently, it's my
new best coding friend. 🙃
406 lines
12 KiB
Rust
406 lines
12 KiB
Rust
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
//! Defines routines for computing sight of a piece. Sight is the set of squares
|
|
//! that a piece can see. In other words, it's the set of squares attacked or
|
|
//! controled by a piece.
|
|
//!
|
|
//! These functions use some common terms to describe arguments.
|
|
//!
|
|
//! occupancy
|
|
//! : The set of occupied squares on the board.
|
|
//!
|
|
//! vacancy
|
|
//! : The set of empty squares on the board, `!occpuancy`.
|
|
//!
|
|
//! blockers
|
|
//! : The set of squares occupied by friendly pieces that block moves to that
|
|
//! square and beyond.
|
|
|
|
use crate::Board;
|
|
use chessfriend_bitboard::{BitBoard, IterationDirection};
|
|
use chessfriend_core::{Color, Direction, Piece, Shape, Square};
|
|
use std::ops::BitOr;
|
|
|
|
impl Board {
|
|
/// Compute sight of the piece on the given square.
|
|
pub fn sight(&self, square: Square) -> BitBoard {
|
|
if let Some(piece) = self.get_piece(square) {
|
|
piece.sight(square, self)
|
|
} else {
|
|
BitBoard::empty()
|
|
}
|
|
}
|
|
|
|
pub fn active_sight(&self) -> BitBoard {
|
|
self.friendly_sight(self.active_color)
|
|
}
|
|
|
|
/// A [`BitBoard`] of all squares the given color can see.
|
|
pub fn friendly_sight(&self, color: Color) -> BitBoard {
|
|
// TODO: Probably want to implement a caching layer here.
|
|
self.friendly_occupancy(color)
|
|
.occupied_squares(&IterationDirection::default())
|
|
.map(|square| self.sight(square))
|
|
.fold(BitBoard::empty(), BitOr::bitor)
|
|
}
|
|
|
|
pub fn active_color_opposing_sight(&self) -> BitBoard {
|
|
self.opposing_sight(self.active_color)
|
|
}
|
|
|
|
/// A [`BitBoard`] of all squares visible by colors that oppose the given color.
|
|
pub fn opposing_sight(&self, color: Color) -> BitBoard {
|
|
// TODO: Probably want to implement a caching layer here.
|
|
Color::ALL
|
|
.into_iter()
|
|
.filter_map(|c| {
|
|
if c == color {
|
|
None
|
|
} else {
|
|
Some(self.friendly_sight(c))
|
|
}
|
|
})
|
|
.fold(BitBoard::empty(), BitOr::bitor)
|
|
}
|
|
}
|
|
|
|
pub trait Sight {
|
|
fn sight(&self, square: Square, board: &Board) -> BitBoard;
|
|
}
|
|
|
|
impl Sight for Piece {
|
|
fn sight(&self, square: Square, board: &Board) -> BitBoard {
|
|
let occupancy = board.occupancy();
|
|
let info = SightInfo {
|
|
square,
|
|
occupancy,
|
|
friendly_occupancy: board.friendly_occupancy(self.color),
|
|
};
|
|
|
|
match self.shape {
|
|
Shape::Pawn => {
|
|
let en_passant_square: BitBoard = board.en_passant_target.into();
|
|
match self.color {
|
|
Color::White => white_pawn_sight(&info, en_passant_square),
|
|
Color::Black => black_pawn_sight(&info, en_passant_square),
|
|
}
|
|
}
|
|
Shape::Knight => knight_sight(&info),
|
|
Shape::Bishop => bishop_sight(&info),
|
|
Shape::Rook => rook_sight(&info),
|
|
Shape::Queen => queen_sight(&info),
|
|
Shape::King => king_sight(&info),
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! sight_method {
|
|
($name:ident) => {
|
|
pub fn $name(&self, square: Square, color: Option<Color>) -> BitBoard {
|
|
let color = self.unwrap_color(color);
|
|
|
|
let info = SightInfo {
|
|
square,
|
|
occupancy: self.occupancy(),
|
|
friendly_occupancy: self.friendly_occupancy(color),
|
|
};
|
|
|
|
$name(&info)
|
|
}
|
|
};
|
|
}
|
|
|
|
impl Board {
|
|
sight_method!(bishop_sight);
|
|
sight_method!(rook_sight);
|
|
sight_method!(queen_sight);
|
|
sight_method!(king_sight);
|
|
}
|
|
|
|
struct SightInfo {
|
|
square: Square,
|
|
occupancy: BitBoard,
|
|
friendly_occupancy: BitBoard,
|
|
}
|
|
|
|
macro_rules! ray_in_direction {
|
|
($square:expr, $blockers:expr, $direction:ident, $first_occupied_square:tt) => {{
|
|
let ray = BitBoard::ray($square, Direction::$direction);
|
|
let ray_blockers = ray & $blockers;
|
|
if let Some(first_occupied_square) = ray_blockers.$first_occupied_square() {
|
|
let remainder = BitBoard::ray(first_occupied_square, Direction::$direction);
|
|
let attack_ray = ray & !remainder;
|
|
attack_ray
|
|
} else {
|
|
ray
|
|
}
|
|
}};
|
|
}
|
|
|
|
/// Compute sight of a white pawn.
|
|
fn white_pawn_sight(info: &SightInfo, en_passant_square: BitBoard) -> BitBoard {
|
|
let possible_squares = !info.friendly_occupancy | en_passant_square;
|
|
|
|
let pawn: BitBoard = info.square.into();
|
|
let pawn = pawn.shift_north_west_one() | pawn.shift_north_east_one();
|
|
|
|
pawn & possible_squares
|
|
}
|
|
|
|
fn black_pawn_sight(info: &SightInfo, en_passant_square: BitBoard) -> BitBoard {
|
|
let possible_squares = !info.friendly_occupancy | en_passant_square;
|
|
|
|
let pawn: BitBoard = info.square.into();
|
|
let pawn = pawn.shift_south_west_one() | pawn.shift_south_east_one();
|
|
|
|
pawn & possible_squares
|
|
}
|
|
|
|
fn knight_sight(info: &SightInfo) -> BitBoard {
|
|
BitBoard::knight_moves(info.square)
|
|
}
|
|
|
|
fn bishop_sight(info: &SightInfo) -> BitBoard {
|
|
let bishop = info.square;
|
|
let occupancy = info.occupancy;
|
|
|
|
#[rustfmt::skip]
|
|
let sight = ray_in_direction!(bishop, occupancy, NorthEast, first_occupied_square_trailing)
|
|
| ray_in_direction!(bishop, occupancy, SouthEast, first_occupied_square_leading)
|
|
| ray_in_direction!(bishop, occupancy, SouthWest, first_occupied_square_leading)
|
|
| ray_in_direction!(bishop, occupancy, NorthWest, first_occupied_square_trailing);
|
|
|
|
sight
|
|
}
|
|
|
|
fn rook_sight(info: &SightInfo) -> BitBoard {
|
|
let rook = info.square;
|
|
let occupancy = info.occupancy;
|
|
|
|
#[rustfmt::skip]
|
|
let sight = ray_in_direction!(rook, occupancy, North, first_occupied_square_trailing)
|
|
| ray_in_direction!(rook, occupancy, East, first_occupied_square_trailing)
|
|
| ray_in_direction!(rook, occupancy, South, first_occupied_square_leading)
|
|
| ray_in_direction!(rook, occupancy, West, first_occupied_square_leading);
|
|
|
|
sight
|
|
}
|
|
|
|
fn queen_sight(info: &SightInfo) -> BitBoard {
|
|
let queen = info.square;
|
|
let occupancy = info.occupancy;
|
|
|
|
#[rustfmt::skip]
|
|
let sight = ray_in_direction!(queen, occupancy, NorthWest, first_occupied_square_trailing)
|
|
| ray_in_direction!(queen, occupancy, North, first_occupied_square_trailing)
|
|
| ray_in_direction!(queen, occupancy, NorthEast, first_occupied_square_trailing)
|
|
| ray_in_direction!(queen, occupancy, East, first_occupied_square_trailing)
|
|
| ray_in_direction!(queen, occupancy, SouthEast, first_occupied_square_leading)
|
|
| ray_in_direction!(queen, occupancy, South, first_occupied_square_leading)
|
|
| ray_in_direction!(queen, occupancy, SouthWest, first_occupied_square_leading)
|
|
| ray_in_direction!(queen, occupancy, West, first_occupied_square_leading);
|
|
|
|
sight
|
|
}
|
|
|
|
fn king_sight(info: &SightInfo) -> BitBoard {
|
|
BitBoard::king_moves(info.square)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::test_board;
|
|
use chessfriend_bitboard::bitboard;
|
|
use chessfriend_core::piece;
|
|
|
|
macro_rules! sight_test {
|
|
($test_name:ident, $position:expr, $piece:expr, $square:expr, $bitboard:expr) => {
|
|
#[test]
|
|
fn $test_name() {
|
|
use chessfriend_core::Square;
|
|
use $crate::sight::Sight;
|
|
|
|
let pos = $position;
|
|
let piece = $piece;
|
|
let sight = piece.sight($square, &pos);
|
|
|
|
assert_eq!(sight, $bitboard);
|
|
}
|
|
};
|
|
($test_name:ident, $piece:expr, $square:expr, $bitboard:expr) => {
|
|
sight_test! {$test_name, $crate::Board::empty(), $piece, $square, $bitboard}
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn friendly_sight() {
|
|
let pos = test_board!(
|
|
White King on E4,
|
|
);
|
|
|
|
let sight = pos.active_sight();
|
|
assert_eq!(sight, bitboard![E5 F5 F4 F3 E3 D3 D4 D5]);
|
|
}
|
|
|
|
#[test]
|
|
fn opposing_sight() {
|
|
let pos = test_board!(
|
|
White King on E4,
|
|
Black Rook on E7,
|
|
);
|
|
|
|
let sight = pos.active_color_opposing_sight();
|
|
assert_eq!(sight, bitboard![A7 B7 C7 D7 F7 G7 H7 E8 E6 E5 E4]);
|
|
}
|
|
|
|
// #[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::{sight::Sight, test_board};
|
|
use chessfriend_bitboard::{bitboard, BitBoard};
|
|
use chessfriend_core::{piece, Square};
|
|
|
|
sight_test!(e4_pawn, piece!(White Pawn), Square::E4, bitboard![D5 F5]);
|
|
|
|
sight_test!(
|
|
e4_pawn_one_blocker,
|
|
test_board![
|
|
White Bishop on D5,
|
|
White Pawn on E4,
|
|
],
|
|
piece!(White Pawn),
|
|
Square::E4,
|
|
bitboard!(F5)
|
|
);
|
|
|
|
#[test]
|
|
fn e4_pawn_two_blocker() {
|
|
let pos = test_board!(
|
|
White Bishop on D5,
|
|
White Queen on F5,
|
|
White Pawn on E4,
|
|
);
|
|
|
|
let piece = piece!(White Pawn);
|
|
let sight = piece.sight(Square::E4, &pos);
|
|
|
|
assert_eq!(sight, BitBoard::empty());
|
|
}
|
|
|
|
#[test]
|
|
fn e4_pawn_capturable() {
|
|
let pos = test_board!(
|
|
Black Bishop on D5,
|
|
White Queen on F5,
|
|
White Pawn on E4,
|
|
);
|
|
|
|
let piece = piece!(White Pawn);
|
|
let sight = piece.sight(Square::E4, &pos);
|
|
|
|
assert_eq!(sight, bitboard![D5]);
|
|
}
|
|
|
|
#[test]
|
|
fn e5_en_passant() {
|
|
let pos = test_board!(White, [
|
|
White Pawn on E5,
|
|
Black Pawn on D5,
|
|
], D6);
|
|
let piece = piece!(White Pawn);
|
|
let sight = piece.sight(Square::E5, &pos);
|
|
|
|
assert_eq!(sight, bitboard!(D6 F6));
|
|
}
|
|
}
|
|
|
|
#[macro_use]
|
|
mod knight {
|
|
use chessfriend_bitboard::bitboard;
|
|
use chessfriend_core::piece;
|
|
|
|
sight_test!(
|
|
f6_knight,
|
|
piece!(Black Knight),
|
|
Square::F6,
|
|
bitboard![H7 G8 E8 D7 D5 E4 G4 H5]
|
|
);
|
|
}
|
|
|
|
mod bishop {
|
|
use super::*;
|
|
|
|
sight_test!(
|
|
c2_bishop,
|
|
piece!(Black Bishop),
|
|
Square::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_board;
|
|
|
|
sight_test!(
|
|
g3_rook,
|
|
piece!(White Rook),
|
|
Square::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_board![
|
|
White Rook on E4,
|
|
White King on E2,
|
|
Black King on E7,
|
|
],
|
|
piece!(White Rook),
|
|
Square::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 {
|
|
use chessfriend_bitboard::bitboard;
|
|
use chessfriend_core::piece;
|
|
|
|
sight_test!(
|
|
e1_king,
|
|
piece!(White King),
|
|
Square::E1,
|
|
bitboard![D1 D2 E2 F2 F1]
|
|
);
|
|
}
|
|
}
|