From edf7d1cd2f54713960ae2da15f2286ab4684e866 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 6 Jan 2024 20:33:30 -0800 Subject: [PATCH] [board] Implement queen::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. --- board/src/moves/mod.rs | 1 + board/src/moves/move_generator.rs | 4 + board/src/moves/queen.rs | 172 ++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 board/src/moves/queen.rs diff --git a/board/src/moves/mod.rs b/board/src/moves/mod.rs index b13ae12..f847db5 100644 --- a/board/src/moves/mod.rs +++ b/board/src/moves/mod.rs @@ -7,6 +7,7 @@ mod r#move; mod move_generator; mod move_set; mod pawn; +mod queen; mod rook; pub use move_generator::Moves; diff --git a/board/src/moves/move_generator.rs b/board/src/moves/move_generator.rs index 62cb5ea..b4962db 100644 --- a/board/src/moves/move_generator.rs +++ b/board/src/moves/move_generator.rs @@ -3,6 +3,7 @@ use super::{ bishop::ClassicalMoveGenerator as BishopMoveGenerator, king::KingMoveGenerator, knight::KnightMoveGenerator, pawn::PawnMoveGenerator, + queen::ClassicalMoveGenerator as QueenMoveGenerator, rook::ClassicalMoveGenerator as RookMoveGenerator, Move, }; use crate::piece::Color; @@ -13,6 +14,7 @@ pub struct Moves<'pos> { knight_moves: KnightMoveGenerator<'pos>, bishop_moves: BishopMoveGenerator<'pos>, rook_moves: RookMoveGenerator<'pos>, + queen_moves: QueenMoveGenerator<'pos>, king_moves: KingMoveGenerator<'pos>, } @@ -23,6 +25,7 @@ impl<'a> Moves<'a> { 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), } } @@ -33,6 +36,7 @@ impl<'a> Moves<'a> { .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() } diff --git a/board/src/moves/queen.rs b/board/src/moves/queen.rs new file mode 100644 index 0000000..770a9ae --- /dev/null +++ b/board/src/moves/queen.rs @@ -0,0 +1,172 @@ +// Eryn Wells + +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::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::{ + bitboard::BitBoard, + piece::{Color, Piece}, + position::DiagramFormatter, + 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 + ) + ); + } +}