[position] Remove dead move_generator code
This commit is contained in:
		
							parent
							
								
									942d9fe47b
								
							
						
					
					
						commit
						a4b713a558
					
				
					 8 changed files with 0 additions and 1441 deletions
				
			
		| 
						 | 
				
			
			@ -1,173 +0,0 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
mod bishop;
 | 
			
		||||
mod king;
 | 
			
		||||
mod knight;
 | 
			
		||||
mod move_set;
 | 
			
		||||
mod pawn;
 | 
			
		||||
mod queen;
 | 
			
		||||
mod rook;
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests;
 | 
			
		||||
 | 
			
		||||
pub(crate) use move_set::MoveSet;
 | 
			
		||||
 | 
			
		||||
use self::{
 | 
			
		||||
    bishop::ClassicalMoveGenerator as BishopMoveGenerator, king::KingMoveGenerator,
 | 
			
		||||
    knight::KnightMoveGenerator, pawn::PawnMoveGenerator,
 | 
			
		||||
    queen::ClassicalMoveGenerator as QueenMoveGenerator,
 | 
			
		||||
    rook::ClassicalMoveGenerator as RookMoveGenerator,
 | 
			
		||||
};
 | 
			
		||||
use crate::Position;
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_board::Board;
 | 
			
		||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
 | 
			
		||||
use chessfriend_moves::Move;
 | 
			
		||||
use std::collections::BTreeMap;
 | 
			
		||||
 | 
			
		||||
trait MoveGenerator {
 | 
			
		||||
    fn iter(&self) -> dyn Iterator<Item = Move>;
 | 
			
		||||
    fn moves(&self, color: Color) -> dyn Iterator<Item = Move>;
 | 
			
		||||
    fn attacks(&self, color: Color) -> dyn Iterator<Item = Move>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
macro_rules! move_generator_declaration {
 | 
			
		||||
    ($name:ident) => {
 | 
			
		||||
        move_generator_declaration!($name, struct);
 | 
			
		||||
        move_generator_declaration!($name, new);
 | 
			
		||||
        move_generator_declaration!($name, getters);
 | 
			
		||||
    };
 | 
			
		||||
    ($name:ident, struct) => {
 | 
			
		||||
        #[derive(Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
        pub(super) struct $name {
 | 
			
		||||
            color: chessfriend_core::Color,
 | 
			
		||||
            move_sets: std::collections::BTreeMap<
 | 
			
		||||
                chessfriend_core::Square,
 | 
			
		||||
                $crate::move_generator::MoveSet,
 | 
			
		||||
            >,
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    ($name:ident, new) => {
 | 
			
		||||
        impl $name {
 | 
			
		||||
            pub(super) fn new(
 | 
			
		||||
                board: &chessfriend_board::Board,
 | 
			
		||||
                color: chessfriend_core::Color,
 | 
			
		||||
                capture_mask: chessfriend_bitboard::BitBoard,
 | 
			
		||||
                push_mask: chessfriend_bitboard::BitBoard,
 | 
			
		||||
            ) -> $name {
 | 
			
		||||
                let move_sets = if Self::shape() == chessfriend_core::Shape::King
 | 
			
		||||
                    || !(capture_mask.is_empty() && push_mask.is_empty())
 | 
			
		||||
                {
 | 
			
		||||
                    Self::move_sets(board, color, capture_mask, push_mask)
 | 
			
		||||
                } else {
 | 
			
		||||
                    std::collections::BTreeMap::new()
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                $name { color, move_sets }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    ($name:ident, getters) => {
 | 
			
		||||
        impl $name {
 | 
			
		||||
            pub(super) fn iter(&self) -> impl Iterator<Item = chessfriend_moves::Move> + '_ {
 | 
			
		||||
                self.move_sets.values().flat_map(|set| set.moves())
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            pub(crate) fn moves_for_piece(
 | 
			
		||||
                &self,
 | 
			
		||||
                piece: &chessfriend_core::PlacedPiece,
 | 
			
		||||
            ) -> Option<&$crate::move_generator::move_set::MoveSet> {
 | 
			
		||||
                self.move_sets.get(&piece.square())
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            #[cfg(test)]
 | 
			
		||||
            fn _test_bitboard(&self) -> chessfriend_bitboard::BitBoard {
 | 
			
		||||
                self.move_sets.values().fold(
 | 
			
		||||
                    chessfriend_bitboard::BitBoard::empty(),
 | 
			
		||||
                    |partial, mv_set| partial | mv_set.bitboard(),
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(self) use move_generator_declaration;
 | 
			
		||||
 | 
			
		||||
trait MoveGeneratorInternal {
 | 
			
		||||
    fn shape() -> Shape;
 | 
			
		||||
 | 
			
		||||
    fn piece(color: Color) -> Piece {
 | 
			
		||||
        Piece::new(color, Self::shape())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_sets(
 | 
			
		||||
        board: &Board,
 | 
			
		||||
        color: Color,
 | 
			
		||||
        capture_mask: BitBoard,
 | 
			
		||||
        push_mask: BitBoard,
 | 
			
		||||
    ) -> BTreeMap<Square, MoveSet> {
 | 
			
		||||
        let piece = Self::piece(color);
 | 
			
		||||
        BTreeMap::from_iter(
 | 
			
		||||
            board
 | 
			
		||||
                .bitboard_for_piece(piece)
 | 
			
		||||
                .occupied_squares()
 | 
			
		||||
                .map(|square| {
 | 
			
		||||
                    let piece = PlacedPiece::new(piece, square);
 | 
			
		||||
                    let move_set = Self::move_set_for_piece(board, &piece, capture_mask, push_mask);
 | 
			
		||||
                    (square, move_set)
 | 
			
		||||
                }),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(
 | 
			
		||||
        board: &Board,
 | 
			
		||||
        piece: &PlacedPiece,
 | 
			
		||||
        capture_mask: BitBoard,
 | 
			
		||||
        push_mask: BitBoard,
 | 
			
		||||
    ) -> MoveSet;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
pub struct Moves {
 | 
			
		||||
    pawn_moves: PawnMoveGenerator,
 | 
			
		||||
    knight_moves: KnightMoveGenerator,
 | 
			
		||||
    bishop_moves: BishopMoveGenerator,
 | 
			
		||||
    rook_moves: RookMoveGenerator,
 | 
			
		||||
    queen_moves: QueenMoveGenerator,
 | 
			
		||||
    king_moves: KingMoveGenerator,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Moves {
 | 
			
		||||
    pub fn new(board: &Board, color: Color, capture_mask: BitBoard, push_mask: BitBoard) -> Moves {
 | 
			
		||||
        Moves {
 | 
			
		||||
            pawn_moves: PawnMoveGenerator::new(board, color, capture_mask, push_mask),
 | 
			
		||||
            knight_moves: KnightMoveGenerator::new(board, color, capture_mask, push_mask),
 | 
			
		||||
            bishop_moves: BishopMoveGenerator::new(board, color, capture_mask, push_mask),
 | 
			
		||||
            rook_moves: RookMoveGenerator::new(board, color, capture_mask, push_mask),
 | 
			
		||||
            queen_moves: QueenMoveGenerator::new(board, color, capture_mask, push_mask),
 | 
			
		||||
            king_moves: KingMoveGenerator::new(board, color, capture_mask, push_mask),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn moves_for_piece(&self, piece: &PlacedPiece) -> Option<&MoveSet> {
 | 
			
		||||
        match piece.shape() {
 | 
			
		||||
            Shape::Pawn => self.pawn_moves.moves_for_piece(piece),
 | 
			
		||||
            Shape::Knight => self.knight_moves.moves_for_piece(piece),
 | 
			
		||||
            Shape::Bishop => self.bishop_moves.moves_for_piece(piece),
 | 
			
		||||
            Shape::Rook => self.rook_moves.moves_for_piece(piece),
 | 
			
		||||
            Shape::Queen => self.queen_moves.moves_for_piece(piece),
 | 
			
		||||
            Shape::King => self.king_moves.moves_for_piece(piece),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn iter(&self) -> impl Iterator<Item = Move> + '_ {
 | 
			
		||||
        self.pawn_moves
 | 
			
		||||
            .iter()
 | 
			
		||||
            .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())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,143 +0,0 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_board::Board;
 | 
			
		||||
use chessfriend_core::{Direction, PlacedPiece, Shape};
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(ClassicalMoveGenerator);
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for ClassicalMoveGenerator {
 | 
			
		||||
    fn shape() -> Shape {
 | 
			
		||||
        Shape::Bishop
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(
 | 
			
		||||
        board: &Board,
 | 
			
		||||
        piece: &PlacedPiece,
 | 
			
		||||
        capture_mask: BitBoard,
 | 
			
		||||
        push_mask: BitBoard,
 | 
			
		||||
    ) -> MoveSet {
 | 
			
		||||
        let square = piece.square();
 | 
			
		||||
 | 
			
		||||
        let blockers = board.occupied_squares();
 | 
			
		||||
        let empty_squares = !blockers;
 | 
			
		||||
        let (friendly_pieces, opposing_pieces) = board.all_pieces();
 | 
			
		||||
 | 
			
		||||
        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 = all_moves & (empty_squares | !friendly_pieces) & push_mask;
 | 
			
		||||
        let capture_moves = all_moves & opposing_pieces & capture_mask;
 | 
			
		||||
 | 
			
		||||
        MoveSet::new(*piece)
 | 
			
		||||
            .quiet_moves(quiet_moves)
 | 
			
		||||
            .capture_moves(capture_moves)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::position;
 | 
			
		||||
    use chessfriend_bitboard::BitBoard;
 | 
			
		||||
    use chessfriend_core::Color;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_bishop_bitboard() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Bishop on A1,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator._test_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 pos = position![
 | 
			
		||||
            White Bishop on A1,
 | 
			
		||||
            White Knight on E5,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        println!("{}", pos.display());
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator._test_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 pos = position![
 | 
			
		||||
            White Bishop on A1,
 | 
			
		||||
            Black Knight on C3,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator._test_bitboard(),
 | 
			
		||||
            BitBoard::new(
 | 
			
		||||
                0b00000000_00000000_00000000_00000000_00000000_00000100_00000010_00000000
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_bishop_in_center() {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Bishop on E4,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        println!("{}", pos.display());
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
        let bitboard = generator._test_bitboard();
 | 
			
		||||
        let expected = BitBoard::new(
 | 
			
		||||
            0b00000001_10000010_01000100_00101000_00000000_00101000_01000100_10000010,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            bitboard, expected,
 | 
			
		||||
            "actual:\n{}\nexpected:\n{}",
 | 
			
		||||
            bitboard, expected
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,228 +0,0 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
//! Declares the KingMoveGenerator type. This struct is responsible for
 | 
			
		||||
//! generating the possible moves for the king in the given position.
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_board::{castle::Castle, Board};
 | 
			
		||||
use chessfriend_core::{PlacedPiece, Shape};
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(KingMoveGenerator, struct);
 | 
			
		||||
move_generator_declaration!(KingMoveGenerator, new);
 | 
			
		||||
move_generator_declaration!(KingMoveGenerator, getters);
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for KingMoveGenerator {
 | 
			
		||||
    fn shape() -> Shape {
 | 
			
		||||
        Shape::King
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(
 | 
			
		||||
        board: &Board,
 | 
			
		||||
        placed_piece: &PlacedPiece,
 | 
			
		||||
        _capture_mask: BitBoard,
 | 
			
		||||
        _push_mask: BitBoard,
 | 
			
		||||
    ) -> MoveSet {
 | 
			
		||||
        let piece = placed_piece.piece();
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
        let square = placed_piece.square();
 | 
			
		||||
 | 
			
		||||
        let safe_squares = BitBoard::FULL;
 | 
			
		||||
        let all_king_moves = BitBoard::king_moves(square);
 | 
			
		||||
 | 
			
		||||
        let empty_squares = board.empty_squares();
 | 
			
		||||
        let safe_empty_squares = empty_squares & safe_squares;
 | 
			
		||||
 | 
			
		||||
        let opposing_pieces = board.bitboard_for_color(color.other());
 | 
			
		||||
        let opposing_pieces_on_safe_squares = opposing_pieces & safe_squares;
 | 
			
		||||
 | 
			
		||||
        let quiet_moves = all_king_moves & safe_empty_squares;
 | 
			
		||||
        let capture_moves = all_king_moves & opposing_pieces_on_safe_squares;
 | 
			
		||||
 | 
			
		||||
        let mut move_set = MoveSet::new(*placed_piece)
 | 
			
		||||
            .quiet_moves(quiet_moves)
 | 
			
		||||
            .capture_moves(capture_moves);
 | 
			
		||||
 | 
			
		||||
        if board.player_can_castle(color, Castle::KingSide) {
 | 
			
		||||
            move_set.kingside_castle();
 | 
			
		||||
        }
 | 
			
		||||
        if board.player_can_castle(color, Castle::QueenSide) {
 | 
			
		||||
            move_set.queenside_castle();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        move_set
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{assert_move_list, test_position, testing::*};
 | 
			
		||||
    use chessfriend_bitboard::bitboard;
 | 
			
		||||
    use chessfriend_board::castle::Castle;
 | 
			
		||||
    use chessfriend_core::{piece, Color, Square};
 | 
			
		||||
    use chessfriend_moves::{Builder as MoveBuilder, Move};
 | 
			
		||||
    use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_king() -> TestResult {
 | 
			
		||||
        let pos = test_position![White King on E4];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator._test_bitboard(),
 | 
			
		||||
            bitboard![E5 F5 F4 F3 E3 D3 D4 D5]
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let builder = MoveBuilder::push(&piece!(White King on E4));
 | 
			
		||||
        let expected_moves: HashSet<Move> = HashSet::from_iter([
 | 
			
		||||
            builder.clone().to(Square::D5).build()?,
 | 
			
		||||
            builder.clone().to(Square::E5).build()?,
 | 
			
		||||
            builder.clone().to(Square::F5).build()?,
 | 
			
		||||
            builder.clone().to(Square::F4).build()?,
 | 
			
		||||
            builder.clone().to(Square::F3).build()?,
 | 
			
		||||
            builder.clone().to(Square::E3).build()?,
 | 
			
		||||
            builder.clone().to(Square::D3).build()?,
 | 
			
		||||
            builder.clone().to(Square::D4).build()?,
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        assert_move_list!(generated_moves, expected_moves, pos);
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_king_corner() -> TestResult {
 | 
			
		||||
        let pos = test_position![White King on A1];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        let generated_bitboard = generator._test_bitboard();
 | 
			
		||||
        let expected_bitboard = bitboard![A2 B2 B1];
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator._test_bitboard(),
 | 
			
		||||
            bitboard![A2 B2 B1],
 | 
			
		||||
            "Generated:\n{generated_bitboard}\nExpected:\n{expected_bitboard}"
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let builder = MoveBuilder::push(&piece!(White King on A1));
 | 
			
		||||
        let expected_moves = [
 | 
			
		||||
            builder.clone().to(Square::A2).build()?,
 | 
			
		||||
            builder.clone().to(Square::B1).build()?,
 | 
			
		||||
            builder.clone().to(Square::B2).build()?,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let mut generated_moves: HashSet<_> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        for ex_move in expected_moves {
 | 
			
		||||
            assert!(
 | 
			
		||||
                generated_moves.remove(&ex_move),
 | 
			
		||||
                "{:#?} was not generated",
 | 
			
		||||
                &ex_move
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assert!(
 | 
			
		||||
            generated_moves.is_empty(),
 | 
			
		||||
            "Moves unexpectedly present: {:#?}",
 | 
			
		||||
            generated_moves
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn black_king_in_check_by_rook() {
 | 
			
		||||
        let pos = test_position!(Black, [
 | 
			
		||||
            White King on E1,
 | 
			
		||||
            White Rook on E4,
 | 
			
		||||
            Black King on E7,
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        assert!(pos.is_king_in_check());
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            KingMoveGenerator::new(&pos.board, Color::Black, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
        let generated_moves = generator._test_bitboard();
 | 
			
		||||
 | 
			
		||||
        let expected_moves = bitboard![F8 F7 F6 D6 D7 D8];
 | 
			
		||||
 | 
			
		||||
        assert_eq!(generated_moves, expected_moves);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn white_king_unobstructed_castles() -> TestResult {
 | 
			
		||||
        let pos = test_position!(
 | 
			
		||||
            White King on E1,
 | 
			
		||||
            White Rook on A1,
 | 
			
		||||
            White Rook on H1,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        assert!(pos.player_can_castle(Color::White, Castle::KingSide));
 | 
			
		||||
        assert!(pos.player_can_castle(Color::White, Castle::QueenSide));
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        assert!(generated_moves
 | 
			
		||||
            .contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
 | 
			
		||||
        assert!(generated_moves
 | 
			
		||||
            .contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn white_king_obstructed_queenside_castle() -> TestResult {
 | 
			
		||||
        let pos = test_position!(
 | 
			
		||||
            White King on E1,
 | 
			
		||||
            White Knight on B1,
 | 
			
		||||
            White Rook on A1,
 | 
			
		||||
            White Rook on H1,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        assert!(pos.player_can_castle(Color::White, Castle::KingSide));
 | 
			
		||||
        assert!(!pos.player_can_castle(Color::White, Castle::QueenSide));
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        assert!(generated_moves
 | 
			
		||||
            .contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
 | 
			
		||||
        assert!(!generated_moves
 | 
			
		||||
            .contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn white_king_obstructed_kingside_castle() -> TestResult {
 | 
			
		||||
        let pos = test_position!(
 | 
			
		||||
            White King on E1,
 | 
			
		||||
            White Rook on A1,
 | 
			
		||||
            White Knight on G1,
 | 
			
		||||
            White Rook on H1,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        assert!(!pos.player_can_castle(Color::White, Castle::KingSide));
 | 
			
		||||
        assert!(pos.player_can_castle(Color::White, Castle::QueenSide));
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        assert!(!generated_moves
 | 
			
		||||
            .contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
 | 
			
		||||
        assert!(generated_moves
 | 
			
		||||
            .contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,68 +0,0 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_board::Board;
 | 
			
		||||
use chessfriend_core::{PlacedPiece, Shape};
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(KnightMoveGenerator);
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for KnightMoveGenerator {
 | 
			
		||||
    fn shape() -> Shape {
 | 
			
		||||
        Shape::Knight
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(
 | 
			
		||||
        board: &Board,
 | 
			
		||||
        placed_piece: &PlacedPiece,
 | 
			
		||||
        capture_mask: BitBoard,
 | 
			
		||||
        push_mask: BitBoard,
 | 
			
		||||
    ) -> MoveSet {
 | 
			
		||||
        let opposing_pieces = board.bitboard_for_color(placed_piece.piece().color().other());
 | 
			
		||||
        let empty_squares = board.empty_squares();
 | 
			
		||||
        let knight_moves = BitBoard::knight_moves(placed_piece.square());
 | 
			
		||||
 | 
			
		||||
        let quiet_moves = knight_moves & empty_squares & push_mask;
 | 
			
		||||
        let capture_moves = knight_moves & opposing_pieces & capture_mask;
 | 
			
		||||
 | 
			
		||||
        MoveSet::new(*placed_piece)
 | 
			
		||||
            .quiet_moves(quiet_moves)
 | 
			
		||||
            .capture_moves(capture_moves)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{assert_move_list, position, testing::*};
 | 
			
		||||
    use chessfriend_core::{piece, Color, Square};
 | 
			
		||||
    use chessfriend_moves::Builder as MoveBuilder;
 | 
			
		||||
    use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_knight() -> TestResult {
 | 
			
		||||
        let pos = position![
 | 
			
		||||
            White Knight on E4,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            KnightMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
        let generated_moves: HashSet<_> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        let piece = piece!(White Knight on E4);
 | 
			
		||||
        let expected_moves = HashSet::from_iter([
 | 
			
		||||
            MoveBuilder::push(&piece).to(Square::C3).build()?,
 | 
			
		||||
            MoveBuilder::push(&piece).to(Square::D2).build()?,
 | 
			
		||||
            MoveBuilder::push(&piece).to(Square::F2).build()?,
 | 
			
		||||
            MoveBuilder::push(&piece).to(Square::G3).build()?,
 | 
			
		||||
            MoveBuilder::push(&piece).to(Square::C5).build()?,
 | 
			
		||||
            MoveBuilder::push(&piece).to(Square::D6).build()?,
 | 
			
		||||
            MoveBuilder::push(&piece).to(Square::G5).build()?,
 | 
			
		||||
            MoveBuilder::push(&piece).to(Square::F6).build()?,
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        assert_move_list!(generated_moves, expected_moves, pos);
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,191 +0,0 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_board::{castle::Castle, en_passant::EnPassant};
 | 
			
		||||
use chessfriend_core::{PlacedPiece, Square};
 | 
			
		||||
use chessfriend_moves::{Builder as MoveBuilder, Move};
 | 
			
		||||
 | 
			
		||||
/// A set of bitboards defining the moves for a single piece on the board.
 | 
			
		||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
 | 
			
		||||
struct BitBoardSet {
 | 
			
		||||
    quiet: BitBoard,
 | 
			
		||||
    captures: BitBoard,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
pub(crate) enum Special {
 | 
			
		||||
    Pawn { en_passant: EnPassant },
 | 
			
		||||
    King { castles: u8 },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A set of moves for a single piece on the board.
 | 
			
		||||
#[derive(Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
pub(crate) struct MoveSet {
 | 
			
		||||
    piece: PlacedPiece,
 | 
			
		||||
    bitboards: BitBoardSet,
 | 
			
		||||
    special: Option<Special>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MoveSet {
 | 
			
		||||
    pub(super) fn new(piece: PlacedPiece) -> MoveSet {
 | 
			
		||||
        MoveSet {
 | 
			
		||||
            piece,
 | 
			
		||||
            bitboards: BitBoardSet::default(),
 | 
			
		||||
            special: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn can_move_to_square(&self, target_square: Square) -> bool {
 | 
			
		||||
        match self.special {
 | 
			
		||||
            Some(Special::King { castles }) => {
 | 
			
		||||
                if self.check_castle_field(castles, Castle::KingSide)
 | 
			
		||||
                    && target_square
 | 
			
		||||
                        == Castle::KingSide
 | 
			
		||||
                            .parameters(self.piece.color())
 | 
			
		||||
                            .king_target_square()
 | 
			
		||||
                {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if self.check_castle_field(castles, Castle::KingSide)
 | 
			
		||||
                    && target_square
 | 
			
		||||
                        == Castle::QueenSide
 | 
			
		||||
                            .parameters(self.piece.color())
 | 
			
		||||
                            .king_target_square()
 | 
			
		||||
                {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Some(Special::Pawn { en_passant }) => {
 | 
			
		||||
                if target_square == en_passant.target_square() {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            None => {}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.bitboard().contains(target_square)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn can_castle(&self, castle: Castle) -> bool {
 | 
			
		||||
        match self.special {
 | 
			
		||||
            Some(Special::King { castles }) => self.check_castle_field(castles, castle),
 | 
			
		||||
            _ => false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn check_castle_field(&self, castle_field: u8, castle: Castle) -> bool {
 | 
			
		||||
        (castle_field & 1 << castle as u8) != 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn quiet_moves(mut self, bitboard: BitBoard) -> MoveSet {
 | 
			
		||||
        self.bitboards.quiet = bitboard;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn capture_moves(mut self, bitboard: BitBoard) -> MoveSet {
 | 
			
		||||
        self.bitboards.captures = bitboard;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn kingside_castle(&mut self) -> &mut MoveSet {
 | 
			
		||||
        match self.special {
 | 
			
		||||
            Some(Special::King { ref mut castles }) => *castles |= 1 << Castle::KingSide as u8,
 | 
			
		||||
            _ => {
 | 
			
		||||
                self.special = Some(Special::King {
 | 
			
		||||
                    castles: 1 << Castle::KingSide as u8,
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn queenside_castle(&mut self) -> &mut MoveSet {
 | 
			
		||||
        match self.special {
 | 
			
		||||
            Some(Special::King { ref mut castles }) => *castles |= 1 << Castle::QueenSide as u8,
 | 
			
		||||
            _ => {
 | 
			
		||||
                self.special = Some(Special::King {
 | 
			
		||||
                    castles: 1 << Castle::QueenSide as u8,
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn en_passant(&mut self, en_passant: EnPassant) -> &mut MoveSet {
 | 
			
		||||
        self.special = Some(Special::Pawn { en_passant });
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// A `BitBoard` representing all possible moves.
 | 
			
		||||
    pub(super) fn bitboard(&self) -> BitBoard {
 | 
			
		||||
        self.bitboards.captures | self.bitboards.quiet
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn moves(&self) -> impl Iterator<Item = Move> + '_ {
 | 
			
		||||
        let piece = &self.piece;
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
 | 
			
		||||
        let is_pawn_on_starting_rank =
 | 
			
		||||
            piece.is_pawn() && piece.square().rank().is_pawn_starting_rank(color);
 | 
			
		||||
 | 
			
		||||
        self.bitboards
 | 
			
		||||
            .quiet
 | 
			
		||||
            .occupied_squares()
 | 
			
		||||
            .filter_map(move |to_square| {
 | 
			
		||||
                if is_pawn_on_starting_rank
 | 
			
		||||
                    && to_square.rank().is_pawn_double_push_target_rank(color)
 | 
			
		||||
                {
 | 
			
		||||
                    MoveBuilder::double_push(piece.square().file(), color)
 | 
			
		||||
                        .build()
 | 
			
		||||
                        .ok()
 | 
			
		||||
                } else {
 | 
			
		||||
                    MoveBuilder::push(piece).to(to_square).build().ok()
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .chain(
 | 
			
		||||
                self.bitboards
 | 
			
		||||
                    .captures
 | 
			
		||||
                    .occupied_squares()
 | 
			
		||||
                    .filter_map(|to_square| {
 | 
			
		||||
                        MoveBuilder::push(piece)
 | 
			
		||||
                            .capturing_on(to_square)
 | 
			
		||||
                            .build()
 | 
			
		||||
                            .ok()
 | 
			
		||||
                    }),
 | 
			
		||||
            )
 | 
			
		||||
            .chain(self.castle_move(Castle::KingSide))
 | 
			
		||||
            .chain(self.castle_move(Castle::QueenSide))
 | 
			
		||||
            .chain(self.en_passant_move())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn castle_move(&self, castle: Castle) -> Option<Move> {
 | 
			
		||||
        match self.special {
 | 
			
		||||
            Some(Special::King { castles }) => {
 | 
			
		||||
                if (castles & 1 << castle as u8) != 0 {
 | 
			
		||||
                    Some(
 | 
			
		||||
                        MoveBuilder::castling(self.piece.color(), castle)
 | 
			
		||||
                            .build()
 | 
			
		||||
                            .ok()?,
 | 
			
		||||
                    )
 | 
			
		||||
                } else {
 | 
			
		||||
                    None
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn en_passant_move(&self) -> Option<Move> {
 | 
			
		||||
        match self.special {
 | 
			
		||||
            Some(Special::Pawn { en_passant }) => Some(unsafe {
 | 
			
		||||
                MoveBuilder::push(&self.piece)
 | 
			
		||||
                    .capturing_en_passant_on(en_passant.target_square())
 | 
			
		||||
                    .build_unchecked()
 | 
			
		||||
            }),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,353 +0,0 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_board::{en_passant::EnPassant, Board};
 | 
			
		||||
use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square};
 | 
			
		||||
use chessfriend_moves::Move;
 | 
			
		||||
use std::collections::BTreeMap;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct MoveIterator(usize, usize);
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
pub(super) struct PawnMoveGenerator {
 | 
			
		||||
    color: chessfriend_core::Color,
 | 
			
		||||
    move_sets: BTreeMap<Square, MoveSet>,
 | 
			
		||||
    en_passant_captures: Vec<Move>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(PawnMoveGenerator, getters);
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for PawnMoveGenerator {
 | 
			
		||||
    fn shape() -> Shape {
 | 
			
		||||
        Shape::Pawn
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(
 | 
			
		||||
        board: &Board,
 | 
			
		||||
        placed_piece: &PlacedPiece,
 | 
			
		||||
        capture_mask: BitBoard,
 | 
			
		||||
        push_mask: BitBoard,
 | 
			
		||||
    ) -> MoveSet {
 | 
			
		||||
        let capture_moves = Self::attacks(board, &placed_piece) & capture_mask;
 | 
			
		||||
        let quiet_moves = Self::pushes(board, &placed_piece) & push_mask;
 | 
			
		||||
 | 
			
		||||
        let mut move_set = MoveSet::new(*placed_piece)
 | 
			
		||||
            .quiet_moves(quiet_moves)
 | 
			
		||||
            .capture_moves(capture_moves);
 | 
			
		||||
 | 
			
		||||
        if let Some(en_passant) = Self::en_passant(board, placed_piece, &push_mask, &capture_mask) {
 | 
			
		||||
            move_set.en_passant(en_passant);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        move_set
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PawnMoveGenerator {
 | 
			
		||||
    pub(super) fn new(
 | 
			
		||||
        board: &Board,
 | 
			
		||||
        player_to_move: Color,
 | 
			
		||||
        capture_mask: BitBoard,
 | 
			
		||||
        push_mask: BitBoard,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let move_sets = if !(capture_mask.is_empty() && push_mask.is_empty()) {
 | 
			
		||||
            Self::move_sets(board, player_to_move, capture_mask, push_mask)
 | 
			
		||||
        } else {
 | 
			
		||||
            std::collections::BTreeMap::new()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            color: player_to_move,
 | 
			
		||||
            move_sets,
 | 
			
		||||
            en_passant_captures: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_sets(
 | 
			
		||||
        board: &Board,
 | 
			
		||||
        color: Color,
 | 
			
		||||
        capture_mask: BitBoard,
 | 
			
		||||
        push_mask: BitBoard,
 | 
			
		||||
    ) -> BTreeMap<Square, MoveSet> {
 | 
			
		||||
        let piece = Self::piece(color);
 | 
			
		||||
        let moves_for_pieces = BTreeMap::from_iter(
 | 
			
		||||
            board
 | 
			
		||||
                .bitboard_for_piece(piece)
 | 
			
		||||
                .occupied_squares()
 | 
			
		||||
                .map(|square| {
 | 
			
		||||
                    let piece = PlacedPiece::new(piece, square);
 | 
			
		||||
                    let move_set = Self::move_set_for_piece(board, &piece, capture_mask, push_mask);
 | 
			
		||||
                    (square, move_set)
 | 
			
		||||
                }),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        moves_for_pieces
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn pushes(board: &Board, piece: &PlacedPiece) -> BitBoard {
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
        let square = piece.square();
 | 
			
		||||
        let bitboard: BitBoard = square.into();
 | 
			
		||||
 | 
			
		||||
        let starting_rank = Rank::PAWN_STARTING_RANKS[color as usize];
 | 
			
		||||
        let empty_squares = board.empty_squares();
 | 
			
		||||
 | 
			
		||||
        match color {
 | 
			
		||||
            Color::White => {
 | 
			
		||||
                let mut moves = bitboard.shift_north_one() & empty_squares;
 | 
			
		||||
                if !(bitboard & BitBoard::rank(starting_rank.as_index())).is_empty() {
 | 
			
		||||
                    moves |= moves.shift_north_one() & empty_squares;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                moves
 | 
			
		||||
            }
 | 
			
		||||
            Color::Black => {
 | 
			
		||||
                let mut moves = bitboard.shift_south_one() & empty_squares;
 | 
			
		||||
                if !(bitboard & BitBoard::rank(starting_rank.as_index())).is_empty() {
 | 
			
		||||
                    moves |= moves.shift_south_one() & empty_squares;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                moves
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn attacks(board: &Board, piece: &PlacedPiece) -> BitBoard {
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
 | 
			
		||||
        let opponent_pieces = board.bitboard_for_color(color.other());
 | 
			
		||||
 | 
			
		||||
        BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn en_passant(
 | 
			
		||||
        board: &Board,
 | 
			
		||||
        piece: &PlacedPiece,
 | 
			
		||||
        push_mask: &BitBoard,
 | 
			
		||||
        capture_mask: &BitBoard,
 | 
			
		||||
    ) -> Option<EnPassant> {
 | 
			
		||||
        match board.en_passant() {
 | 
			
		||||
            Some(en_passant) => {
 | 
			
		||||
                let target_square: BitBoard = en_passant.target_square().into();
 | 
			
		||||
                let capture_square: BitBoard = en_passant.capture_square().into();
 | 
			
		||||
 | 
			
		||||
                if (target_square & push_mask).is_empty()
 | 
			
		||||
                    && (capture_square & capture_mask).is_empty()
 | 
			
		||||
                {
 | 
			
		||||
                    // Do not allow en passant if capturing would not either
 | 
			
		||||
                    // block an active check, or capture a checking pawn.
 | 
			
		||||
                    return None;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let capture = BitBoard::pawn_attacks(piece.square(), piece.color()) & target_square;
 | 
			
		||||
                if capture.is_empty() {
 | 
			
		||||
                    return None;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                match board.piece_on_square(en_passant.capture_square()) {
 | 
			
		||||
                    Some(_) => Some(en_passant),
 | 
			
		||||
                    None => None,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            None => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(none)]
 | 
			
		||||
    fn does_en_passant_reveal_check(&self, position: &Position) -> bool {
 | 
			
		||||
        let player_to_move = position.player_to_move();
 | 
			
		||||
        let opposing_player = player_to_move.other();
 | 
			
		||||
 | 
			
		||||
        if position.king_square(opposing_player).rank()
 | 
			
		||||
            != Rank::PAWN_DOUBLE_PUSH_TARGET_RANKS[player_to_move as usize]
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::{assert_move_list, formatted_move_list, test_position, testing::*};
 | 
			
		||||
    use chessfriend_core::{piece, Color, Square};
 | 
			
		||||
    use chessfriend_moves::{Builder as MoveBuilder, Move};
 | 
			
		||||
    use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_double_push() -> TestResult {
 | 
			
		||||
        let pos = test_position![White Pawn on E2];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        let pawn = piece!(White Pawn on E2);
 | 
			
		||||
        let expected_moves = HashSet::from_iter([
 | 
			
		||||
            MoveBuilder::push(&pawn).to(Square::E3).build()?,
 | 
			
		||||
            MoveBuilder::double_push(pawn.square().file(), pawn.color()).build()?,
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<_> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        assert_move_list!(generated_moves, expected_moves, pos);
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_single_push() -> TestResult {
 | 
			
		||||
        let pos = test_position![White Pawn on E3];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
        let generated_moves: HashSet<_> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E3))
 | 
			
		||||
            .to(Square::E4)
 | 
			
		||||
            .build()?]);
 | 
			
		||||
 | 
			
		||||
        assert_move_list!(generated_moves, expected_moves, pos);
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_obstructed_2square_push() -> TestResult {
 | 
			
		||||
        let pos = test_position![
 | 
			
		||||
            White Pawn on E2,
 | 
			
		||||
            White Knight on E4,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E2))
 | 
			
		||||
            .to(Square::E3)
 | 
			
		||||
            .build()?]);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<_> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        assert_move_list!(generated_moves, expected_moves, pos);
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_obstructed_1square_push() {
 | 
			
		||||
        let pos = test_position![
 | 
			
		||||
            White Pawn on E2,
 | 
			
		||||
            White Knight on E3,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<_> = generator.iter().collect();
 | 
			
		||||
        let expected_moves: HashSet<_> = HashSet::new();
 | 
			
		||||
 | 
			
		||||
        assert_move_list!(generated_moves, expected_moves, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_attack() -> TestResult {
 | 
			
		||||
        let pos = test_position![
 | 
			
		||||
            White Pawn on E4,
 | 
			
		||||
            White Bishop on E5,
 | 
			
		||||
            Black Knight on D5,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E4))
 | 
			
		||||
            .capturing_on(Square::D5)
 | 
			
		||||
            .build()?]);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<_> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        assert_move_list!(generated_moves, expected_moves, pos);
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_double_attack() -> TestResult {
 | 
			
		||||
        let pos = test_position![
 | 
			
		||||
            White Pawn on E4,
 | 
			
		||||
            White Bishop on E5,
 | 
			
		||||
            Black Knight on D5,
 | 
			
		||||
            Black Queen on F5,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        let builder = MoveBuilder::push(&piece!(White Pawn on E4));
 | 
			
		||||
        let expected_moves = HashSet::from_iter([
 | 
			
		||||
            builder.clone().capturing_on(Square::D5).build()?,
 | 
			
		||||
            builder.clone().capturing_on(Square::F5).build()?,
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<_> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generated_moves, expected_moves,
 | 
			
		||||
            "generated: {:#?}\nexpected: {:#?}",
 | 
			
		||||
            generated_moves, expected_moves
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn one_en_passant_attack() -> TestResult {
 | 
			
		||||
        let pos = test_position!(Black, [
 | 
			
		||||
            White Pawn on D4,
 | 
			
		||||
            Black Pawn on E4,
 | 
			
		||||
        ], D3);
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            PawnMoveGenerator::new(&pos.board, Color::Black, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
        let generated_moves: HashSet<Move> = generator.iter().collect();
 | 
			
		||||
 | 
			
		||||
        let builder = MoveBuilder::push(&piece!(Black Pawn on E4));
 | 
			
		||||
        let expected_moves = HashSet::from_iter([
 | 
			
		||||
            builder.capturing_en_passant_on(Square::D3).build()?,
 | 
			
		||||
            builder.clone().to(Square::E3).build()?,
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        assert_move_list!(generated_moves, expected_moves, pos);
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Make sure the player cannot capture en passant if doing so would not resolve the check.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn cannot_capture_en_passant_while_in_check() -> TestResult {
 | 
			
		||||
        let pos = test_position!(Black, [
 | 
			
		||||
            Black King on B5,
 | 
			
		||||
            Black Pawn on E4,
 | 
			
		||||
            White Pawn on D4,
 | 
			
		||||
            White Rook on B1,
 | 
			
		||||
        ], D3);
 | 
			
		||||
 | 
			
		||||
        assert!(pos.is_king_in_check());
 | 
			
		||||
 | 
			
		||||
        let generated_moves: HashSet<_> = pos.moves().iter().collect();
 | 
			
		||||
 | 
			
		||||
        assert!(
 | 
			
		||||
            !generated_moves.contains(
 | 
			
		||||
                &MoveBuilder::push(&piece!(Black Pawn on E4))
 | 
			
		||||
                    .capturing_en_passant_on(Square::D3)
 | 
			
		||||
                    .build()?
 | 
			
		||||
            ),
 | 
			
		||||
            "Valid moves: {:?}",
 | 
			
		||||
            formatted_move_list!(generated_moves, pos)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,154 +0,0 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_board::Board;
 | 
			
		||||
use chessfriend_core::{Direction, PlacedPiece, Shape};
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(ClassicalMoveGenerator);
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for ClassicalMoveGenerator {
 | 
			
		||||
    fn shape() -> Shape {
 | 
			
		||||
        Shape::Queen
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(
 | 
			
		||||
        board: &Board,
 | 
			
		||||
        placed_piece: &PlacedPiece,
 | 
			
		||||
        capture_mask: BitBoard,
 | 
			
		||||
        push_mask: BitBoard,
 | 
			
		||||
    ) -> MoveSet {
 | 
			
		||||
        let piece = placed_piece.piece();
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
        let square = placed_piece.square();
 | 
			
		||||
 | 
			
		||||
        let blockers = board.occupied_squares();
 | 
			
		||||
        let empty_squares = !blockers;
 | 
			
		||||
        let friendly_pieces = board.bitboard_for_color(color);
 | 
			
		||||
        let opposing_pieces = board.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 = all_moves & (empty_squares | !friendly_pieces) & push_mask;
 | 
			
		||||
        let capture_moves = all_moves & opposing_pieces & capture_mask;
 | 
			
		||||
 | 
			
		||||
        MoveSet::new(*placed_piece)
 | 
			
		||||
            .quiet_moves(quiet_moves)
 | 
			
		||||
            .capture_moves(capture_moves)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::test_position;
 | 
			
		||||
    use chessfriend_bitboard::{bitboard, BitBoard};
 | 
			
		||||
    use chessfriend_core::Color;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_queen_bitboard() {
 | 
			
		||||
        let pos = test_position![White Queen on B2];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
        let bitboard = generator._test_bitboard();
 | 
			
		||||
        let expected = bitboard![
 | 
			
		||||
            A2 C2 D2 E2 F2 G2 H2 // Rank
 | 
			
		||||
            B1 B3 B4 B5 B6 B7 B8 // File
 | 
			
		||||
            A1 C3 D4 E5 F6 G7 H8 // Diagonal
 | 
			
		||||
            C1 A3 // Anti-diagonal
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        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 pos = test_position![
 | 
			
		||||
            White Queen on A1,
 | 
			
		||||
            White Knight on E1,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
        let bitboard = generator._test_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 pos = test_position![
 | 
			
		||||
            White Queen on B2,
 | 
			
		||||
            Black Knight on E5,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator._test_bitboard(),
 | 
			
		||||
            bitboard![
 | 
			
		||||
                A2 C2 D2 E2 F2 G2 H2 // Rank
 | 
			
		||||
                B1 B3 B4 B5 B6 B7 B8 // File
 | 
			
		||||
                A1 C3 D4 E5 // Diagonal
 | 
			
		||||
                C1 A3 // Anti-diagonal
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_queen_in_center() {
 | 
			
		||||
        let pos = test_position![White Queen on D3];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator._test_bitboard(),
 | 
			
		||||
            bitboard![
 | 
			
		||||
                A3 B3 C3 E3 F3 G3 H3 // Rank
 | 
			
		||||
                D1 D2 D4 D5 D6 D7 D8 // File
 | 
			
		||||
                B1 C2 E4 F5 G6 H7 // Diagonal
 | 
			
		||||
                F1 E2 C4 B5 A6 // Anti-diagonal
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,131 +0,0 @@
 | 
			
		|||
// Eryn Wells <eryn@erynwells.me>
 | 
			
		||||
 | 
			
		||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
			
		||||
use chessfriend_bitboard::BitBoard;
 | 
			
		||||
use chessfriend_board::Board;
 | 
			
		||||
use chessfriend_core::{Direction, PlacedPiece, Shape};
 | 
			
		||||
 | 
			
		||||
move_generator_declaration!(ClassicalMoveGenerator);
 | 
			
		||||
 | 
			
		||||
impl MoveGeneratorInternal for ClassicalMoveGenerator {
 | 
			
		||||
    fn shape() -> Shape {
 | 
			
		||||
        Shape::Rook
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn move_set_for_piece(
 | 
			
		||||
        board: &Board,
 | 
			
		||||
        placed_piece: &PlacedPiece,
 | 
			
		||||
        capture_mask: BitBoard,
 | 
			
		||||
        push_mask: BitBoard,
 | 
			
		||||
    ) -> MoveSet {
 | 
			
		||||
        let piece = placed_piece.piece();
 | 
			
		||||
        let color = piece.color();
 | 
			
		||||
        let square = placed_piece.square();
 | 
			
		||||
 | 
			
		||||
        let blockers = board.occupied_squares();
 | 
			
		||||
        let empty_squares = !blockers;
 | 
			
		||||
        let friendly_pieces = board.bitboard_for_color(color);
 | 
			
		||||
        let opposing_pieces = board.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!(North, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(East, occupied_squares_trailing);
 | 
			
		||||
        update_moves_with_ray!(South, occupied_squares);
 | 
			
		||||
        update_moves_with_ray!(West, occupied_squares);
 | 
			
		||||
 | 
			
		||||
        let quiet_moves = all_moves & (empty_squares | !friendly_pieces) & capture_mask;
 | 
			
		||||
        let capture_moves = all_moves & opposing_pieces & push_mask;
 | 
			
		||||
 | 
			
		||||
        MoveSet::new(*placed_piece)
 | 
			
		||||
            .quiet_moves(quiet_moves)
 | 
			
		||||
            .capture_moves(capture_moves)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::test_position;
 | 
			
		||||
    use chessfriend_bitboard::{bitboard, BitBoard};
 | 
			
		||||
    use chessfriend_core::Color;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_rook_bitboard() {
 | 
			
		||||
        let pos = test_position![White Rook on A2];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator._test_bitboard(),
 | 
			
		||||
            bitboard![A1 A3 A4 A5 A6 A7 A8 B2 C2 D2 E2 F2 G2 H2]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 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 pos = test_position![
 | 
			
		||||
            White Rook on A1,
 | 
			
		||||
            White Knight on E1,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        println!("{}", pos.display());
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator._test_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 pos = test_position![
 | 
			
		||||
            White Rook on A1,
 | 
			
		||||
            Black Knight on E1,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator._test_bitboard(),
 | 
			
		||||
            bitboard![A2 A3 A4 A5 A6 A7 A8 B1 C1 D1 E1]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn classical_single_rook_in_center() {
 | 
			
		||||
        let pos = test_position![White Rook on D4];
 | 
			
		||||
 | 
			
		||||
        let generator =
 | 
			
		||||
            ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            generator._test_bitboard(),
 | 
			
		||||
            bitboard![A4 B4 C4 E4 F4 G4 H4 D1 D2 D3 D5 D6 D7 D8]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue