[position] Remove dead move_generator code

This commit is contained in:
Eryn Wells 2025-05-29 09:21:25 -07:00
parent 942d9fe47b
commit a4b713a558
8 changed files with 0 additions and 1441 deletions

View file

@ -1,173 +0,0 @@
// Eryn Wells <eryn@erynwells.me>
mod bishop;
mod king;
mod knight;
mod move_set;
mod pawn;
mod queen;
mod rook;
#[cfg(test)]
mod tests;
pub(crate) use move_set::MoveSet;
use self::{
bishop::ClassicalMoveGenerator as BishopMoveGenerator, king::KingMoveGenerator,
knight::KnightMoveGenerator, pawn::PawnMoveGenerator,
queen::ClassicalMoveGenerator as QueenMoveGenerator,
rook::ClassicalMoveGenerator as RookMoveGenerator,
};
use crate::Position;
use chessfriend_bitboard::BitBoard;
use chessfriend_board::Board;
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
use chessfriend_moves::Move;
use std::collections::BTreeMap;
trait MoveGenerator {
fn iter(&self) -> dyn Iterator<Item = Move>;
fn moves(&self, color: Color) -> dyn Iterator<Item = Move>;
fn attacks(&self, color: Color) -> dyn Iterator<Item = Move>;
}
macro_rules! move_generator_declaration {
($name:ident) => {
move_generator_declaration!($name, struct);
move_generator_declaration!($name, new);
move_generator_declaration!($name, getters);
};
($name:ident, struct) => {
#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) struct $name {
color: chessfriend_core::Color,
move_sets: std::collections::BTreeMap<
chessfriend_core::Square,
$crate::move_generator::MoveSet,
>,
}
};
($name:ident, new) => {
impl $name {
pub(super) fn new(
board: &chessfriend_board::Board,
color: chessfriend_core::Color,
capture_mask: chessfriend_bitboard::BitBoard,
push_mask: chessfriend_bitboard::BitBoard,
) -> $name {
let move_sets = if Self::shape() == chessfriend_core::Shape::King
|| !(capture_mask.is_empty() && push_mask.is_empty())
{
Self::move_sets(board, color, capture_mask, push_mask)
} else {
std::collections::BTreeMap::new()
};
$name { color, move_sets }
}
}
};
($name:ident, getters) => {
impl $name {
pub(super) fn iter(&self) -> impl Iterator<Item = chessfriend_moves::Move> + '_ {
self.move_sets.values().flat_map(|set| set.moves())
}
pub(crate) fn moves_for_piece(
&self,
piece: &chessfriend_core::PlacedPiece,
) -> Option<&$crate::move_generator::move_set::MoveSet> {
self.move_sets.get(&piece.square())
}
#[cfg(test)]
fn _test_bitboard(&self) -> chessfriend_bitboard::BitBoard {
self.move_sets.values().fold(
chessfriend_bitboard::BitBoard::empty(),
|partial, mv_set| partial | mv_set.bitboard(),
)
}
}
};
}
pub(self) use move_generator_declaration;
trait MoveGeneratorInternal {
fn shape() -> Shape;
fn piece(color: Color) -> Piece {
Piece::new(color, Self::shape())
}
fn move_sets(
board: &Board,
color: Color,
capture_mask: BitBoard,
push_mask: BitBoard,
) -> BTreeMap<Square, MoveSet> {
let piece = Self::piece(color);
BTreeMap::from_iter(
board
.bitboard_for_piece(piece)
.occupied_squares()
.map(|square| {
let piece = PlacedPiece::new(piece, square);
let move_set = Self::move_set_for_piece(board, &piece, capture_mask, push_mask);
(square, move_set)
}),
)
}
fn move_set_for_piece(
board: &Board,
piece: &PlacedPiece,
capture_mask: BitBoard,
push_mask: BitBoard,
) -> MoveSet;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Moves {
pawn_moves: PawnMoveGenerator,
knight_moves: KnightMoveGenerator,
bishop_moves: BishopMoveGenerator,
rook_moves: RookMoveGenerator,
queen_moves: QueenMoveGenerator,
king_moves: KingMoveGenerator,
}
impl Moves {
pub fn new(board: &Board, color: Color, capture_mask: BitBoard, push_mask: BitBoard) -> Moves {
Moves {
pawn_moves: PawnMoveGenerator::new(board, color, capture_mask, push_mask),
knight_moves: KnightMoveGenerator::new(board, color, capture_mask, push_mask),
bishop_moves: BishopMoveGenerator::new(board, color, capture_mask, push_mask),
rook_moves: RookMoveGenerator::new(board, color, capture_mask, push_mask),
queen_moves: QueenMoveGenerator::new(board, color, capture_mask, push_mask),
king_moves: KingMoveGenerator::new(board, color, capture_mask, push_mask),
}
}
pub(crate) fn moves_for_piece(&self, piece: &PlacedPiece) -> Option<&MoveSet> {
match piece.shape() {
Shape::Pawn => self.pawn_moves.moves_for_piece(piece),
Shape::Knight => self.knight_moves.moves_for_piece(piece),
Shape::Bishop => self.bishop_moves.moves_for_piece(piece),
Shape::Rook => self.rook_moves.moves_for_piece(piece),
Shape::Queen => self.queen_moves.moves_for_piece(piece),
Shape::King => self.king_moves.moves_for_piece(piece),
}
}
pub fn iter(&self) -> impl Iterator<Item = Move> + '_ {
self.pawn_moves
.iter()
.chain(self.knight_moves.iter())
.chain(self.bishop_moves.iter())
.chain(self.rook_moves.iter())
.chain(self.queen_moves.iter())
.chain(self.king_moves.iter())
}
}

View file

@ -1,143 +0,0 @@
// Eryn Wells <eryn@erynwells.me>
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use chessfriend_bitboard::BitBoard;
use chessfriend_board::Board;
use chessfriend_core::{Direction, PlacedPiece, Shape};
move_generator_declaration!(ClassicalMoveGenerator);
impl MoveGeneratorInternal for ClassicalMoveGenerator {
fn shape() -> Shape {
Shape::Bishop
}
fn move_set_for_piece(
board: &Board,
piece: &PlacedPiece,
capture_mask: BitBoard,
push_mask: BitBoard,
) -> MoveSet {
let square = piece.square();
let blockers = board.occupied_squares();
let empty_squares = !blockers;
let (friendly_pieces, opposing_pieces) = board.all_pieces();
let mut all_moves = BitBoard::empty();
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;
all_moves |= attack_ray;
} else {
all_moves |= 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 quiet_moves = all_moves & (empty_squares | !friendly_pieces) & push_mask;
let capture_moves = all_moves & opposing_pieces & capture_mask;
MoveSet::new(*piece)
.quiet_moves(quiet_moves)
.capture_moves(capture_moves)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::position;
use chessfriend_bitboard::BitBoard;
use chessfriend_core::Color;
#[test]
fn classical_single_bishop_bitboard() {
let pos = position![
White Bishop on A1,
];
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
assert_eq!(
generator._test_bitboard(),
BitBoard::new(
0b10000000_01000000_00100000_00010000_00001000_00000100_00000010_00000000
)
);
}
/// Test that a bishop can move up to, but not onto, a friendly piece.
#[test]
fn classical_single_bishop_with_same_color_blocker_bitboard() {
let pos = position![
White Bishop on A1,
White Knight on E5,
];
println!("{}", pos.display());
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
assert_eq!(
generator._test_bitboard(),
BitBoard::new(
0b00000000_00000000_00000000_00000000_00001000_00000100_00000010_00000000
)
);
}
/// Test that a rook can move up to, and then capture, an enemy piece.
#[test]
fn classical_single_bishop_with_opposing_color_blocker_bitboard() {
let pos = position![
White Bishop on A1,
Black Knight on C3,
];
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
assert_eq!(
generator._test_bitboard(),
BitBoard::new(
0b00000000_00000000_00000000_00000000_00000000_00000100_00000010_00000000
)
);
}
#[test]
fn classical_single_bishop_in_center() {
let pos = position![
White Bishop on E4,
];
println!("{}", pos.display());
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let bitboard = generator._test_bitboard();
let expected = BitBoard::new(
0b00000001_10000010_01000100_00101000_00000000_00101000_01000100_10000010,
);
assert_eq!(
bitboard, expected,
"actual:\n{}\nexpected:\n{}",
bitboard, expected
);
}
}

View file

@ -1,228 +0,0 @@
// Eryn Wells <eryn@erynwells.me>
//! Declares the KingMoveGenerator type. This struct is responsible for
//! generating the possible moves for the king in the given position.
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use chessfriend_bitboard::BitBoard;
use chessfriend_board::{castle::Castle, Board};
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 shape() -> Shape {
Shape::King
}
fn move_set_for_piece(
board: &Board,
placed_piece: &PlacedPiece,
_capture_mask: BitBoard,
_push_mask: BitBoard,
) -> MoveSet {
let piece = placed_piece.piece();
let color = piece.color();
let square = placed_piece.square();
let safe_squares = BitBoard::FULL;
let all_king_moves = BitBoard::king_moves(square);
let empty_squares = board.empty_squares();
let safe_empty_squares = empty_squares & safe_squares;
let opposing_pieces = board.bitboard_for_color(color.other());
let opposing_pieces_on_safe_squares = opposing_pieces & safe_squares;
let quiet_moves = all_king_moves & safe_empty_squares;
let capture_moves = all_king_moves & opposing_pieces_on_safe_squares;
let mut move_set = MoveSet::new(*placed_piece)
.quiet_moves(quiet_moves)
.capture_moves(capture_moves);
if board.player_can_castle(color, Castle::KingSide) {
move_set.kingside_castle();
}
if board.player_can_castle(color, Castle::QueenSide) {
move_set.queenside_castle();
}
move_set
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_move_list, test_position, testing::*};
use chessfriend_bitboard::bitboard;
use chessfriend_board::castle::Castle;
use chessfriend_core::{piece, Color, Square};
use chessfriend_moves::{Builder as MoveBuilder, Move};
use std::collections::HashSet;
#[test]
fn one_king() -> TestResult {
let pos = test_position![White King on E4];
let generator =
KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
assert_eq!(
generator._test_bitboard(),
bitboard![E5 F5 F4 F3 E3 D3 D4 D5]
);
let builder = MoveBuilder::push(&piece!(White King on E4));
let expected_moves: HashSet<Move> = HashSet::from_iter([
builder.clone().to(Square::D5).build()?,
builder.clone().to(Square::E5).build()?,
builder.clone().to(Square::F5).build()?,
builder.clone().to(Square::F4).build()?,
builder.clone().to(Square::F3).build()?,
builder.clone().to(Square::E3).build()?,
builder.clone().to(Square::D3).build()?,
builder.clone().to(Square::D4).build()?,
]);
let generated_moves: HashSet<Move> = generator.iter().collect();
assert_move_list!(generated_moves, expected_moves, pos);
Ok(())
}
#[test]
fn one_king_corner() -> TestResult {
let pos = test_position![White King on A1];
let generator =
KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let generated_bitboard = generator._test_bitboard();
let expected_bitboard = bitboard![A2 B2 B1];
assert_eq!(
generator._test_bitboard(),
bitboard![A2 B2 B1],
"Generated:\n{generated_bitboard}\nExpected:\n{expected_bitboard}"
);
let builder = MoveBuilder::push(&piece!(White King on A1));
let expected_moves = [
builder.clone().to(Square::A2).build()?,
builder.clone().to(Square::B1).build()?,
builder.clone().to(Square::B2).build()?,
];
let mut generated_moves: HashSet<_> = generator.iter().collect();
for ex_move in expected_moves {
assert!(
generated_moves.remove(&ex_move),
"{:#?} was not generated",
&ex_move
);
}
assert!(
generated_moves.is_empty(),
"Moves unexpectedly present: {:#?}",
generated_moves
);
Ok(())
}
#[test]
fn black_king_in_check_by_rook() {
let pos = test_position!(Black, [
White King on E1,
White Rook on E4,
Black King on E7,
]);
assert!(pos.is_king_in_check());
let generator =
KingMoveGenerator::new(&pos.board, Color::Black, BitBoard::FULL, BitBoard::FULL);
let generated_moves = generator._test_bitboard();
let expected_moves = bitboard![F8 F7 F6 D6 D7 D8];
assert_eq!(generated_moves, expected_moves);
}
#[test]
fn white_king_unobstructed_castles() -> TestResult {
let pos = test_position!(
White King on E1,
White Rook on A1,
White Rook on H1,
);
assert!(pos.player_can_castle(Color::White, Castle::KingSide));
assert!(pos.player_can_castle(Color::White, Castle::QueenSide));
let generator =
KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let generated_moves: HashSet<Move> = generator.iter().collect();
assert!(generated_moves
.contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
assert!(generated_moves
.contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
Ok(())
}
#[test]
fn white_king_obstructed_queenside_castle() -> TestResult {
let pos = test_position!(
White King on E1,
White Knight on B1,
White Rook on A1,
White Rook on H1,
);
assert!(pos.player_can_castle(Color::White, Castle::KingSide));
assert!(!pos.player_can_castle(Color::White, Castle::QueenSide));
let generator =
KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let generated_moves: HashSet<Move> = generator.iter().collect();
assert!(generated_moves
.contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
assert!(!generated_moves
.contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
Ok(())
}
#[test]
fn white_king_obstructed_kingside_castle() -> TestResult {
let pos = test_position!(
White King on E1,
White Rook on A1,
White Knight on G1,
White Rook on H1,
);
assert!(!pos.player_can_castle(Color::White, Castle::KingSide));
assert!(pos.player_can_castle(Color::White, Castle::QueenSide));
let generator =
KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let generated_moves: HashSet<Move> = generator.iter().collect();
assert!(!generated_moves
.contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
assert!(generated_moves
.contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
Ok(())
}
}

View file

@ -1,68 +0,0 @@
// Eryn Wells <eryn@erynwells.me>
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use chessfriend_bitboard::BitBoard;
use chessfriend_board::Board;
use chessfriend_core::{PlacedPiece, Shape};
move_generator_declaration!(KnightMoveGenerator);
impl MoveGeneratorInternal for KnightMoveGenerator {
fn shape() -> Shape {
Shape::Knight
}
fn move_set_for_piece(
board: &Board,
placed_piece: &PlacedPiece,
capture_mask: BitBoard,
push_mask: BitBoard,
) -> MoveSet {
let opposing_pieces = board.bitboard_for_color(placed_piece.piece().color().other());
let empty_squares = board.empty_squares();
let knight_moves = BitBoard::knight_moves(placed_piece.square());
let quiet_moves = knight_moves & empty_squares & push_mask;
let capture_moves = knight_moves & opposing_pieces & capture_mask;
MoveSet::new(*placed_piece)
.quiet_moves(quiet_moves)
.capture_moves(capture_moves)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_move_list, position, testing::*};
use chessfriend_core::{piece, Color, Square};
use chessfriend_moves::Builder as MoveBuilder;
use std::collections::HashSet;
#[test]
fn one_knight() -> TestResult {
let pos = position![
White Knight on E4,
];
let generator =
KnightMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let generated_moves: HashSet<_> = generator.iter().collect();
let piece = piece!(White Knight on E4);
let expected_moves = HashSet::from_iter([
MoveBuilder::push(&piece).to(Square::C3).build()?,
MoveBuilder::push(&piece).to(Square::D2).build()?,
MoveBuilder::push(&piece).to(Square::F2).build()?,
MoveBuilder::push(&piece).to(Square::G3).build()?,
MoveBuilder::push(&piece).to(Square::C5).build()?,
MoveBuilder::push(&piece).to(Square::D6).build()?,
MoveBuilder::push(&piece).to(Square::G5).build()?,
MoveBuilder::push(&piece).to(Square::F6).build()?,
]);
assert_move_list!(generated_moves, expected_moves, pos);
Ok(())
}
}

View file

@ -1,191 +0,0 @@
// Eryn Wells <eryn@erynwells.me>
use chessfriend_bitboard::BitBoard;
use chessfriend_board::{castle::Castle, en_passant::EnPassant};
use chessfriend_core::{PlacedPiece, Square};
use chessfriend_moves::{Builder as MoveBuilder, Move};
/// A set of bitboards defining the moves for a single piece on the board.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct BitBoardSet {
quiet: BitBoard,
captures: BitBoard,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Special {
Pawn { en_passant: EnPassant },
King { castles: u8 },
}
/// A set of moves for a single piece on the board.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct MoveSet {
piece: PlacedPiece,
bitboards: BitBoardSet,
special: Option<Special>,
}
impl MoveSet {
pub(super) fn new(piece: PlacedPiece) -> MoveSet {
MoveSet {
piece,
bitboards: BitBoardSet::default(),
special: None,
}
}
pub(crate) fn can_move_to_square(&self, target_square: Square) -> bool {
match self.special {
Some(Special::King { castles }) => {
if self.check_castle_field(castles, Castle::KingSide)
&& target_square
== Castle::KingSide
.parameters(self.piece.color())
.king_target_square()
{
return true;
}
if self.check_castle_field(castles, Castle::KingSide)
&& target_square
== Castle::QueenSide
.parameters(self.piece.color())
.king_target_square()
{
return true;
}
}
Some(Special::Pawn { en_passant }) => {
if target_square == en_passant.target_square() {
return true;
}
}
None => {}
}
self.bitboard().contains(target_square)
}
pub(crate) fn can_castle(&self, castle: Castle) -> bool {
match self.special {
Some(Special::King { castles }) => self.check_castle_field(castles, castle),
_ => false,
}
}
fn check_castle_field(&self, castle_field: u8, castle: Castle) -> bool {
(castle_field & 1 << castle as u8) != 0
}
pub(super) fn quiet_moves(mut self, bitboard: BitBoard) -> MoveSet {
self.bitboards.quiet = bitboard;
self
}
pub(super) fn capture_moves(mut self, bitboard: BitBoard) -> MoveSet {
self.bitboards.captures = bitboard;
self
}
pub(super) fn kingside_castle(&mut self) -> &mut MoveSet {
match self.special {
Some(Special::King { ref mut castles }) => *castles |= 1 << Castle::KingSide as u8,
_ => {
self.special = Some(Special::King {
castles: 1 << Castle::KingSide as u8,
})
}
}
self
}
pub(super) fn queenside_castle(&mut self) -> &mut MoveSet {
match self.special {
Some(Special::King { ref mut castles }) => *castles |= 1 << Castle::QueenSide as u8,
_ => {
self.special = Some(Special::King {
castles: 1 << Castle::QueenSide as u8,
})
}
}
self
}
pub(super) fn en_passant(&mut self, en_passant: EnPassant) -> &mut MoveSet {
self.special = Some(Special::Pawn { en_passant });
self
}
/// A `BitBoard` representing all possible moves.
pub(super) fn bitboard(&self) -> BitBoard {
self.bitboards.captures | self.bitboards.quiet
}
pub(crate) fn moves(&self) -> impl Iterator<Item = Move> + '_ {
let piece = &self.piece;
let color = piece.color();
let is_pawn_on_starting_rank =
piece.is_pawn() && piece.square().rank().is_pawn_starting_rank(color);
self.bitboards
.quiet
.occupied_squares()
.filter_map(move |to_square| {
if is_pawn_on_starting_rank
&& to_square.rank().is_pawn_double_push_target_rank(color)
{
MoveBuilder::double_push(piece.square().file(), color)
.build()
.ok()
} else {
MoveBuilder::push(piece).to(to_square).build().ok()
}
})
.chain(
self.bitboards
.captures
.occupied_squares()
.filter_map(|to_square| {
MoveBuilder::push(piece)
.capturing_on(to_square)
.build()
.ok()
}),
)
.chain(self.castle_move(Castle::KingSide))
.chain(self.castle_move(Castle::QueenSide))
.chain(self.en_passant_move())
}
fn castle_move(&self, castle: Castle) -> Option<Move> {
match self.special {
Some(Special::King { castles }) => {
if (castles & 1 << castle as u8) != 0 {
Some(
MoveBuilder::castling(self.piece.color(), castle)
.build()
.ok()?,
)
} else {
None
}
}
_ => None,
}
}
fn en_passant_move(&self) -> Option<Move> {
match self.special {
Some(Special::Pawn { en_passant }) => Some(unsafe {
MoveBuilder::push(&self.piece)
.capturing_en_passant_on(en_passant.target_square())
.build_unchecked()
}),
_ => None,
}
}
}

View file

@ -1,353 +0,0 @@
// Eryn Wells <eryn@erynwells.me>
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use chessfriend_bitboard::BitBoard;
use chessfriend_board::{en_passant::EnPassant, Board};
use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square};
use chessfriend_moves::Move;
use std::collections::BTreeMap;
#[derive(Debug)]
struct MoveIterator(usize, usize);
#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) struct PawnMoveGenerator {
color: chessfriend_core::Color,
move_sets: BTreeMap<Square, MoveSet>,
en_passant_captures: Vec<Move>,
}
move_generator_declaration!(PawnMoveGenerator, getters);
impl MoveGeneratorInternal for PawnMoveGenerator {
fn shape() -> Shape {
Shape::Pawn
}
fn move_set_for_piece(
board: &Board,
placed_piece: &PlacedPiece,
capture_mask: BitBoard,
push_mask: BitBoard,
) -> MoveSet {
let capture_moves = Self::attacks(board, &placed_piece) & capture_mask;
let quiet_moves = Self::pushes(board, &placed_piece) & push_mask;
let mut move_set = MoveSet::new(*placed_piece)
.quiet_moves(quiet_moves)
.capture_moves(capture_moves);
if let Some(en_passant) = Self::en_passant(board, placed_piece, &push_mask, &capture_mask) {
move_set.en_passant(en_passant);
}
move_set
}
}
impl PawnMoveGenerator {
pub(super) fn new(
board: &Board,
player_to_move: Color,
capture_mask: BitBoard,
push_mask: BitBoard,
) -> Self {
let move_sets = if !(capture_mask.is_empty() && push_mask.is_empty()) {
Self::move_sets(board, player_to_move, capture_mask, push_mask)
} else {
std::collections::BTreeMap::new()
};
Self {
color: player_to_move,
move_sets,
en_passant_captures: Vec::new(),
}
}
fn move_sets(
board: &Board,
color: Color,
capture_mask: BitBoard,
push_mask: BitBoard,
) -> BTreeMap<Square, MoveSet> {
let piece = Self::piece(color);
let moves_for_pieces = BTreeMap::from_iter(
board
.bitboard_for_piece(piece)
.occupied_squares()
.map(|square| {
let piece = PlacedPiece::new(piece, square);
let move_set = Self::move_set_for_piece(board, &piece, capture_mask, push_mask);
(square, move_set)
}),
);
moves_for_pieces
}
fn pushes(board: &Board, piece: &PlacedPiece) -> BitBoard {
let color = piece.color();
let square = piece.square();
let bitboard: BitBoard = square.into();
let starting_rank = Rank::PAWN_STARTING_RANKS[color as usize];
let empty_squares = board.empty_squares();
match color {
Color::White => {
let mut moves = bitboard.shift_north_one() & empty_squares;
if !(bitboard & BitBoard::rank(starting_rank.as_index())).is_empty() {
moves |= moves.shift_north_one() & empty_squares;
}
moves
}
Color::Black => {
let mut moves = bitboard.shift_south_one() & empty_squares;
if !(bitboard & BitBoard::rank(starting_rank.as_index())).is_empty() {
moves |= moves.shift_south_one() & empty_squares;
}
moves
}
}
}
fn attacks(board: &Board, piece: &PlacedPiece) -> BitBoard {
let color = piece.color();
let opponent_pieces = board.bitboard_for_color(color.other());
BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces
}
fn en_passant(
board: &Board,
piece: &PlacedPiece,
push_mask: &BitBoard,
capture_mask: &BitBoard,
) -> Option<EnPassant> {
match board.en_passant() {
Some(en_passant) => {
let target_square: BitBoard = en_passant.target_square().into();
let capture_square: BitBoard = en_passant.capture_square().into();
if (target_square & push_mask).is_empty()
&& (capture_square & capture_mask).is_empty()
{
// Do not allow en passant if capturing would not either
// block an active check, or capture a checking pawn.
return None;
}
let capture = BitBoard::pawn_attacks(piece.square(), piece.color()) & target_square;
if capture.is_empty() {
return None;
}
match board.piece_on_square(en_passant.capture_square()) {
Some(_) => Some(en_passant),
None => None,
}
}
None => None,
}
}
#[cfg(none)]
fn does_en_passant_reveal_check(&self, position: &Position) -> bool {
let player_to_move = position.player_to_move();
let opposing_player = player_to_move.other();
if position.king_square(opposing_player).rank()
!= Rank::PAWN_DOUBLE_PUSH_TARGET_RANKS[player_to_move as usize]
{
return false;
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_move_list, formatted_move_list, test_position, testing::*};
use chessfriend_core::{piece, Color, Square};
use chessfriend_moves::{Builder as MoveBuilder, Move};
use std::collections::HashSet;
#[test]
fn one_double_push() -> TestResult {
let pos = test_position![White Pawn on E2];
let generator =
PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let pawn = piece!(White Pawn on E2);
let expected_moves = HashSet::from_iter([
MoveBuilder::push(&pawn).to(Square::E3).build()?,
MoveBuilder::double_push(pawn.square().file(), pawn.color()).build()?,
]);
let generated_moves: HashSet<_> = generator.iter().collect();
assert_move_list!(generated_moves, expected_moves, pos);
Ok(())
}
#[test]
fn one_single_push() -> TestResult {
let pos = test_position![White Pawn on E3];
let generator =
PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let generated_moves: HashSet<_> = generator.iter().collect();
let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E3))
.to(Square::E4)
.build()?]);
assert_move_list!(generated_moves, expected_moves, pos);
Ok(())
}
#[test]
fn one_obstructed_2square_push() -> TestResult {
let pos = test_position![
White Pawn on E2,
White Knight on E4,
];
let generator =
PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E2))
.to(Square::E3)
.build()?]);
let generated_moves: HashSet<_> = generator.iter().collect();
assert_move_list!(generated_moves, expected_moves, pos);
Ok(())
}
#[test]
fn one_obstructed_1square_push() {
let pos = test_position![
White Pawn on E2,
White Knight on E3,
];
let generator =
PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let generated_moves: HashSet<_> = generator.iter().collect();
let expected_moves: HashSet<_> = HashSet::new();
assert_move_list!(generated_moves, expected_moves, pos);
}
#[test]
fn one_attack() -> TestResult {
let pos = test_position![
White Pawn on E4,
White Bishop on E5,
Black Knight on D5,
];
let generator =
PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E4))
.capturing_on(Square::D5)
.build()?]);
let generated_moves: HashSet<_> = generator.iter().collect();
assert_move_list!(generated_moves, expected_moves, pos);
Ok(())
}
#[test]
fn one_double_attack() -> TestResult {
let pos = test_position![
White Pawn on E4,
White Bishop on E5,
Black Knight on D5,
Black Queen on F5,
];
let generator =
PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let builder = MoveBuilder::push(&piece!(White Pawn on E4));
let expected_moves = HashSet::from_iter([
builder.clone().capturing_on(Square::D5).build()?,
builder.clone().capturing_on(Square::F5).build()?,
]);
let generated_moves: HashSet<_> = generator.iter().collect();
assert_eq!(
generated_moves, expected_moves,
"generated: {:#?}\nexpected: {:#?}",
generated_moves, expected_moves
);
Ok(())
}
#[test]
fn one_en_passant_attack() -> TestResult {
let pos = test_position!(Black, [
White Pawn on D4,
Black Pawn on E4,
], D3);
let generator =
PawnMoveGenerator::new(&pos.board, Color::Black, BitBoard::FULL, BitBoard::FULL);
let generated_moves: HashSet<Move> = generator.iter().collect();
let builder = MoveBuilder::push(&piece!(Black Pawn on E4));
let expected_moves = HashSet::from_iter([
builder.capturing_en_passant_on(Square::D3).build()?,
builder.clone().to(Square::E3).build()?,
]);
assert_move_list!(generated_moves, expected_moves, pos);
Ok(())
}
/// Make sure the player cannot capture en passant if doing so would not resolve the check.
#[test]
fn cannot_capture_en_passant_while_in_check() -> TestResult {
let pos = test_position!(Black, [
Black King on B5,
Black Pawn on E4,
White Pawn on D4,
White Rook on B1,
], D3);
assert!(pos.is_king_in_check());
let generated_moves: HashSet<_> = pos.moves().iter().collect();
assert!(
!generated_moves.contains(
&MoveBuilder::push(&piece!(Black Pawn on E4))
.capturing_en_passant_on(Square::D3)
.build()?
),
"Valid moves: {:?}",
formatted_move_list!(generated_moves, pos)
);
Ok(())
}
}

View file

@ -1,154 +0,0 @@
// Eryn Wells <eryn@erynwells.me>
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use chessfriend_bitboard::BitBoard;
use chessfriend_board::Board;
use chessfriend_core::{Direction, PlacedPiece, Shape};
move_generator_declaration!(ClassicalMoveGenerator);
impl MoveGeneratorInternal for ClassicalMoveGenerator {
fn shape() -> Shape {
Shape::Queen
}
fn move_set_for_piece(
board: &Board,
placed_piece: &PlacedPiece,
capture_mask: BitBoard,
push_mask: BitBoard,
) -> MoveSet {
let piece = placed_piece.piece();
let color = piece.color();
let square = placed_piece.square();
let blockers = board.occupied_squares();
let empty_squares = !blockers;
let friendly_pieces = board.bitboard_for_color(color);
let opposing_pieces = board.bitboard_for_color(color.other());
let mut all_moves = BitBoard::empty();
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;
all_moves |= attack_ray;
} else {
all_moves |= 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 quiet_moves = all_moves & (empty_squares | !friendly_pieces) & push_mask;
let capture_moves = all_moves & opposing_pieces & capture_mask;
MoveSet::new(*placed_piece)
.quiet_moves(quiet_moves)
.capture_moves(capture_moves)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_position;
use chessfriend_bitboard::{bitboard, BitBoard};
use chessfriend_core::Color;
#[test]
fn classical_single_queen_bitboard() {
let pos = test_position![White Queen on B2];
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let bitboard = generator._test_bitboard();
let expected = bitboard![
A2 C2 D2 E2 F2 G2 H2 // Rank
B1 B3 B4 B5 B6 B7 B8 // File
A1 C3 D4 E5 F6 G7 H8 // Diagonal
C1 A3 // Anti-diagonal
];
assert_eq!(
bitboard, expected,
"actual:\n{}\nexpected:\n{}",
bitboard, expected
);
}
/// Test that a rook can move up to, but not onto, a friendly piece.
#[test]
fn classical_single_queen_with_same_color_blocker_bitboard() {
let pos = test_position![
White Queen on A1,
White Knight on E1,
];
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
let bitboard = generator._test_bitboard();
let expected = BitBoard::new(
0b10000001_01000001_00100001_00010001_00001001_00000101_00000011_00001110,
);
assert_eq!(
bitboard, expected,
"actual:\n{}\nexpected:\n{}",
bitboard, expected
);
}
/// Test that a rook can move up to, and then capture, an enemy piece.
#[test]
fn classical_single_queen_with_opposing_color_blocker_bitboard() {
let pos = test_position![
White Queen on B2,
Black Knight on E5,
];
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
assert_eq!(
generator._test_bitboard(),
bitboard![
A2 C2 D2 E2 F2 G2 H2 // Rank
B1 B3 B4 B5 B6 B7 B8 // File
A1 C3 D4 E5 // Diagonal
C1 A3 // Anti-diagonal
]
);
}
#[test]
fn classical_single_queen_in_center() {
let pos = test_position![White Queen on D3];
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
assert_eq!(
generator._test_bitboard(),
bitboard![
A3 B3 C3 E3 F3 G3 H3 // Rank
D1 D2 D4 D5 D6 D7 D8 // File
B1 C2 E4 F5 G6 H7 // Diagonal
F1 E2 C4 B5 A6 // Anti-diagonal
]
);
}
}

View file

@ -1,131 +0,0 @@
// Eryn Wells <eryn@erynwells.me>
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use chessfriend_bitboard::BitBoard;
use chessfriend_board::Board;
use chessfriend_core::{Direction, PlacedPiece, Shape};
move_generator_declaration!(ClassicalMoveGenerator);
impl MoveGeneratorInternal for ClassicalMoveGenerator {
fn shape() -> Shape {
Shape::Rook
}
fn move_set_for_piece(
board: &Board,
placed_piece: &PlacedPiece,
capture_mask: BitBoard,
push_mask: BitBoard,
) -> MoveSet {
let piece = placed_piece.piece();
let color = piece.color();
let square = placed_piece.square();
let blockers = board.occupied_squares();
let empty_squares = !blockers;
let friendly_pieces = board.bitboard_for_color(color);
let opposing_pieces = board.bitboard_for_color(color.other());
let mut all_moves = BitBoard::empty();
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;
all_moves |= attack_ray;
} else {
all_moves |= 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 quiet_moves = all_moves & (empty_squares | !friendly_pieces) & capture_mask;
let capture_moves = all_moves & opposing_pieces & push_mask;
MoveSet::new(*placed_piece)
.quiet_moves(quiet_moves)
.capture_moves(capture_moves)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_position;
use chessfriend_bitboard::{bitboard, BitBoard};
use chessfriend_core::Color;
#[test]
fn classical_single_rook_bitboard() {
let pos = test_position![White Rook on A2];
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
assert_eq!(
generator._test_bitboard(),
bitboard![A1 A3 A4 A5 A6 A7 A8 B2 C2 D2 E2 F2 G2 H2]
);
}
/// Test that a rook can move up to, but not onto, a friendly piece.
#[test]
fn classical_single_rook_with_same_color_blocker_bitboard() {
let pos = test_position![
White Rook on A1,
White Knight on E1,
];
println!("{}", pos.display());
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
assert_eq!(
generator._test_bitboard(),
BitBoard::new(
0b00000001_00000001_00000001_00000001_00000001_00000001_00000001_00001110
)
);
}
/// Test that a rook can move up to, and then capture, an enemy piece.
#[test]
fn classical_single_rook_with_opposing_color_blocker_bitboard() {
let pos = test_position![
White Rook on A1,
Black Knight on E1,
];
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
assert_eq!(
generator._test_bitboard(),
bitboard![A2 A3 A4 A5 A6 A7 A8 B1 C1 D1 E1]
);
}
#[test]
fn classical_single_rook_in_center() {
let pos = test_position![White Rook on D4];
let generator =
ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
assert_eq!(
generator._test_bitboard(),
bitboard![A4 B4 C4 E4 F4 G4 H4 D1 D2 D3 D5 D6 D7 D8]
);
}
}