diff --git a/position/src/move_generator.rs b/position/src/move_generator.rs deleted file mode 100644 index 2f23616..0000000 --- a/position/src/move_generator.rs +++ /dev/null @@ -1,173 +0,0 @@ -// Eryn Wells - -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; - fn moves(&self, color: Color) -> dyn Iterator; - fn attacks(&self, color: Color) -> dyn Iterator; -} - -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 + '_ { - 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 { - 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 + '_ { - 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()) - } -} diff --git a/position/src/move_generator/bishop.rs b/position/src/move_generator/bishop.rs deleted file mode 100644 index 45d9086..0000000 --- a/position/src/move_generator/bishop.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Eryn Wells - -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 - ); - } -} diff --git a/position/src/move_generator/king.rs b/position/src/move_generator/king.rs deleted file mode 100644 index 4daa155..0000000 --- a/position/src/move_generator/king.rs +++ /dev/null @@ -1,228 +0,0 @@ -// Eryn Wells - -//! 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 = 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 = 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 = 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 = 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 = 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(()) - } -} diff --git a/position/src/move_generator/knight.rs b/position/src/move_generator/knight.rs deleted file mode 100644 index 76afa44..0000000 --- a/position/src/move_generator/knight.rs +++ /dev/null @@ -1,68 +0,0 @@ -// Eryn Wells - -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(()) - } -} diff --git a/position/src/move_generator/move_set.rs b/position/src/move_generator/move_set.rs deleted file mode 100644 index b4ae2d3..0000000 --- a/position/src/move_generator/move_set.rs +++ /dev/null @@ -1,191 +0,0 @@ -// Eryn Wells - -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, -} - -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 + '_ { - 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 { - 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 { - 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, - } - } -} diff --git a/position/src/move_generator/pawn.rs b/position/src/move_generator/pawn.rs deleted file mode 100644 index ddc1767..0000000 --- a/position/src/move_generator/pawn.rs +++ /dev/null @@ -1,353 +0,0 @@ -// Eryn Wells - -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, - en_passant_captures: Vec, -} - -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 { - 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 { - 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 = 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(()) - } -} diff --git a/position/src/move_generator/queen.rs b/position/src/move_generator/queen.rs deleted file mode 100644 index b189805..0000000 --- a/position/src/move_generator/queen.rs +++ /dev/null @@ -1,154 +0,0 @@ -// Eryn Wells - -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 - ] - ); - } -} diff --git a/position/src/move_generator/rook.rs b/position/src/move_generator/rook.rs deleted file mode 100644 index e01f733..0000000 --- a/position/src/move_generator/rook.rs +++ /dev/null @@ -1,131 +0,0 @@ -// Eryn Wells - -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] - ); - } -}