Fix the pawn unit tests
This commit is contained in:
parent
63c03fb879
commit
2a6b098cb8
4 changed files with 112 additions and 45 deletions
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()?,
|
||||
]);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue