From f90ea2d1be923550f0e549eefa7bbbf9bdb1b90c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 21 Jan 2024 11:21:37 -0800 Subject: [PATCH] [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. --- board/src/move.rs | 7 ++- board/src/position/builders/move_builder.rs | 60 ++++++++++++++++++- .../src/position/builders/position_builder.rs | 5 ++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/board/src/move.rs b/board/src/move.rs index 3e48c50..4f06000 100644 --- a/board/src/move.rs +++ b/board/src/move.rs @@ -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 { diff --git a/board/src/position/builders/move_builder.rs b/board/src/position/builders/move_builder.rs index 97802c1..e0d2765 100644 --- a/board/src/position/builders/move_builder.rs +++ b/board/src/position/builders/move_builder.rs @@ -63,7 +63,20 @@ where let to_square = mv.to_square(); let player = self.position.player_to_move(); - let captured_piece: Option = if mv.is_capture() { + let captured_piece: Option = 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::::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::::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(()) + } } diff --git a/board/src/position/builders/position_builder.rs b/board/src/position/builders/position_builder.rs index d9c0a18..c99625b 100644 --- a/board/src/position/builders/position_builder.rs +++ b/board/src/position/builders/position_builder.rs @@ -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();