[position] Remove dead move_generator code
This commit is contained in:
parent
942d9fe47b
commit
a4b713a558
8 changed files with 0 additions and 1441 deletions
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue