Fix the pawn unit tests

This commit is contained in:
Eryn Wells 2024-02-25 10:51:27 -08:00
parent 63c03fb879
commit 2a6b098cb8
4 changed files with 112 additions and 45 deletions

View file

@ -1,5 +1,6 @@
// Eryn Wells <eryn@erynwells.me>
use crate::Color;
use std::fmt;
use std::str::FromStr;
@ -168,6 +169,16 @@ impl Rank {
/// assert_eq!(Rank::PAWN_STARTING_RANKS[Color::Black as usize], Rank::SEVEN);
/// ```
pub const PAWN_STARTING_RANKS: [Rank; 2] = [Rank::TWO, Rank::SEVEN];
pub const PAWN_DOUBLE_PUSH_TARGET_RANKS: [Rank; 2] = [Rank::FOUR, Rank::FIVE];
pub fn is_pawn_starting_rank(&self, color: Color) -> bool {
self == &Self::PAWN_STARTING_RANKS[color as usize]
}
pub fn is_pawn_double_push_target_rank(&self, color: Color) -> bool {
self == &Self::PAWN_DOUBLE_PUSH_TARGET_RANKS[color as usize]
}
}
#[rustfmt::skip]

View file

@ -3,7 +3,7 @@
use chessfriend_core::{Rank, Square};
/// En passant information.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EnPassant {
target: Square,
capture: Square,
@ -39,10 +39,12 @@ impl EnPassant {
}
}
/// The square the capturing piece will move to.
pub fn target_square(&self) -> Square {
self.target
}
/// The square on which the captured pawn sits.
pub fn capture_square(&self) -> Square {
self.capture
}

View file

@ -2,7 +2,7 @@
use chessfriend_bitboard::BitBoard;
use chessfriend_core::{PlacedPiece, Square};
use chessfriend_moves::{Builder as MoveBuilder, Castle, Move};
use chessfriend_moves::{Builder as MoveBuilder, Castle, EnPassant, Move};
/// A set of bitboards defining the moves for a single piece on the board.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
@ -11,12 +11,18 @@ struct BitBoardSet {
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: u8,
special: Option<Special>,
}
impl MoveSet {
@ -24,7 +30,7 @@ impl MoveSet {
MoveSet {
piece,
bitboards: BitBoardSet::default(),
special: 0,
special: None,
}
}
@ -33,9 +39,9 @@ impl MoveSet {
}
pub(crate) fn can_castle(&self, castle: Castle) -> bool {
match castle {
Castle::KingSide => (self.special & 0b1) != 0,
Castle::QueenSide => (self.special & 0b10) != 0,
match self.special {
Some(Special::King { castles }) => (castles & 1 << castle as u8) != 0,
_ => false,
}
}
@ -50,49 +56,99 @@ impl MoveSet {
}
pub(super) fn kingside_castle(&mut self) -> &mut MoveSet {
self.special |= 0b1;
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 {
self.special |= 0b10;
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
}
/// Return a BitBoard representing all possible moves.
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(|to_square| MoveBuilder::push(&self.piece).to(to_square).build().ok())
.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).to(to_square).build().ok()),
.filter_map(|to_square| {
MoveBuilder::push(piece)
.capturing_on(to_square)
.build()
.ok()
}),
)
.chain(
if (self.special & 0b1) != 0 {
let mv = MoveBuilder::castling(Castle::KingSide).build();
Some(mv)
.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(castle).build())
} else {
None
}
.into_iter(),
)
.chain(
if (self.special & 0b10) != 0 {
Some(MoveBuilder::castling(Castle::QueenSide).build())
} else {
None
}
.into_iter(),
)
}
_ => 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,
}
}
}

View file

@ -4,7 +4,7 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use crate::Position;
use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square};
use chessfriend_moves::{Builder as MoveBuilder, Move};
use chessfriend_moves::{EnPassant, Move};
use std::collections::BTreeMap;
#[derive(Debug)]
@ -33,27 +33,33 @@ impl MoveGeneratorInternal for PawnMoveGenerator {
let capture_moves = Self::attacks(position, &placed_piece) & capture_mask;
let quiet_moves = Self::pushes(position, &placed_piece) & push_mask;
MoveSet::new(*placed_piece)
let mut move_set = MoveSet::new(*placed_piece)
.quiet_moves(quiet_moves)
.capture_moves(capture_moves)
.capture_moves(capture_moves);
if let Some(en_passant) = Self::en_passant(position, placed_piece) {
move_set.en_passant(en_passant);
}
move_set
}
}
impl PawnMoveGenerator {
pub(super) fn new(
position: &Position,
color: Color,
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(position, color, capture_mask, push_mask)
Self::move_sets(position, player_to_move, capture_mask, push_mask)
} else {
std::collections::BTreeMap::new()
};
Self {
color,
color: player_to_move,
move_sets,
en_passant_captures: Vec::new(),
}
@ -115,7 +121,7 @@ impl PawnMoveGenerator {
BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces
}
fn en_passant_attack(position: &Position, piece: &PlacedPiece) -> Option<Move> {
fn en_passant(position: &Position, piece: &PlacedPiece) -> Option<EnPassant> {
match position.en_passant() {
Some(en_passant) => {
let target_square = en_passant.target_square();
@ -128,12 +134,7 @@ impl PawnMoveGenerator {
}
match position.piece_on_square(en_passant.capture_square()) {
Some(_) => Some(
MoveBuilder::push(piece)
.capturing_en_passant_on(target_square)
.build()
.ok()?,
),
Some(_) => Some(en_passant),
None => None,
}
}
@ -164,7 +165,7 @@ mod tests {
let generated_moves: HashSet<_> = generator.iter().collect();
assert_eq!(generated_moves, expected_moves);
assert_move_list!(generated_moves, expected_moves, pos);
Ok(())
}
@ -278,15 +279,12 @@ mod tests {
Black Pawn on E4,
], D3);
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
let generator = PawnMoveGenerator::new(&pos, 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
.clone()
.capturing_en_passant_on(Square::D3)
.build()?,
builder.capturing_en_passant_on(Square::D3).build()?,
builder.clone().to(Square::E3).build()?,
]);