Fix a couple of the obscure en passant cases from PEJ

This commit is contained in:
Eryn Wells 2024-02-25 14:51:25 -08:00
parent 673d57c02e
commit 8f07e08500
3 changed files with 77 additions and 20 deletions

View file

@ -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 {

View file

@ -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<EnPassant> {
fn en_passant(
position: &Position,
piece: &PlacedPiece,
push_mask: &BitBoard,
capture_mask: &BitBoard,
) -> Option<EnPassant> {
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(())
}
}

View file

@ -145,12 +145,12 @@ fn en_passant_check_capture() -> TestResult {
assert!(pos.is_king_in_check());
let generated_moves = pos.moves().iter().collect::<HashSet<_>>();
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::<HashSet<_>>();
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::<HashSet<_>>();
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!(