[board] Implement rook::ClassicalMoveGenerator
Implement a rook move generator using the "classical" approach. This approach walks each ray to find blockers and then masks out friendly pieces. This is not efficient compared to other approaches, but it's much easier to implement.
This commit is contained in:
parent
6e483b412b
commit
359bab9173
4 changed files with 194 additions and 3 deletions
|
@ -6,6 +6,7 @@ mod r#move;
|
||||||
mod move_generator;
|
mod move_generator;
|
||||||
mod move_set;
|
mod move_set;
|
||||||
mod pawn;
|
mod pawn;
|
||||||
|
mod rook;
|
||||||
|
|
||||||
pub use move_generator::Moves;
|
pub use move_generator::Moves;
|
||||||
pub use r#move::Move;
|
pub use r#move::Move;
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use super::{king::KingMoveGenerator, knight::KnightMoveGenerator, pawn::PawnMoveGenerator, Move};
|
use super::{
|
||||||
|
king::KingMoveGenerator, knight::KnightMoveGenerator, pawn::PawnMoveGenerator,
|
||||||
|
rook::ClassicalMoveGenerator as RookMoveGenerator, Move,
|
||||||
|
};
|
||||||
use crate::piece::Color;
|
use crate::piece::Color;
|
||||||
use crate::Position;
|
use crate::Position;
|
||||||
|
|
||||||
pub struct Moves<'pos> {
|
pub struct Moves<'pos> {
|
||||||
pawn_moves: PawnMoveGenerator<'pos>,
|
pawn_moves: PawnMoveGenerator<'pos>,
|
||||||
knight_moves: KnightMoveGenerator<'pos>,
|
knight_moves: KnightMoveGenerator<'pos>,
|
||||||
|
rook_moves: RookMoveGenerator<'pos>,
|
||||||
king_moves: KingMoveGenerator<'pos>,
|
king_moves: KingMoveGenerator<'pos>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +19,7 @@ impl<'a> Moves<'a> {
|
||||||
Moves {
|
Moves {
|
||||||
pawn_moves: PawnMoveGenerator::new(position, color),
|
pawn_moves: PawnMoveGenerator::new(position, color),
|
||||||
knight_moves: KnightMoveGenerator::new(position, color),
|
knight_moves: KnightMoveGenerator::new(position, color),
|
||||||
|
rook_moves: RookMoveGenerator::new(position, color),
|
||||||
king_moves: KingMoveGenerator::new(position, color),
|
king_moves: KingMoveGenerator::new(position, color),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +28,7 @@ impl<'a> Moves<'a> {
|
||||||
self.pawn_moves
|
self.pawn_moves
|
||||||
.iter()
|
.iter()
|
||||||
.chain(self.knight_moves.iter())
|
.chain(self.knight_moves.iter())
|
||||||
|
.chain(self.rook_moves.iter())
|
||||||
.chain(self.king_moves.iter())
|
.chain(self.king_moves.iter())
|
||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
183
board/src/moves/rook.rs
Normal file
183
board/src/moves/rook.rs
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use std::io::empty;
|
||||||
|
|
||||||
|
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
||||||
|
use crate::{
|
||||||
|
bitboard::BitBoard,
|
||||||
|
piece::{Color, Piece, PlacedPiece},
|
||||||
|
square::Direction,
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Find the first occupied square along the northward ray. Do this by
|
||||||
|
// looking at the squares along that ray in trailing (south to north)
|
||||||
|
// order.
|
||||||
|
let ray = BitBoard::ray(square, Direction::North);
|
||||||
|
if let Some(first_occupied_square) = (ray & blockers).occupied_squares_trailing().next() {
|
||||||
|
let remainder = BitBoard::ray(first_occupied_square, Direction::North);
|
||||||
|
let attack_ray = ray & !remainder;
|
||||||
|
all_moves |= attack_ray;
|
||||||
|
} else {
|
||||||
|
all_moves |= ray;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ray = BitBoard::ray(square, Direction::East);
|
||||||
|
if let Some(first_occupied_square) = (ray & blockers).occupied_squares_trailing().next() {
|
||||||
|
let remainder = BitBoard::ray(first_occupied_square, Direction::East);
|
||||||
|
let attack_ray = ray & !remainder;
|
||||||
|
all_moves |= attack_ray;
|
||||||
|
} else {
|
||||||
|
all_moves |= ray;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ray = BitBoard::ray(square, Direction::South);
|
||||||
|
if let Some(first_occupied_square) = (ray & blockers).occupied_squares().next() {
|
||||||
|
let remainder = BitBoard::ray(first_occupied_square, Direction::South);
|
||||||
|
let attack_ray = ray & !remainder;
|
||||||
|
all_moves |= attack_ray;
|
||||||
|
} else {
|
||||||
|
all_moves |= ray;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ray = BitBoard::ray(square, Direction::West);
|
||||||
|
if let Some(first_occupied_square) = (ray & blockers).occupied_squares().next() {
|
||||||
|
let remainder = BitBoard::ray(first_occupied_square, Direction::West);
|
||||||
|
let attack_ray = ray & !remainder;
|
||||||
|
all_moves |= attack_ray;
|
||||||
|
} else {
|
||||||
|
all_moves |= ray;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::{
|
||||||
|
bitboard::BitBoard,
|
||||||
|
piece::{Color, Piece},
|
||||||
|
position::DiagramFormatter,
|
||||||
|
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
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -97,15 +97,16 @@ impl Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a BitBoard representing the set of squares containing a piece.
|
/// Return a BitBoard representing the set of squares containing a piece.
|
||||||
|
#[inline]
|
||||||
pub(crate) fn occupied_squares(&self) -> BitBoard {
|
pub(crate) fn occupied_squares(&self) -> BitBoard {
|
||||||
self.pieces_per_color[Color::White as usize] | self.pieces_per_color[Color::Black as usize]
|
self.pieces_per_color[Color::White as usize] | self.pieces_per_color[Color::Black as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a BitBoard representing the set of squares containing a piece.
|
/// Return a BitBoard representing the set of squares containing a piece.
|
||||||
/// This set is the inverse of `occupied_squares`.
|
/// This set is the inverse of `occupied_squares`.
|
||||||
|
#[inline]
|
||||||
pub(crate) fn empty_squares(&self) -> BitBoard {
|
pub(crate) fn empty_squares(&self) -> BitBoard {
|
||||||
!(self.pieces_per_color[Color::White as usize]
|
!self.occupied_squares()
|
||||||
| self.pieces_per_color[Color::Black as usize])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn bitboard_for_piece(&self, piece: Piece) -> BitBoard {
|
pub(crate) fn bitboard_for_piece(&self, piece: Piece) -> BitBoard {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue