[board] Implement bishop::ClassicalMoveGenerator

Implement a bishop 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:
Eryn Wells 2024-01-06 20:06:54 -08:00
parent 359bab9173
commit c6e799f722
3 changed files with 173 additions and 1 deletions

167
board/src/moves/bishop.rs Normal file
View file

@ -0,0 +1,167 @@
// Eryn Wells <eryn@erynwells.me>
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::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::{
bitboard::BitBoard,
piece::{Color, Piece},
position::DiagramFormatter,
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
);
}
}

View file

@ -1,5 +1,6 @@
// Eryn Wells <eryn@erynwells.me> // Eryn Wells <eryn@erynwells.me>
mod bishop;
mod king; mod king;
mod knight; mod knight;
mod r#move; mod r#move;

View file

@ -1,7 +1,8 @@
// Eryn Wells <eryn@erynwells.me> // Eryn Wells <eryn@erynwells.me>
use super::{ use super::{
king::KingMoveGenerator, knight::KnightMoveGenerator, pawn::PawnMoveGenerator, bishop::ClassicalMoveGenerator as BishopMoveGenerator, king::KingMoveGenerator,
knight::KnightMoveGenerator, pawn::PawnMoveGenerator,
rook::ClassicalMoveGenerator as RookMoveGenerator, Move, rook::ClassicalMoveGenerator as RookMoveGenerator, Move,
}; };
use crate::piece::Color; use crate::piece::Color;
@ -10,6 +11,7 @@ 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>,
bishop_moves: BishopMoveGenerator<'pos>,
rook_moves: RookMoveGenerator<'pos>, rook_moves: RookMoveGenerator<'pos>,
king_moves: KingMoveGenerator<'pos>, king_moves: KingMoveGenerator<'pos>,
} }
@ -19,6 +21,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),
bishop_moves: BishopMoveGenerator::new(position, color),
rook_moves: RookMoveGenerator::new(position, color), rook_moves: RookMoveGenerator::new(position, color),
king_moves: KingMoveGenerator::new(position, color), king_moves: KingMoveGenerator::new(position, color),
} }
@ -28,6 +31,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.bishop_moves.iter())
.chain(self.rook_moves.iter()) .chain(self.rook_moves.iter())
.chain(self.king_moves.iter()) .chain(self.king_moves.iter())
.cloned() .cloned()