From 8f07e08500d7ac67493a69e4a2bd5a6b85ec8857 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 14:51:25 -0800 Subject: [PATCH] Fix a couple of the obscure en passant cases from PEJ --- position/src/move_generator.rs | 2 +- position/src/move_generator/pawn.rs | 73 +++++++++++++++++-- .../move_generator/tests/peterellisjones.rs | 22 +++--- 3 files changed, 77 insertions(+), 20 deletions(-) diff --git a/position/src/move_generator.rs b/position/src/move_generator.rs index 4d14ece..c4271ce 100644 --- a/position/src/move_generator.rs +++ b/position/src/move_generator.rs @@ -56,7 +56,7 @@ macro_rules! move_generator_declaration { push_mask: chessfriend_bitboard::BitBoard, ) -> $name { let move_sets = if Self::shape() == chessfriend_core::Shape::King - || (!capture_mask.is_empty() && !push_mask.is_empty()) + || !(capture_mask.is_empty() && push_mask.is_empty()) { Self::move_sets(position, color, capture_mask, push_mask) } else { diff --git a/position/src/move_generator/pawn.rs b/position/src/move_generator/pawn.rs index e5db4cf..1a07c4c 100644 --- a/position/src/move_generator/pawn.rs +++ b/position/src/move_generator/pawn.rs @@ -37,7 +37,9 @@ impl MoveGeneratorInternal for PawnMoveGenerator { .quiet_moves(quiet_moves) .capture_moves(capture_moves); - if let Some(en_passant) = Self::en_passant(position, placed_piece) { + if let Some(en_passant) = + Self::en_passant(position, placed_piece, &push_mask, &capture_mask) + { move_set.en_passant(en_passant); } @@ -52,7 +54,7 @@ impl PawnMoveGenerator { capture_mask: BitBoard, push_mask: BitBoard, ) -> 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, player_to_move, capture_mask, push_mask) } else { std::collections::BTreeMap::new() @@ -121,14 +123,26 @@ impl PawnMoveGenerator { BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces } - fn en_passant(position: &Position, piece: &PlacedPiece) -> Option { + fn en_passant( + position: &Position, + piece: &PlacedPiece, + push_mask: &BitBoard, + capture_mask: &BitBoard, + ) -> Option { match position.en_passant() { Some(en_passant) => { - let target_square = en_passant.target_square(); + let target_square: BitBoard = en_passant.target_square().into(); + let capture_square: BitBoard = en_passant.capture_square().into(); - let en_passant_bitboard: BitBoard = target_square.into(); - let capture = - BitBoard::pawn_attacks(piece.square(), piece.color()) & en_passant_bitboard; + if (target_square & push_mask).is_empty() + && (capture_square & capture_mask).is_empty() + { + // Do not allow en passant if capturing would not either + // block an active check, or capture a checking pawn. + return None; + } + + let capture = BitBoard::pawn_attacks(piece.square(), piece.color()) & target_square; if capture.is_empty() { return None; } @@ -141,12 +155,28 @@ impl PawnMoveGenerator { None => None, } } + + #[cfg(none)] + fn does_en_passant_reveal_check(&self, position: &Position) -> bool { + let player_to_move = position.player_to_move(); + let opposing_player = player_to_move.other(); + + if position.king_square(opposing_player).rank() + != Rank::PAWN_DOUBLE_PUSH_TARGET_RANKS[player_to_move as usize] + { + return false; + } + false + } } #[cfg(test)] mod tests { use super::*; - use crate::{assert_move_list, position::DiagramFormatter, test_position, testing::*}; + use crate::{ + assert_move_list, formatted_move_list, position::DiagramFormatter, test_position, + testing::*, + }; use chessfriend_core::{piece, Color, Square}; use chessfriend_moves::{Builder as MoveBuilder, Move}; use std::collections::HashSet; @@ -292,4 +322,31 @@ mod tests { Ok(()) } + + /// Make sure the player cannot capture en passant if doing so would not resolve the check. + #[test] + fn cannot_capture_en_passant_while_in_check() -> TestResult { + let pos = test_position!(Black, [ + Black King on B5, + Black Pawn on E4, + White Pawn on D4, + White Rook on B1, + ], D3); + + assert!(pos.is_king_in_check()); + + let generated_moves: HashSet<_> = pos.moves().iter().collect(); + + assert!( + !generated_moves.contains( + &MoveBuilder::push(&piece!(Black Pawn on E4)) + .capturing_en_passant_on(Square::D3) + .build()? + ), + "Valid moves: {:?}", + formatted_move_list!(generated_moves, pos) + ); + + Ok(()) + } } diff --git a/position/src/move_generator/tests/peterellisjones.rs b/position/src/move_generator/tests/peterellisjones.rs index 31a878a..4a12b92 100644 --- a/position/src/move_generator/tests/peterellisjones.rs +++ b/position/src/move_generator/tests/peterellisjones.rs @@ -145,12 +145,12 @@ fn en_passant_check_capture() -> TestResult { assert!(pos.is_king_in_check()); - let generated_moves = pos.moves().iter().collect::>(); + let generated_moves: HashSet<_> = pos.moves().iter().collect(); assert!( generated_moves.contains( &MoveBuilder::push(&piece!(Black Pawn on E4)) - .capturing_en_passant_on(Square::D4) + .capturing_en_passant_on(Square::D3) .build()? ), "Valid moves: {:?}", @@ -171,12 +171,12 @@ fn en_passant_check_block() -> TestResult { assert!(pos.is_king_in_check()); - let generated_moves = pos.moves().iter().collect::>(); + let generated_moves: HashSet<_> = pos.moves().iter().collect(); assert!( generated_moves.contains( &MoveBuilder::push(&piece!(Black Pawn on E4)) - .capturing_en_passant_on(Square::D4) + .capturing_en_passant_on(Square::D3) .build()? ), "Valid moves: {:?}", @@ -187,7 +187,7 @@ fn en_passant_check_block() -> TestResult { } #[test] -fn pinned_pieces_rook_cannot_move_out_of_pin() -> Result<(), String> { +fn pinned_pieces_rook_cannot_move_out_of_pin() -> TestResult { let pos = test_position!(Black, [ Black King on E8, Black Rook on E6, @@ -200,15 +200,15 @@ fn pinned_pieces_rook_cannot_move_out_of_pin() -> Result<(), String> { let generated_moves = pos.moves(); let rook_moves = generated_moves .moves_for_piece(&piece!(Black Rook on E6)) - .ok_or("No valid rook moves")?; + .ok_or(TestError::NoLegalMoves)?; assert!(!rook_moves.can_move_to_square(Square::D6)); assert!(!rook_moves.can_move_to_square(Square::F6)); - assert!(rook_moves.can_move_to_square(Square::E7)); - assert!(rook_moves.can_move_to_square(Square::E5)); - assert!(rook_moves.can_move_to_square(Square::E4)); assert!(rook_moves.can_move_to_square(Square::E3)); + assert!(rook_moves.can_move_to_square(Square::E4)); + assert!(rook_moves.can_move_to_square(Square::E5)); + assert!(rook_moves.can_move_to_square(Square::E7)); Ok(()) } @@ -222,10 +222,10 @@ fn en_passant_discovered_check() -> TestResult { White Queen on H4, ], D3); - let generated_moves = pos.moves().iter().collect::>(); + let generated_moves: HashSet<_> = pos.moves().iter().collect(); let unexpected_move = MoveBuilder::push(&piece!(Black Pawn on E4)) - .capturing_en_passant_on(Square::D4) + .capturing_en_passant_on(Square::D3) .build()?; assert!(