diff --git a/board/src/move_generator/pawn.rs b/board/src/move_generator/pawn.rs index c7899b0..61b2246 100644 --- a/board/src/move_generator/pawn.rs +++ b/board/src/move_generator/pawn.rs @@ -3,7 +3,7 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use crate::{Move, MoveBuilder, Position}; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece, Shape}; +use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; enum MoveList { Quiet = 0, @@ -22,43 +22,41 @@ struct MoveGenerationParameters { right_capture_shift: fn(&BitBoard) -> BitBoard, } -pub(super) struct PawnMoveGenerator<'pos> { - color: Color, - position: &'pos Position, +move_generator_declaration!(PawnMoveGenerator); - did_populate_move_lists: bool, +impl<'pos> MoveGeneratorInternal for PawnMoveGenerator<'pos> { + fn piece(color: Color) -> Piece { + Piece::pawn(color) + } - pushes: BitBoard, - attacks: BitBoard, + fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet { + let from_square = placed_piece.square(); + let parameters = Self::move_generation_parameters(placed_piece.color()); - move_lists: [Vec; 3], - move_iterator: MoveIterator, + let opposing_pieces = position.opposing_pieces(); + + let captures_bitboard = Self::attacks(position, placed_piece, ¶meters); + let quiet_moves_bitboard = Self::pushes(position, placed_piece, ¶meters); + + let quiet_moves = quiet_moves_bitboard.occupied_squares().map(|to_square| { + MoveBuilder::new(*placed_piece.piece(), from_square, to_square).build() + }); + let capture_moves = captures_bitboard.occupied_squares().map(|to_square| { + let captured_piece = position.piece_on_square(to_square).unwrap(); + MoveBuilder::new(*placed_piece.piece(), from_square, to_square) + .capturing(captured_piece) + .build() + }); + + MoveSet::new(placed_piece) + .quiet_moves(quiet_moves_bitboard, quiet_moves) + .capture_moves(captures_bitboard, capture_moves) + } } impl<'pos> PawnMoveGenerator<'pos> { - pub(super) fn new(position: &Position, color: Color) -> PawnMoveGenerator { - PawnMoveGenerator { - position, - color, - did_populate_move_lists: false, - pushes: BitBoard::empty(), - attacks: BitBoard::empty(), - move_lists: [Vec::new(), Vec::new(), Vec::new()], - move_iterator: MoveIterator(0, 0), - } - } - - pub(super) fn iter(&self) -> impl Iterator + '_ { - self.move_lists.iter().flatten() - } - - fn generate_moves(&mut self) { - self.generate_move_bitboards(); - self.populate_move_lists(); - } - - fn move_generation_parameters(&self) -> MoveGenerationParameters { - match self.color { + fn move_generation_parameters(color: Color) -> MoveGenerationParameters { + match color { Color::White => MoveGenerationParameters { starting_rank: BitBoard::rank(1), promotion_rank: BitBoard::rank(7), @@ -76,178 +74,43 @@ impl<'pos> PawnMoveGenerator<'pos> { } } - #[inline] - fn quiet_move_list(&mut self) -> &mut Vec { - &mut self.move_lists[MoveList::Quiet as usize] + fn pushes( + position: &Position, + piece: PlacedPiece, + parameters: &MoveGenerationParameters, + ) -> BitBoard { + let empty_squares = position.empty_squares(); + let from_square: BitBoard = piece.square().into(); + + (parameters.push_shift)(&from_square) & empty_squares } - #[inline] - fn promotion_move_list(&mut self) -> &mut Vec { - &mut self.move_lists[MoveList::Promotions as usize] - } + fn attacks( + position: &Position, + piece: PlacedPiece, + parameters: &MoveGenerationParameters, + ) -> BitBoard { + let color = piece.color(); - #[inline] - fn capture_move_list(&mut self) -> &mut Vec { - &mut self.move_lists[MoveList::Captures as usize] - } -} + let opponent_pieces = position.bitboard_for_color(color.other()); + let en_passant_square = position + .en_passant_square() + .map(|square| >::into(square)) + .unwrap_or(BitBoard::empty()); -impl<'pos> PawnMoveGenerator<'pos> { - fn generate_move_bitboards(&mut self) { - let parameters = self.move_generation_parameters(); - self.generate_pushes_bitboard(¶meters); - self.generate_attacks_bitboard(¶meters); - } + let from_square: BitBoard = piece.square().into(); - fn generate_pushes_bitboard(&mut self, parameters: &MoveGenerationParameters) { - let empty_squares = self.position.empty_squares(); - let bb = self.position.bitboard_for_piece(Piece::pawn(self.color)); - - let legal_1square_pushes = (parameters.push_shift)(bb) & empty_squares; - let legal_2square_pushes = - (parameters.push_shift)(&(legal_1square_pushes & BitBoard::rank(2))) & empty_squares; - - self.pushes = legal_1square_pushes | legal_2square_pushes; - } - - fn generate_attacks_bitboard(&mut self, parameters: &MoveGenerationParameters) { - let opponent_pieces = self.position.bitboard_for_color(self.color.other()); - let bb = self.position.bitboard_for_piece(Piece::pawn(Color::White)); - - self.attacks = ((parameters.left_capture_shift)(bb) | (parameters.right_capture_shift)(bb)) - & opponent_pieces; - - #[allow(unused_variables)] - if let Some(en_passant) = self.en_passant() { - // TODO: Add en passant move to the attacks bitboard. - } - } -} - -impl<'pos> PawnMoveGenerator<'pos> { - fn populate_move_lists(&mut self) { - let parameters = self.move_generation_parameters(); - - self._populate_move_lists(¶meters); - self.did_populate_move_lists = true; - } - - fn _populate_move_lists(&mut self, parameters: &MoveGenerationParameters) { - let piece = Piece::pawn(self.color); - - let bb = self.position.bitboard_for_piece(piece); - if bb.is_empty() { - return; - } - - let empty_squares = self.position.empty_squares(); - let black_pieces = self.position.bitboard_for_color(self.color.other()); - - for from_sq in bb.occupied_squares() { - let pawn: BitBoard = from_sq.into(); - - let push = (parameters.push_shift)(&pawn); - if !(push & empty_squares).is_empty() { - let to_sq = push.occupied_squares().next().unwrap(); - - let builder = MoveBuilder::new(piece, from_sq, to_sq); - if !(push & parameters.promotion_rank).is_empty() { - for shape in Shape::promotable() { - self.promotion_move_list() - .push(builder.clone().promoting_to(*shape).build()); - } - } else { - self.quiet_move_list().push(builder.build()); - } - - if !(pawn & parameters.starting_rank).is_empty() { - let push = (parameters.push_shift)(&push); - if !(push & empty_squares).is_empty() { - let to_sq = push.occupied_squares().next().unwrap(); - self.quiet_move_list() - .push(MoveBuilder::new(piece, from_sq, to_sq).build()); - } - } - } - - for attack in [ - (parameters.left_capture_shift)(&pawn), - (parameters.right_capture_shift)(&pawn), - ] { - if !(attack & black_pieces).is_empty() { - let to_sq = attack.occupied_squares().next().unwrap(); - let captured_piece = self.position.piece_on_square(to_sq).unwrap(); - - let builder = MoveBuilder::new(piece, from_sq, to_sq).capturing(captured_piece); - if !(attack & parameters.promotion_rank).is_empty() { - for shape in Shape::promotable() { - self.capture_move_list() - .push(builder.clone().promoting_to(*shape).build()); - } - } else { - self.capture_move_list().push(builder.build()); - } - } - } - - if let Some(en_passant) = self.en_passant() { - self.capture_move_list().push(en_passant); - } - } - } - - fn en_passant(&self) -> Option { - // TODO: En passant. I think the way to do this is to have the position mark - // an en passant square when the conditions are correct, i.e. when the - // opposing player has pushed a pawn two squares off the initial rank, and - // then check in these routines if a pawn of this color attacks that square. - - None - } -} - -impl<'pos> Iterator for PawnMoveGenerator<'pos> { - type Item = Move; - - fn next(&mut self) -> Option { - if !self.did_populate_move_lists { - self.generate_moves(); - } - - let iter = &mut self.move_iterator; - - // Find the next non-empty list. - loop { - if iter.0 >= self.move_lists.len() || !self.move_lists[iter.0].is_empty() { - break; - } - - iter.0 += 1; - } - - if let Some(move_list) = self.move_lists.get(iter.0) { - let next_move = move_list[iter.1].clone(); - - iter.1 += 1; - if iter.1 >= move_list.len() { - // Increment the list index here. On the next iteration, find the next non-empty list. - iter.0 += 1; - iter.1 = 0; - } - - Some(next_move) - } else { - None - } + ((parameters.left_capture_shift)(&from_square) + | (parameters.right_capture_shift)(&from_square)) + & (opponent_pieces | en_passant_square) } } #[cfg(test)] mod tests { use super::*; - use crate::position::DiagramFormatter; - use crate::{piece, Position}; - use chessfriend_core::Square; + use crate::{position, position::DiagramFormatter}; + use chessfriend_core::{piece, Square}; use std::collections::HashSet; #[test] @@ -261,7 +124,7 @@ mod tests { MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build(), ]); - let generated_moves: HashSet = generator.collect(); + let generated_moves: HashSet = generator.iter().cloned().collect(); assert_eq!(generated_moves, expected_moves); } @@ -279,7 +142,7 @@ mod tests { ) .build()]); - let generated_moves: HashSet = generator.collect(); + let generated_moves: HashSet = generator.iter().cloned().collect(); assert_eq!(generated_moves, expected_moves); } @@ -302,7 +165,7 @@ mod tests { ) .build()]); - let generated_moves: HashSet = generator.collect(); + let generated_moves: HashSet = generator.iter().cloned().collect(); assert_eq!(generated_moves, expected_moves); } @@ -317,7 +180,7 @@ mod tests { let generator = PawnMoveGenerator::new(&pos, Color::White); - let generated_moves: HashSet = generator.collect(); + let generated_moves: HashSet = generator.iter().cloned().collect(); assert_eq!(generated_moves, HashSet::new()); } @@ -340,7 +203,7 @@ mod tests { .build()], ); - let generated_moves: HashSet = generator.collect(); + let generated_moves: HashSet = generator.iter().cloned().collect(); assert_eq!(generated_moves, expected_moves); } @@ -366,7 +229,7 @@ mod tests { .build(), ]); - let generated_moves: HashSet = generator.collect(); + let generated_moves: HashSet = generator.iter().cloned().collect(); assert_eq!( generated_moves, expected_moves,