diff --git a/board/src/moves/mod.rs b/board/src/moves/mod.rs index fd22516..75db67c 100644 --- a/board/src/moves/mod.rs +++ b/board/src/moves/mod.rs @@ -6,6 +6,7 @@ mod r#move; mod move_generator; mod move_set; mod pawn; +mod rook; pub use move_generator::Moves; pub use r#move::Move; diff --git a/board/src/moves/move_generator.rs b/board/src/moves/move_generator.rs index a67220e..6816b93 100644 --- a/board/src/moves/move_generator.rs +++ b/board/src/moves/move_generator.rs @@ -1,12 +1,16 @@ // Eryn Wells -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::Position; pub struct Moves<'pos> { pawn_moves: PawnMoveGenerator<'pos>, knight_moves: KnightMoveGenerator<'pos>, + rook_moves: RookMoveGenerator<'pos>, king_moves: KingMoveGenerator<'pos>, } @@ -15,6 +19,7 @@ impl<'a> Moves<'a> { Moves { pawn_moves: PawnMoveGenerator::new(position, color), knight_moves: KnightMoveGenerator::new(position, color), + rook_moves: RookMoveGenerator::new(position, color), king_moves: KingMoveGenerator::new(position, color), } } @@ -23,6 +28,7 @@ impl<'a> Moves<'a> { self.pawn_moves .iter() .chain(self.knight_moves.iter()) + .chain(self.rook_moves.iter()) .chain(self.king_moves.iter()) .cloned() } diff --git a/board/src/moves/rook.rs b/board/src/moves/rook.rs new file mode 100644 index 0000000..d5526da --- /dev/null +++ b/board/src/moves/rook.rs @@ -0,0 +1,183 @@ +// Eryn Wells + +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 + ) + ); + } +} diff --git a/board/src/position/position.rs b/board/src/position/position.rs index f1d2b06..8868760 100644 --- a/board/src/position/position.rs +++ b/board/src/position/position.rs @@ -97,15 +97,16 @@ impl Position { } /// Return a BitBoard representing the set of squares containing a piece. + #[inline] pub(crate) fn occupied_squares(&self) -> BitBoard { 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. /// This set is the inverse of `occupied_squares`. + #[inline] pub(crate) fn empty_squares(&self) -> BitBoard { - !(self.pieces_per_color[Color::White as usize] - | self.pieces_per_color[Color::Black as usize]) + !self.occupied_squares() } pub(crate) fn bitboard_for_piece(&self, piece: Piece) -> BitBoard {