[board] Rename the moves modules → move_generator
Update the imports. Also update some references to crate symbols in move_generator macros to use $crate.
This commit is contained in:
parent
2d4ad70994
commit
ca9ff94d2a
11 changed files with 10 additions and 11 deletions
165
board/src/move_generator/bishop.rs
Normal file
165
board/src/move_generator/bishop.rs
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
||||
use crate::{
|
||||
piece::{Color, Piece, PlacedPiece},
|
||||
square::Direction,
|
||||
BitBoard, Move, Position,
|
||||
};
|
||||
|
||||
move_generator_declaration!(ClassicalMoveGenerator);
|
||||
|
||||
impl<'pos> MoveGeneratorInternal for ClassicalMoveGenerator<'pos> {
|
||||
fn piece(color: Color) -> Piece {
|
||||
Piece::bishop(color)
|
||||
}
|
||||
|
||||
fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
|
||||
let piece = placed_piece.piece();
|
||||
let color = piece.color();
|
||||
let square = placed_piece.square();
|
||||
|
||||
let blockers = position.occupied_squares();
|
||||
let empty_squares = !blockers;
|
||||
let friendly_pieces = position.bitboard_for_color(color);
|
||||
let opposing_pieces = position.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!(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_bb = all_moves & (empty_squares | !friendly_pieces);
|
||||
let capture_moves_bb = all_moves & opposing_pieces;
|
||||
|
||||
let map_to_move = |sq| Move::new(piece, square, sq);
|
||||
let quiet_moves = quiet_moves_bb.occupied_squares().map(map_to_move);
|
||||
let capture_moves = capture_moves_bb.occupied_squares().map(map_to_move);
|
||||
|
||||
MoveSet::new(placed_piece)
|
||||
.quiet_moves(quiet_moves_bb, quiet_moves)
|
||||
.capture_moves(capture_moves_bb, capture_moves)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
piece::{Color, Piece},
|
||||
position::DiagramFormatter,
|
||||
BitBoard, Position, Square,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn classical_single_bishop_bitboard() {
|
||||
let mut pos = Position::empty();
|
||||
[(Piece::bishop(Color::White), Square::A1)]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {:?} on {}", &p, &sq))
|
||||
});
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
assert_eq!(
|
||||
generator.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 mut pos = Position::empty();
|
||||
[
|
||||
(Piece::bishop(Color::White), Square::A1),
|
||||
(Piece::knight(Color::White), Square::E5),
|
||||
]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {} on {}", p, sq))
|
||||
});
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
assert_eq!(
|
||||
generator.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 mut pos = Position::empty();
|
||||
[
|
||||
(Piece::bishop(Color::White), Square::A1),
|
||||
(Piece::knight(Color::Black), Square::C3),
|
||||
]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {} on {}", p, sq))
|
||||
});
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
BitBoard::new(
|
||||
0b00000000_00000000_00000000_00000000_00000000_00000100_00000010_00000000
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classical_single_bishop_in_center() {
|
||||
let mut pos = Position::empty();
|
||||
[(Piece::bishop(Color::White), Square::E4)]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {} on {}", p, sq))
|
||||
});
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
let bitboard = generator.bitboard();
|
||||
let expected = BitBoard::new(
|
||||
0b00000001_10000010_01000100_00101000_00000000_00101000_01000100_10000010,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
bitboard, expected,
|
||||
"actual:\n{}\nexpected:\n{}",
|
||||
bitboard, expected
|
||||
);
|
||||
}
|
||||
}
|
||||
164
board/src/move_generator/king.rs
Normal file
164
board/src/move_generator/king.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// 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 crate::{
|
||||
piece::{Color, Piece, PlacedPiece},
|
||||
position::BoardSide,
|
||||
BitBoard, Move, Position,
|
||||
};
|
||||
|
||||
move_generator_declaration!(KingMoveGenerator, struct);
|
||||
move_generator_declaration!(KingMoveGenerator, new);
|
||||
move_generator_declaration!(KingMoveGenerator, getters);
|
||||
|
||||
impl<'a> KingMoveGenerator<'a> {
|
||||
#[allow(unused_variables)]
|
||||
fn king_side_castle(position: &Position, color: Color) -> Option<Move> {
|
||||
if !position.player_has_right_to_castle(color, BoardSide::King) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// TODO: Implement king side castle.
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn queen_side_castle(position: &Position, color: Color) -> Option<Move> {
|
||||
if !position.player_has_right_to_castle(color, BoardSide::Queen) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// TODO: Implement queen side castle.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'pos> MoveGeneratorInternal for KingMoveGenerator<'pos> {
|
||||
fn piece(color: Color) -> Piece {
|
||||
Piece::king(color)
|
||||
}
|
||||
|
||||
fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
|
||||
let piece = placed_piece.piece();
|
||||
let color = piece.color();
|
||||
let square = placed_piece.square();
|
||||
|
||||
let empty_squares = position.empty_squares();
|
||||
let opposing_pieces = position.bitboard_for_color(color.other());
|
||||
|
||||
let all_moves = BitBoard::king_moves(square);
|
||||
let quiet_moves_bb = all_moves & empty_squares;
|
||||
let capture_moves_bb = all_moves & opposing_pieces;
|
||||
|
||||
// TODO: Handle checks. Prevent moving a king to a square attacked by a
|
||||
// piece of the opposite color.
|
||||
|
||||
let map_to_move = |sq| Move::new(piece, square, sq);
|
||||
|
||||
let king_side_castle = Self::king_side_castle(position, color);
|
||||
let queen_side_castle = Self::queen_side_castle(position, color);
|
||||
let quiet_moves = quiet_moves_bb
|
||||
.occupied_squares()
|
||||
.map(map_to_move)
|
||||
.chain(king_side_castle.iter().cloned())
|
||||
.chain(queen_side_castle.iter().cloned());
|
||||
let capture_moves = capture_moves_bb.occupied_squares().map(map_to_move);
|
||||
|
||||
MoveSet::new(placed_piece)
|
||||
.quiet_moves(quiet_moves_bb, quiet_moves)
|
||||
.capture_moves(capture_moves_bb, capture_moves)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{piece::Piece, Position, Square};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn one_king() {
|
||||
let mut pos = Position::empty();
|
||||
pos.place_piece(Piece::king(Color::White), Square::E4)
|
||||
.expect("Failed to place king on e4");
|
||||
|
||||
let generator = KingMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let bb = generator.bitboard();
|
||||
assert_eq!(
|
||||
bb,
|
||||
BitBoard::new(
|
||||
0b00000000_00000000_00000000_00111000_00101000_00111000_00000000_00000000
|
||||
)
|
||||
);
|
||||
|
||||
let expected_moves = [
|
||||
Move::new(Piece::king(Color::White), Square::E4, Square::D5),
|
||||
Move::new(Piece::king(Color::White), Square::E4, Square::E5),
|
||||
Move::new(Piece::king(Color::White), Square::E4, Square::F5),
|
||||
Move::new(Piece::king(Color::White), Square::E4, Square::F4),
|
||||
Move::new(Piece::king(Color::White), Square::E4, Square::F3),
|
||||
Move::new(Piece::king(Color::White), Square::E4, Square::E3),
|
||||
Move::new(Piece::king(Color::White), Square::E4, Square::D3),
|
||||
Move::new(Piece::king(Color::White), Square::E4, Square::D4),
|
||||
];
|
||||
|
||||
let mut generated_moves: HashSet<Move> = generator.iter().cloned().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
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_king_corner() {
|
||||
let mut pos = Position::empty();
|
||||
pos.place_piece(Piece::king(Color::White), Square::A1)
|
||||
.expect("Failed to place king on a1");
|
||||
|
||||
let generator = KingMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let bb = generator.bitboard();
|
||||
assert_eq!(
|
||||
bb,
|
||||
BitBoard::new(
|
||||
0b00000000_00000000_00000000_00000000_00000000_00000000_00000011_00000010
|
||||
)
|
||||
);
|
||||
|
||||
let expected_moves = [
|
||||
Move::new(Piece::king(Color::White), Square::A1, Square::A2),
|
||||
Move::new(Piece::king(Color::White), Square::A1, Square::B1),
|
||||
Move::new(Piece::king(Color::White), Square::A1, Square::B2),
|
||||
];
|
||||
|
||||
let mut generated_moves: HashSet<Move> = generator.iter().cloned().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
|
||||
);
|
||||
}
|
||||
}
|
||||
89
board/src/move_generator/knight.rs
Normal file
89
board/src/move_generator/knight.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
||||
use crate::{
|
||||
piece::{Color, Piece, PlacedPiece},
|
||||
BitBoard, Move, Position,
|
||||
};
|
||||
|
||||
move_generator_declaration!(KnightMoveGenerator);
|
||||
|
||||
impl<'pos> MoveGeneratorInternal for KnightMoveGenerator<'pos> {
|
||||
fn piece(color: Color) -> Piece {
|
||||
Piece::knight(color)
|
||||
}
|
||||
|
||||
fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
|
||||
let opposing_pieces = position.bitboard_for_color(placed_piece.piece().color().other());
|
||||
let empty_squares = position.empty_squares();
|
||||
let knight_moves = BitBoard::knight_moves(placed_piece.square());
|
||||
|
||||
let quiet_moves_bb = knight_moves & empty_squares;
|
||||
let capture_moves_bb = knight_moves & opposing_pieces;
|
||||
|
||||
let quiet_moves = quiet_moves_bb
|
||||
.occupied_squares()
|
||||
.map(|to_sq| Move::new(placed_piece.piece(), placed_piece.square(), to_sq));
|
||||
let capture_moves = capture_moves_bb.occupied_squares().map(|to_sq| {
|
||||
let captured_piece = position.piece_on_square(to_sq).unwrap();
|
||||
Move::new(placed_piece.piece(), placed_piece.square(), to_sq).capturing(captured_piece)
|
||||
});
|
||||
|
||||
MoveSet::new(placed_piece)
|
||||
.quiet_moves(quiet_moves_bb, quiet_moves)
|
||||
.capture_moves(capture_moves_bb, capture_moves)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Square;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn one_knight() {
|
||||
let mut pos = Position::empty();
|
||||
pos.place_piece(Piece::knight(Color::White), Square::E4)
|
||||
.expect("Failed to place knight on e4");
|
||||
|
||||
let generator = KnightMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
/*
|
||||
let bb = generator.bitboard();
|
||||
assert_eq!(
|
||||
bb,
|
||||
BitBoard::new(
|
||||
0b00000000_00000000_00000000_00111000_00101000_00111000_00000000_00000000
|
||||
)
|
||||
);
|
||||
*/
|
||||
|
||||
let expected_moves = [
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::C3),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::D2),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::F2),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::G3),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::C5),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::D6),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::G5),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::F6),
|
||||
];
|
||||
|
||||
let mut generated_moves: HashSet<Move> = generator.iter().cloned().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
|
||||
);
|
||||
}
|
||||
}
|
||||
89
board/src/move_generator/mod.rs
Normal file
89
board/src/move_generator/mod.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
mod bishop;
|
||||
mod king;
|
||||
mod knight;
|
||||
mod move_generator;
|
||||
mod move_set;
|
||||
mod pawn;
|
||||
mod queen;
|
||||
mod rook;
|
||||
|
||||
pub use move_generator::Moves;
|
||||
|
||||
pub(self) use move_set::MoveSet;
|
||||
|
||||
use crate::{
|
||||
piece::{Color, Piece, PlacedPiece},
|
||||
Move, Position, Square,
|
||||
};
|
||||
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) => {
|
||||
pub(super) struct $name<'pos> {
|
||||
position: &'pos $crate::Position,
|
||||
color: $crate::piece::Color,
|
||||
move_sets: std::collections::BTreeMap<$crate::Square, $crate::move_generator::MoveSet>,
|
||||
}
|
||||
};
|
||||
($name:ident, new) => {
|
||||
impl<'pos> $name<'pos> {
|
||||
pub(super) fn new(position: &$crate::Position, color: $crate::piece::Color) -> $name {
|
||||
$name {
|
||||
position,
|
||||
color,
|
||||
move_sets: Self::move_sets(position, color),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
($name:ident, getters) => {
|
||||
impl<'pos> $name<'pos> {
|
||||
pub(super) fn iter(&self) -> impl Iterator<Item = &$crate::Move> + '_ {
|
||||
self.move_sets.values().map(|set| set.moves()).flatten()
|
||||
}
|
||||
|
||||
fn bitboard(&self) -> $crate::BitBoard {
|
||||
self.move_sets
|
||||
.values()
|
||||
.fold($crate::BitBoard::empty(), |partial, mv_set| {
|
||||
partial | mv_set.bitboard()
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(self) use move_generator_declaration;
|
||||
|
||||
trait MoveGeneratorInternal {
|
||||
fn piece(color: Color) -> Piece;
|
||||
|
||||
fn move_sets(position: &Position, color: Color) -> BTreeMap<Square, MoveSet> {
|
||||
let piece = Self::piece(color);
|
||||
BTreeMap::from_iter(
|
||||
position
|
||||
.bitboard_for_piece(piece)
|
||||
.occupied_squares()
|
||||
.map(|sq| {
|
||||
let placed_piece = PlacedPiece::new(piece, sq);
|
||||
let move_set = Self::move_set_for_piece(position, placed_piece);
|
||||
(sq, move_set)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet;
|
||||
}
|
||||
42
board/src/move_generator/move_generator.rs
Normal file
42
board/src/move_generator/move_generator.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use super::{
|
||||
bishop::ClassicalMoveGenerator as BishopMoveGenerator, king::KingMoveGenerator,
|
||||
knight::KnightMoveGenerator, pawn::PawnMoveGenerator,
|
||||
queen::ClassicalMoveGenerator as QueenMoveGenerator,
|
||||
rook::ClassicalMoveGenerator as RookMoveGenerator,
|
||||
};
|
||||
use crate::{piece::Color, Move, Position};
|
||||
|
||||
pub struct Moves<'pos> {
|
||||
pawn_moves: PawnMoveGenerator<'pos>,
|
||||
knight_moves: KnightMoveGenerator<'pos>,
|
||||
bishop_moves: BishopMoveGenerator<'pos>,
|
||||
rook_moves: RookMoveGenerator<'pos>,
|
||||
queen_moves: QueenMoveGenerator<'pos>,
|
||||
king_moves: KingMoveGenerator<'pos>,
|
||||
}
|
||||
|
||||
impl<'a> Moves<'a> {
|
||||
pub fn new(position: &Position, color: Color) -> Moves {
|
||||
Moves {
|
||||
pawn_moves: PawnMoveGenerator::new(position, color),
|
||||
knight_moves: KnightMoveGenerator::new(position, color),
|
||||
bishop_moves: BishopMoveGenerator::new(position, color),
|
||||
rook_moves: RookMoveGenerator::new(position, color),
|
||||
queen_moves: QueenMoveGenerator::new(position, color),
|
||||
king_moves: KingMoveGenerator::new(position, color),
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
68
board/src/move_generator/move_set.rs
Normal file
68
board/src/move_generator/move_set.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use crate::{piece::PlacedPiece, BitBoard, Move};
|
||||
|
||||
struct BitBoardSet {
|
||||
quiet: BitBoard,
|
||||
captures: BitBoard,
|
||||
}
|
||||
|
||||
struct MoveListSet {
|
||||
quiet: Vec<Move>,
|
||||
captures: Vec<Move>,
|
||||
}
|
||||
|
||||
/// A set of moves for a piece on the board.
|
||||
pub(super) struct MoveSet {
|
||||
piece: PlacedPiece,
|
||||
bitboards: BitBoardSet,
|
||||
move_lists: MoveListSet,
|
||||
}
|
||||
|
||||
impl MoveSet {
|
||||
pub(super) fn new(piece: PlacedPiece) -> MoveSet {
|
||||
MoveSet {
|
||||
piece,
|
||||
bitboards: BitBoardSet {
|
||||
quiet: BitBoard::empty(),
|
||||
captures: BitBoard::empty(),
|
||||
},
|
||||
move_lists: MoveListSet {
|
||||
quiet: Vec::new(),
|
||||
captures: Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn quiet_moves(
|
||||
mut self,
|
||||
bitboard: BitBoard,
|
||||
move_list: impl Iterator<Item = Move>,
|
||||
) -> MoveSet {
|
||||
self.bitboards.quiet = bitboard;
|
||||
self.move_lists.quiet = move_list.collect();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub(super) fn capture_moves(
|
||||
mut self,
|
||||
bitboard: BitBoard,
|
||||
move_list: impl Iterator<Item = Move>,
|
||||
) -> MoveSet {
|
||||
self.bitboards.captures = bitboard;
|
||||
self.move_lists.captures = move_list.collect();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Return a BitBoard representing all possible moves.
|
||||
pub(super) fn bitboard(&self) -> BitBoard {
|
||||
self.bitboards.captures | self.bitboards.quiet
|
||||
}
|
||||
|
||||
pub(super) fn moves(&self) -> impl Iterator<Item = &Move> {
|
||||
self.move_lists
|
||||
.captures
|
||||
.iter()
|
||||
.chain(self.move_lists.quiet.iter())
|
||||
}
|
||||
}
|
||||
392
board/src/move_generator/pawn.rs
Normal file
392
board/src/move_generator/pawn.rs
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::{
|
||||
piece::{Color, Piece, Shape},
|
||||
BitBoard, Move, Position,
|
||||
};
|
||||
|
||||
enum MoveList {
|
||||
Quiet = 0,
|
||||
Promotions = 1,
|
||||
Captures = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MoveIterator(usize, usize);
|
||||
|
||||
struct MoveGenerationParameters {
|
||||
starting_rank: BitBoard,
|
||||
promotion_rank: BitBoard,
|
||||
push_shift: fn(BitBoard) -> BitBoard,
|
||||
left_capture_shift: fn(BitBoard) -> BitBoard,
|
||||
right_capture_shift: fn(BitBoard) -> BitBoard,
|
||||
}
|
||||
|
||||
pub(super) struct PawnMoveGenerator<'pos> {
|
||||
color: Color,
|
||||
position: &'pos Position,
|
||||
|
||||
did_populate_move_lists: bool,
|
||||
|
||||
pushes: BitBoard,
|
||||
attacks: BitBoard,
|
||||
|
||||
move_lists: [Vec<Move>; 3],
|
||||
move_iterator: MoveIterator,
|
||||
}
|
||||
|
||||
impl<'pos> PawnMoveGenerator<'pos> {
|
||||
pub(super) fn new(position: &Position, color: Color) -> PawnMoveGenerator {
|
||||
PawnMoveGenerator {
|
||||
position,
|
||||
color,
|
||||
did_populate_move_lists: false,
|
||||
pushes: BitBoard::empty(),
|
||||
attacks: BitBoard::empty(),
|
||||
move_lists: [Vec::new(), Vec::new(), Vec::new()],
|
||||
move_iterator: MoveIterator(0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn iter(&self) -> impl Iterator<Item = &Move> + '_ {
|
||||
self.move_lists.iter().flatten()
|
||||
}
|
||||
|
||||
fn generate_moves(&mut self) {
|
||||
self.generate_move_bitboards();
|
||||
self.populate_move_lists();
|
||||
}
|
||||
|
||||
fn move_generation_parameters(&self) -> MoveGenerationParameters {
|
||||
match self.color {
|
||||
Color::White => MoveGenerationParameters {
|
||||
starting_rank: BitBoard::rank(1),
|
||||
promotion_rank: BitBoard::rank(7),
|
||||
push_shift: BitBoard::shift_north_one,
|
||||
left_capture_shift: BitBoard::shift_north_west_one,
|
||||
right_capture_shift: BitBoard::shift_north_east_one,
|
||||
},
|
||||
Color::Black => MoveGenerationParameters {
|
||||
starting_rank: BitBoard::rank(6),
|
||||
promotion_rank: BitBoard::rank(0),
|
||||
push_shift: BitBoard::shift_south_one,
|
||||
left_capture_shift: BitBoard::shift_south_east_one,
|
||||
right_capture_shift: BitBoard::shift_south_west_one,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn quiet_move_list(&mut self) -> &mut Vec<Move> {
|
||||
&mut self.move_lists[MoveList::Quiet as usize]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn promotion_move_list(&mut self) -> &mut Vec<Move> {
|
||||
&mut self.move_lists[MoveList::Promotions as usize]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn capture_move_list(&mut self) -> &mut Vec<Move> {
|
||||
&mut self.move_lists[MoveList::Captures as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'pos> PawnMoveGenerator<'pos> {
|
||||
fn generate_move_bitboards(&mut self) {
|
||||
let parameters = self.move_generation_parameters();
|
||||
self.generate_pushes_bitboard(¶meters);
|
||||
self.generate_attacks_bitboard(¶meters);
|
||||
}
|
||||
|
||||
fn generate_pushes_bitboard(&mut self, parameters: &MoveGenerationParameters) {
|
||||
let empty_squares = self.position.empty_squares();
|
||||
let bb = self.position.bitboard_for_piece(Piece::pawn(self.color));
|
||||
|
||||
let legal_1square_pushes = (parameters.push_shift)(bb) & empty_squares;
|
||||
let legal_2square_pushes =
|
||||
(parameters.push_shift)(legal_1square_pushes & BitBoard::rank(2)) & empty_squares;
|
||||
|
||||
self.pushes = legal_1square_pushes | legal_2square_pushes;
|
||||
}
|
||||
|
||||
fn generate_attacks_bitboard(&mut self, parameters: &MoveGenerationParameters) {
|
||||
let opponent_pieces = self.position.bitboard_for_color(self.color.other());
|
||||
let bb = self.position.bitboard_for_piece(Piece::pawn(Color::White));
|
||||
|
||||
self.attacks = ((parameters.left_capture_shift)(bb) | (parameters.right_capture_shift)(bb))
|
||||
& opponent_pieces;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
if let Some(en_passant) = self.en_passant() {
|
||||
// TODO: Add en passant move to the attacks bitboard.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'pos> PawnMoveGenerator<'pos> {
|
||||
fn populate_move_lists(&mut self) {
|
||||
let parameters = self.move_generation_parameters();
|
||||
|
||||
self._populate_move_lists(¶meters);
|
||||
self.did_populate_move_lists = true;
|
||||
}
|
||||
|
||||
fn _populate_move_lists(&mut self, parameters: &MoveGenerationParameters) {
|
||||
let piece = Piece::pawn(self.color);
|
||||
|
||||
let bb = self.position.bitboard_for_piece(piece);
|
||||
if bb.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let empty_squares = self.position.empty_squares();
|
||||
let black_pieces = self.position.bitboard_for_color(self.color.other());
|
||||
|
||||
for from_sq in bb.occupied_squares() {
|
||||
let pawn: BitBoard = from_sq.into();
|
||||
|
||||
let push = (parameters.push_shift)(pawn);
|
||||
if !(push & empty_squares).is_empty() {
|
||||
let to_sq = push.occupied_squares().next().unwrap();
|
||||
|
||||
let r#move = Move::new(piece, from_sq, to_sq);
|
||||
if !(push & parameters.promotion_rank).is_empty() {
|
||||
for shape in Shape::promotable() {
|
||||
self.promotion_move_list()
|
||||
.push(r#move.clone().promoting_to(*shape));
|
||||
}
|
||||
} else {
|
||||
self.quiet_move_list().push(r#move);
|
||||
}
|
||||
|
||||
if !(pawn & parameters.starting_rank).is_empty() {
|
||||
let push = (parameters.push_shift)(push);
|
||||
if !(push & empty_squares).is_empty() {
|
||||
let to_sq = push.occupied_squares().next().unwrap();
|
||||
self.quiet_move_list()
|
||||
.push(Move::new(piece, from_sq, to_sq));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for attack in [
|
||||
(parameters.left_capture_shift)(pawn),
|
||||
(parameters.right_capture_shift)(pawn),
|
||||
] {
|
||||
if !(attack & black_pieces).is_empty() {
|
||||
let to_sq = attack.occupied_squares().next().unwrap();
|
||||
let captured_piece = self.position.piece_on_square(to_sq).unwrap();
|
||||
|
||||
let r#move = Move::new(piece, from_sq, to_sq).capturing(captured_piece);
|
||||
if !(attack & parameters.promotion_rank).is_empty() {
|
||||
for shape in Shape::promotable() {
|
||||
self.capture_move_list()
|
||||
.push(r#move.clone().promoting_to(*shape));
|
||||
}
|
||||
} else {
|
||||
self.capture_move_list().push(r#move);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(en_passant) = self.en_passant() {
|
||||
self.capture_move_list().push(en_passant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn en_passant(&self) -> Option<Move> {
|
||||
// TODO: En passant. I think the way to do this is to have the position mark
|
||||
// an en passant square when the conditions are correct, i.e. when the
|
||||
// opposing player has pushed a pawn two squares off the initial rank, and
|
||||
// then check in these routines if a pawn of this color attacks that square.
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'pos> Iterator for PawnMoveGenerator<'pos> {
|
||||
type Item = Move;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if !self.did_populate_move_lists {
|
||||
self.generate_moves();
|
||||
}
|
||||
|
||||
let iter = &mut self.move_iterator;
|
||||
|
||||
// Find the next non-empty list.
|
||||
loop {
|
||||
if iter.0 >= self.move_lists.len() || !self.move_lists[iter.0].is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
iter.0 += 1;
|
||||
}
|
||||
|
||||
if let Some(move_list) = self.move_lists.get(iter.0) {
|
||||
let next_move = move_list[iter.1].clone();
|
||||
|
||||
iter.1 += 1;
|
||||
if iter.1 >= move_list.len() {
|
||||
// Increment the list index here. On the next iteration, find the next non-empty list.
|
||||
iter.0 += 1;
|
||||
iter.1 = 0;
|
||||
}
|
||||
|
||||
Some(next_move)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::piece::PlacedPiece;
|
||||
use crate::position::DiagramFormatter;
|
||||
use crate::{Position, Square};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn one_2square_push() {
|
||||
let mut pos = Position::empty();
|
||||
|
||||
pos.place_piece(Piece::pawn(Color::White), Square::E2)
|
||||
.expect("Failed to place pawn on e2");
|
||||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let expected_moves = HashSet::from_iter(
|
||||
[
|
||||
Move::new(Piece::pawn(Color::White), Square::E2, Square::E3),
|
||||
Move::new(Piece::pawn(Color::White), Square::E2, Square::E4),
|
||||
]
|
||||
.into_iter(),
|
||||
);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.collect();
|
||||
|
||||
assert_eq!(generated_moves, expected_moves);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_1square_push() {
|
||||
let mut pos = Position::empty();
|
||||
|
||||
pos.place_piece(Piece::pawn(Color::White), Square::E3)
|
||||
.expect("Failed to place pawn on e3");
|
||||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let expected_moves = HashSet::from_iter(
|
||||
[Move::new(Piece::pawn(Color::White), Square::E3, Square::E4)].into_iter(),
|
||||
);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.collect();
|
||||
|
||||
assert_eq!(generated_moves, expected_moves);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_obstructed_2square_push() {
|
||||
let mut pos = Position::empty();
|
||||
|
||||
pos.place_piece(Piece::pawn(Color::White), Square::E2)
|
||||
.expect("Failed to place pawn on e2");
|
||||
pos.place_piece(Piece::knight(Color::White), Square::E4)
|
||||
.expect("Failed to place knight on e4");
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let expected_moves = HashSet::from_iter(
|
||||
[Move::new(Piece::pawn(Color::White), Square::E2, Square::E3)].into_iter(),
|
||||
);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.collect();
|
||||
|
||||
assert_eq!(generated_moves, expected_moves);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_obstructed_1square_push() {
|
||||
let mut pos = Position::empty();
|
||||
|
||||
pos.place_piece(Piece::pawn(Color::White), Square::E2)
|
||||
.expect("Failed to place pawn on e2");
|
||||
pos.place_piece(Piece::knight(Color::White), Square::E3)
|
||||
.expect("Failed to place knight on e4");
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.collect();
|
||||
|
||||
assert_eq!(generated_moves, HashSet::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_attack() {
|
||||
let mut pos = Position::empty();
|
||||
pos.place_piece(Piece::pawn(Color::White), Square::E4)
|
||||
.expect("Failed to place pawn on e4");
|
||||
pos.place_piece(Piece::bishop(Color::White), Square::E5)
|
||||
.expect("Failed to place pawn on e4");
|
||||
pos.place_piece(Piece::knight(Color::Black), Square::D5)
|
||||
.expect("Failed to place knight on d5");
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let expected_moves = HashSet::from_iter(
|
||||
[Move::new(Piece::pawn(Color::White), Square::E4, Square::D5)
|
||||
.capturing(PlacedPiece::new(Piece::knight(Color::Black), Square::D5))]
|
||||
.into_iter(),
|
||||
);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.collect();
|
||||
|
||||
assert_eq!(generated_moves, expected_moves);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_double_attack() {
|
||||
let mut pos = Position::empty();
|
||||
pos.place_piece(Piece::pawn(Color::White), Square::E4)
|
||||
.expect("Failed to place pawn on e4");
|
||||
pos.place_piece(Piece::bishop(Color::White), Square::E5)
|
||||
.expect("Failed to place pawn on e4");
|
||||
pos.place_piece(Piece::knight(Color::Black), Square::D5)
|
||||
.expect("Failed to place knight on d5");
|
||||
pos.place_piece(Piece::queen(Color::Black), Square::F5)
|
||||
.expect("Failed to place knight on f5");
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let expected_moves = HashSet::from_iter(
|
||||
[
|
||||
Move::new(Piece::pawn(Color::White), Square::E4, Square::D5)
|
||||
.capturing(PlacedPiece::new(Piece::knight(Color::Black), Square::D5)),
|
||||
Move::new(Piece::pawn(Color::White), Square::E4, Square::F5)
|
||||
.capturing(PlacedPiece::new(Piece::queen(Color::Black), Square::F5)),
|
||||
]
|
||||
.into_iter(),
|
||||
);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.collect();
|
||||
|
||||
assert_eq!(
|
||||
generated_moves, expected_moves,
|
||||
"generated: {:#?}\nexpected: {:#?}",
|
||||
generated_moves, expected_moves
|
||||
);
|
||||
}
|
||||
}
|
||||
170
board/src/move_generator/queen.rs
Normal file
170
board/src/move_generator/queen.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
||||
use crate::{
|
||||
piece::{Color, Piece, PlacedPiece},
|
||||
square::Direction,
|
||||
BitBoard, Move, Position,
|
||||
};
|
||||
|
||||
move_generator_declaration!(ClassicalMoveGenerator);
|
||||
|
||||
impl<'pos> MoveGeneratorInternal for ClassicalMoveGenerator<'pos> {
|
||||
fn piece(color: Color) -> Piece {
|
||||
Piece::queen(color)
|
||||
}
|
||||
|
||||
fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
|
||||
let piece = placed_piece.piece();
|
||||
let color = piece.color();
|
||||
let square = placed_piece.square();
|
||||
|
||||
let blockers = position.occupied_squares();
|
||||
let empty_squares = !blockers;
|
||||
let friendly_pieces = position.bitboard_for_color(color);
|
||||
let opposing_pieces = position.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_bb = all_moves & (empty_squares | !friendly_pieces);
|
||||
let capture_moves_bb = all_moves & opposing_pieces;
|
||||
|
||||
let map_to_move = |sq| Move::new(piece, square, sq);
|
||||
let quiet_moves = quiet_moves_bb.occupied_squares().map(map_to_move);
|
||||
let capture_moves = capture_moves_bb.occupied_squares().map(map_to_move);
|
||||
|
||||
MoveSet::new(placed_piece)
|
||||
.quiet_moves(quiet_moves_bb, quiet_moves)
|
||||
.capture_moves(capture_moves_bb, capture_moves)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
piece::{Color, Piece},
|
||||
position::DiagramFormatter,
|
||||
BitBoard, Position, Square,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn classical_single_queen_bitboard() {
|
||||
let mut pos = Position::empty();
|
||||
[(Piece::queen(Color::White), Square::A1)]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {:?} on {}", &p, &sq))
|
||||
});
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
let bitboard = generator.bitboard();
|
||||
let expected = BitBoard::new(
|
||||
0b10000001_01000001_00100001_00010001_00001001_00000101_00000011_11111110,
|
||||
);
|
||||
|
||||
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 mut pos = Position::empty();
|
||||
[
|
||||
(Piece::queen(Color::White), Square::A1),
|
||||
(Piece::knight(Color::White), Square::E1),
|
||||
]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {} on {}", p, sq))
|
||||
});
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
let bitboard = generator.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 mut pos = Position::empty();
|
||||
[
|
||||
(Piece::queen(Color::White), Square::A1),
|
||||
(Piece::knight(Color::Black), Square::E5),
|
||||
]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {} on {}", p, sq))
|
||||
});
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
BitBoard::new(
|
||||
0b00000001_00000001_00000001_00010001_00001001_00000101_00000011_11111110,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classical_single_queen_in_center() {
|
||||
let mut pos = Position::empty();
|
||||
[(Piece::queen(Color::White), Square::E4)]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {} on {}", p, sq))
|
||||
});
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
BitBoard::new(
|
||||
0b00010001_10010010_01010100_00111000_11101111_00111000_01010100_10010010
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
160
board/src/move_generator/rook.rs
Normal file
160
board/src/move_generator/rook.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
||||
use crate::{
|
||||
piece::{Color, Piece, PlacedPiece},
|
||||
square::Direction,
|
||||
BitBoard, Move, Position,
|
||||
};
|
||||
|
||||
move_generator_declaration!(ClassicalMoveGenerator);
|
||||
|
||||
impl<'pos> MoveGeneratorInternal for ClassicalMoveGenerator<'pos> {
|
||||
fn piece(color: Color) -> Piece {
|
||||
Piece::rook(color)
|
||||
}
|
||||
|
||||
fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
|
||||
let piece = placed_piece.piece();
|
||||
let color = piece.color();
|
||||
let square = placed_piece.square();
|
||||
|
||||
let blockers = position.occupied_squares();
|
||||
let empty_squares = !blockers;
|
||||
let friendly_pieces = position.bitboard_for_color(color);
|
||||
let opposing_pieces = position.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_bb = all_moves & (empty_squares | !friendly_pieces);
|
||||
let capture_moves_bb = all_moves & opposing_pieces;
|
||||
|
||||
let map_to_move = |sq| Move::new(piece, square, sq);
|
||||
let quiet_moves = quiet_moves_bb.occupied_squares().map(map_to_move);
|
||||
let capture_moves = capture_moves_bb.occupied_squares().map(map_to_move);
|
||||
|
||||
MoveSet::new(placed_piece)
|
||||
.quiet_moves(quiet_moves_bb, quiet_moves)
|
||||
.capture_moves(capture_moves_bb, capture_moves)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
piece::{Color, Piece},
|
||||
position::DiagramFormatter,
|
||||
BitBoard, Position, Square,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn classical_single_rook_bitboard() {
|
||||
let mut pos = Position::empty();
|
||||
[(Piece::rook(Color::White), Square::A1)]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {:?} on {}", &p, &sq))
|
||||
});
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
BitBoard::new(
|
||||
0b00000001_00000001_00000001_00000001_00000001_00000001_00000001_11111110
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// 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 mut pos = Position::empty();
|
||||
[
|
||||
(Piece::rook(Color::White), Square::A1),
|
||||
(Piece::knight(Color::White), Square::E1),
|
||||
]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {} on {}", p, sq))
|
||||
});
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
assert_eq!(
|
||||
generator.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 mut pos = Position::empty();
|
||||
[
|
||||
(Piece::rook(Color::White), Square::A1),
|
||||
(Piece::knight(Color::Black), Square::E1),
|
||||
]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {} on {}", p, sq))
|
||||
});
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
BitBoard::new(
|
||||
0b00000001_00000001_00000001_00000001_00000001_00000001_00000001_00011110
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classical_single_rook_in_center() {
|
||||
let mut pos = Position::empty();
|
||||
[(Piece::rook(Color::White), Square::E4)]
|
||||
.into_iter()
|
||||
.for_each(|(p, sq)| {
|
||||
pos.place_piece(p, sq)
|
||||
.expect(&format!("Unable to place {} on {}", p, sq))
|
||||
});
|
||||
|
||||
let generator = ClassicalMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
BitBoard::new(
|
||||
0b00010000_00010000_00010000_00010000_11101111_00010000_00010000_00010000
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue