diff --git a/board/src/moves/bishop.rs b/board/src/moves/bishop.rs new file mode 100644 index 0000000..40c5bea --- /dev/null +++ b/board/src/moves/bishop.rs @@ -0,0 +1,167 @@ +// 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::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 + ); + } +} diff --git a/board/src/moves/mod.rs b/board/src/moves/mod.rs index 75db67c..b13ae12 100644 --- a/board/src/moves/mod.rs +++ b/board/src/moves/mod.rs @@ -1,5 +1,6 @@ // Eryn Wells +mod bishop; mod king; mod knight; mod r#move; diff --git a/board/src/moves/move_generator.rs b/board/src/moves/move_generator.rs index 6816b93..62cb5ea 100644 --- a/board/src/moves/move_generator.rs +++ b/board/src/moves/move_generator.rs @@ -1,7 +1,8 @@ // Eryn Wells use super::{ - king::KingMoveGenerator, knight::KnightMoveGenerator, pawn::PawnMoveGenerator, + bishop::ClassicalMoveGenerator as BishopMoveGenerator, king::KingMoveGenerator, + knight::KnightMoveGenerator, pawn::PawnMoveGenerator, rook::ClassicalMoveGenerator as RookMoveGenerator, Move, }; use crate::piece::Color; @@ -10,6 +11,7 @@ use crate::Position; pub struct Moves<'pos> { pawn_moves: PawnMoveGenerator<'pos>, knight_moves: KnightMoveGenerator<'pos>, + bishop_moves: BishopMoveGenerator<'pos>, rook_moves: RookMoveGenerator<'pos>, king_moves: KingMoveGenerator<'pos>, } @@ -19,6 +21,7 @@ impl<'a> Moves<'a> { 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), king_moves: KingMoveGenerator::new(position, color), } @@ -28,6 +31,7 @@ impl<'a> Moves<'a> { self.pawn_moves .iter() .chain(self.knight_moves.iter()) + .chain(self.bishop_moves.iter()) .chain(self.rook_moves.iter()) .chain(self.king_moves.iter()) .cloned()