[board] Rewrite the pawn move generator
I wrote it before I made the MoveGeneratorInternal trait. Found a few bugs in there too.
This commit is contained in:
parent
a73355c769
commit
54c94a93aa
1 changed files with 62 additions and 199 deletions
|
@ -3,7 +3,7 @@
|
||||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
||||||
use crate::{Move, MoveBuilder, Position};
|
use crate::{Move, MoveBuilder, Position};
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape};
|
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
||||||
|
|
||||||
enum MoveList {
|
enum MoveList {
|
||||||
Quiet = 0,
|
Quiet = 0,
|
||||||
|
@ -22,43 +22,41 @@ struct MoveGenerationParameters {
|
||||||
right_capture_shift: fn(&BitBoard) -> BitBoard,
|
right_capture_shift: fn(&BitBoard) -> BitBoard,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct PawnMoveGenerator<'pos> {
|
move_generator_declaration!(PawnMoveGenerator);
|
||||||
color: Color,
|
|
||||||
position: &'pos Position,
|
|
||||||
|
|
||||||
did_populate_move_lists: bool,
|
impl<'pos> MoveGeneratorInternal for PawnMoveGenerator<'pos> {
|
||||||
|
fn piece(color: Color) -> Piece {
|
||||||
|
Piece::pawn(color)
|
||||||
|
}
|
||||||
|
|
||||||
pushes: BitBoard,
|
fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
|
||||||
attacks: BitBoard,
|
let from_square = placed_piece.square();
|
||||||
|
let parameters = Self::move_generation_parameters(placed_piece.color());
|
||||||
|
|
||||||
move_lists: [Vec<Move>; 3],
|
let opposing_pieces = position.opposing_pieces();
|
||||||
move_iterator: MoveIterator,
|
|
||||||
|
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> {
|
impl<'pos> PawnMoveGenerator<'pos> {
|
||||||
pub(super) fn new(position: &Position, color: Color) -> PawnMoveGenerator {
|
fn move_generation_parameters(color: Color) -> MoveGenerationParameters {
|
||||||
PawnMoveGenerator {
|
match color {
|
||||||
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<Item = &Move> + '_ {
|
|
||||||
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 {
|
|
||||||
Color::White => MoveGenerationParameters {
|
Color::White => MoveGenerationParameters {
|
||||||
starting_rank: BitBoard::rank(1),
|
starting_rank: BitBoard::rank(1),
|
||||||
promotion_rank: BitBoard::rank(7),
|
promotion_rank: BitBoard::rank(7),
|
||||||
|
@ -76,178 +74,43 @@ impl<'pos> PawnMoveGenerator<'pos> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
fn pushes(
|
||||||
fn quiet_move_list(&mut self) -> &mut Vec<Move> {
|
position: &Position,
|
||||||
&mut self.move_lists[MoveList::Quiet as usize]
|
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 attacks(
|
||||||
fn promotion_move_list(&mut self) -> &mut Vec<Move> {
|
position: &Position,
|
||||||
&mut self.move_lists[MoveList::Promotions as usize]
|
piece: PlacedPiece,
|
||||||
}
|
parameters: &MoveGenerationParameters,
|
||||||
|
) -> BitBoard {
|
||||||
|
let color = piece.color();
|
||||||
|
|
||||||
#[inline]
|
let opponent_pieces = position.bitboard_for_color(color.other());
|
||||||
fn capture_move_list(&mut self) -> &mut Vec<Move> {
|
let en_passant_square = position
|
||||||
&mut self.move_lists[MoveList::Captures as usize]
|
.en_passant_square()
|
||||||
}
|
.map(|square| <Square as Into<BitBoard>>::into(square))
|
||||||
}
|
.unwrap_or(BitBoard::empty());
|
||||||
|
|
||||||
impl<'pos> PawnMoveGenerator<'pos> {
|
let from_square: BitBoard = piece.square().into();
|
||||||
fn generate_move_bitboards(&mut self) {
|
|
||||||
let parameters = self.move_generation_parameters();
|
|
||||||
self.generate_pushes_bitboard(¶meters);
|
|
||||||
self.generate_attacks_bitboard(¶meters);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_pushes_bitboard(&mut self, parameters: &MoveGenerationParameters) {
|
((parameters.left_capture_shift)(&from_square)
|
||||||
let empty_squares = self.position.empty_squares();
|
| (parameters.right_capture_shift)(&from_square))
|
||||||
let bb = self.position.bitboard_for_piece(Piece::pawn(self.color));
|
& (opponent_pieces | en_passant_square)
|
||||||
|
|
||||||
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<Move> {
|
|
||||||
// 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<Self::Item> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::position::DiagramFormatter;
|
use crate::{position, position::DiagramFormatter};
|
||||||
use crate::{piece, Position};
|
use chessfriend_core::{piece, Square};
|
||||||
use chessfriend_core::Square;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -261,7 +124,7 @@ mod tests {
|
||||||
MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build(),
|
MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.collect();
|
let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
|
||||||
|
|
||||||
assert_eq!(generated_moves, expected_moves);
|
assert_eq!(generated_moves, expected_moves);
|
||||||
}
|
}
|
||||||
|
@ -279,7 +142,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.build()]);
|
.build()]);
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.collect();
|
let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
|
||||||
|
|
||||||
assert_eq!(generated_moves, expected_moves);
|
assert_eq!(generated_moves, expected_moves);
|
||||||
}
|
}
|
||||||
|
@ -302,7 +165,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.build()]);
|
.build()]);
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.collect();
|
let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
|
||||||
|
|
||||||
assert_eq!(generated_moves, expected_moves);
|
assert_eq!(generated_moves, expected_moves);
|
||||||
}
|
}
|
||||||
|
@ -317,7 +180,7 @@ mod tests {
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.collect();
|
let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
|
||||||
|
|
||||||
assert_eq!(generated_moves, HashSet::new());
|
assert_eq!(generated_moves, HashSet::new());
|
||||||
}
|
}
|
||||||
|
@ -340,7 +203,7 @@ mod tests {
|
||||||
.build()],
|
.build()],
|
||||||
);
|
);
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.collect();
|
let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
|
||||||
|
|
||||||
assert_eq!(generated_moves, expected_moves);
|
assert_eq!(generated_moves, expected_moves);
|
||||||
}
|
}
|
||||||
|
@ -366,7 +229,7 @@ mod tests {
|
||||||
.build(),
|
.build(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.collect();
|
let generated_moves: HashSet<Move> = generator.iter().cloned().collect();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generated_moves, expected_moves,
|
generated_moves, expected_moves,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue