[board] Add a test for capturing en passant; fix the bugs

I wrote a test for capturing en passant that revealed some bugs. Cool!

Implement the missing part of move validation that checks for an en passant move.
Implement PositionBuilder::to_move() to set player_to_move.

The most difficult part of this fix was finding the logic error in
Move::is_en_passant(). Instead of checking for non-zero, check for equality against
the en passant flag value. Checking for non-zero was causing this method to return
true in the double push case.
This commit is contained in:
Eryn Wells 2024-01-21 11:21:37 -08:00
parent 683d89b726
commit f90ea2d1be
3 changed files with 69 additions and 3 deletions

View file

@ -193,7 +193,7 @@ impl Move {
}
pub fn is_en_passant(&self) -> bool {
(self.0 & 0b0101) != 0
(self.0 & 0b0101) == 0b0101
}
pub fn is_promotion(&self) -> bool {
@ -272,6 +272,11 @@ impl MoveBuilder {
self
}
pub fn capturing_en_passant(mut self, captured_piece: PlacedPiece) -> Self {
self.kind = Kind::EnPassantCapture(captured_piece);
self
}
pub fn promoting_to(mut self, shape: Shape) -> Self {
if let Some(shape) = PromotableShape::try_from(shape).ok() {
self.kind = match self.kind {

View file

@ -63,7 +63,20 @@ where
let to_square = mv.to_square();
let player = self.position.player_to_move();
let captured_piece: Option<PlacedPiece> = if mv.is_capture() {
let captured_piece: Option<PlacedPiece> = if mv.is_en_passant() {
// En passant captures the pawn directly ahead (in the player's direction) of the en passant square.
let capture_square = match player {
Color::White => to_square.neighbor(Direction::South),
Color::Black => to_square.neighbor(Direction::North),
}
.ok_or(MakeMoveError::NoCapturedPiece)?;
Some(
self.position
.piece_on_square(capture_square)
.ok_or(MakeMoveError::NoCapturedPiece)?,
)
} else if mv.is_capture() {
Some(
self.position
.piece_on_square(to_square)
@ -207,7 +220,7 @@ impl<'p> From<&'p Position> for Builder<'p, NoMove> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{r#move::Castle, MoveBuilder};
use crate::{r#move::Castle, MoveBuilder, PositionBuilder};
#[test]
fn move_white_pawn_one_square() -> Result<(), MakeMoveError> {
@ -272,4 +285,47 @@ mod tests {
Ok(())
}
#[test]
fn en_passant_capture() -> Result<(), MakeMoveError> {
let pos = PositionBuilder::new()
.place_piece(piece!(White Pawn on B5))
.place_piece(piece!(Black Pawn on A7))
.to_move(Color::Black)
.build();
println!("{pos}");
let black_pawn_move = MoveBuilder::new(piece!(Black Pawn), Square::A7, Square::A5).build();
assert!(black_pawn_move.is_double_push());
assert!(!black_pawn_move.is_en_passant());
let en_passant_position = Builder::<NoMove>::new(&pos).make(&black_pawn_move)?.build();
println!("{en_passant_position}");
assert_eq!(
en_passant_position.piece_on_square(Square::A5),
Some(piece!(Black Pawn on A5))
);
assert_eq!(
en_passant_position.piece_on_square(Square::B5),
Some(piece!(White Pawn on B5))
);
let white_pawn_capture = MoveBuilder::new(piece!(White Pawn), Square::B5, Square::A6)
.capturing_en_passant(piece!(Black Pawn on A5))
.build();
let en_passant_capture = Builder::<NoMove>::new(&en_passant_position)
.make(&white_pawn_capture)?
.build();
println!("{en_passant_capture}");
assert_eq!(en_passant_capture.piece_on_square(Square::A5), None);
assert_eq!(en_passant_capture.piece_on_square(Square::B5), None);
assert_eq!(
en_passant_capture.piece_on_square(Square::A6),
Some(piece!(White Pawn on A6))
);
Ok(())
}
}

View file

@ -23,6 +23,11 @@ impl Builder {
Self::default()
}
pub fn to_move(&mut self, player: Color) -> &mut Self {
self.player_to_move = player;
self
}
pub fn place_piece(&mut self, piece: PlacedPiece) -> &mut Self {
let square = piece.square();