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>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use crate::Color;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
@ -168,6 +169,16 @@ impl Rank {
|
||||||
/// assert_eq!(Rank::PAWN_STARTING_RANKS[Color::Black as usize], Rank::SEVEN);
|
/// 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_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]
|
#[rustfmt::skip]
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use chessfriend_core::{Rank, Square};
|
use chessfriend_core::{Rank, Square};
|
||||||
|
|
||||||
/// En passant information.
|
/// En passant information.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub struct EnPassant {
|
pub struct EnPassant {
|
||||||
target: Square,
|
target: Square,
|
||||||
capture: Square,
|
capture: Square,
|
||||||
|
@ -39,10 +39,12 @@ impl EnPassant {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The square the capturing piece will move to.
|
||||||
pub fn target_square(&self) -> Square {
|
pub fn target_square(&self) -> Square {
|
||||||
self.target
|
self.target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The square on which the captured pawn sits.
|
||||||
pub fn capture_square(&self) -> Square {
|
pub fn capture_square(&self) -> Square {
|
||||||
self.capture
|
self.capture
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_core::{PlacedPiece, Square};
|
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.
|
/// A set of bitboards defining the moves for a single piece on the board.
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
@ -11,12 +11,18 @@ struct BitBoardSet {
|
||||||
captures: 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.
|
/// A set of moves for a single piece on the board.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub(crate) struct MoveSet {
|
pub(crate) struct MoveSet {
|
||||||
piece: PlacedPiece,
|
piece: PlacedPiece,
|
||||||
bitboards: BitBoardSet,
|
bitboards: BitBoardSet,
|
||||||
special: u8,
|
special: Option<Special>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MoveSet {
|
impl MoveSet {
|
||||||
|
@ -24,7 +30,7 @@ impl MoveSet {
|
||||||
MoveSet {
|
MoveSet {
|
||||||
piece,
|
piece,
|
||||||
bitboards: BitBoardSet::default(),
|
bitboards: BitBoardSet::default(),
|
||||||
special: 0,
|
special: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,9 +39,9 @@ impl MoveSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn can_castle(&self, castle: Castle) -> bool {
|
pub(crate) fn can_castle(&self, castle: Castle) -> bool {
|
||||||
match castle {
|
match self.special {
|
||||||
Castle::KingSide => (self.special & 0b1) != 0,
|
Some(Special::King { castles }) => (castles & 1 << castle as u8) != 0,
|
||||||
Castle::QueenSide => (self.special & 0b10) != 0,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,49 +56,99 @@ impl MoveSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn kingside_castle(&mut self) -> &mut 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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn queenside_castle(&mut self) -> &mut MoveSet {
|
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
|
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 {
|
pub(super) fn bitboard(&self) -> BitBoard {
|
||||||
self.bitboards.captures | self.bitboards.quiet
|
self.bitboards.captures | self.bitboards.quiet
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn moves(&self) -> impl Iterator<Item = Move> + '_ {
|
pub(crate) fn moves(&self) -> impl Iterator<Item = Move> + '_ {
|
||||||
let piece = &self.piece;
|
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
|
self.bitboards
|
||||||
.quiet
|
.quiet
|
||||||
.occupied_squares()
|
.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(
|
.chain(
|
||||||
self.bitboards
|
self.bitboards
|
||||||
.captures
|
.captures
|
||||||
.occupied_squares()
|
.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(
|
.chain(self.castle_move(Castle::KingSide))
|
||||||
if (self.special & 0b1) != 0 {
|
.chain(self.castle_move(Castle::QueenSide))
|
||||||
let mv = MoveBuilder::castling(Castle::KingSide).build();
|
.chain(self.en_passant_move())
|
||||||
Some(mv)
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
None
|
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 crate::Position;
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square};
|
use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square};
|
||||||
use chessfriend_moves::{Builder as MoveBuilder, Move};
|
use chessfriend_moves::{EnPassant, Move};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -33,27 +33,33 @@ impl MoveGeneratorInternal for PawnMoveGenerator {
|
||||||
let capture_moves = Self::attacks(position, &placed_piece) & capture_mask;
|
let capture_moves = Self::attacks(position, &placed_piece) & capture_mask;
|
||||||
let quiet_moves = Self::pushes(position, &placed_piece) & push_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)
|
.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 {
|
impl PawnMoveGenerator {
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
position: &Position,
|
position: &Position,
|
||||||
color: Color,
|
player_to_move: Color,
|
||||||
capture_mask: BitBoard,
|
capture_mask: BitBoard,
|
||||||
push_mask: BitBoard,
|
push_mask: BitBoard,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let move_sets = if !capture_mask.is_empty() && !push_mask.is_empty() {
|
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 {
|
} else {
|
||||||
std::collections::BTreeMap::new()
|
std::collections::BTreeMap::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
color,
|
color: player_to_move,
|
||||||
move_sets,
|
move_sets,
|
||||||
en_passant_captures: Vec::new(),
|
en_passant_captures: Vec::new(),
|
||||||
}
|
}
|
||||||
|
@ -115,7 +121,7 @@ impl PawnMoveGenerator {
|
||||||
BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces
|
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() {
|
match position.en_passant() {
|
||||||
Some(en_passant) => {
|
Some(en_passant) => {
|
||||||
let target_square = en_passant.target_square();
|
let target_square = en_passant.target_square();
|
||||||
|
@ -128,12 +134,7 @@ impl PawnMoveGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
match position.piece_on_square(en_passant.capture_square()) {
|
match position.piece_on_square(en_passant.capture_square()) {
|
||||||
Some(_) => Some(
|
Some(_) => Some(en_passant),
|
||||||
MoveBuilder::push(piece)
|
|
||||||
.capturing_en_passant_on(target_square)
|
|
||||||
.build()
|
|
||||||
.ok()?,
|
|
||||||
),
|
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,7 +165,7 @@ mod tests {
|
||||||
|
|
||||||
let generated_moves: HashSet<_> = generator.iter().collect();
|
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||||
|
|
||||||
assert_eq!(generated_moves, expected_moves);
|
assert_move_list!(generated_moves, expected_moves, pos);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -278,15 +279,12 @@ mod tests {
|
||||||
Black Pawn on E4,
|
Black Pawn on E4,
|
||||||
], D3);
|
], 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 generated_moves: HashSet<Move> = generator.iter().collect();
|
||||||
|
|
||||||
let builder = MoveBuilder::push(&piece!(Black Pawn on E4));
|
let builder = MoveBuilder::push(&piece!(Black Pawn on E4));
|
||||||
let expected_moves = HashSet::from_iter([
|
let expected_moves = HashSet::from_iter([
|
||||||
builder
|
builder.capturing_en_passant_on(Square::D3).build()?,
|
||||||
.clone()
|
|
||||||
.capturing_en_passant_on(Square::D3)
|
|
||||||
.build()?,
|
|
||||||
builder.clone().to(Square::E3).build()?,
|
builder.clone().to(Square::E3).build()?,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue