[moves] Bug: En passant moves were generated when no pawn was available to capture
Found another bug in the pawn move generator related to en passant moves. The generator was emitting e.p. captures even when no pawn was available to capture on that target square. The solution was to include the e.p. square in the enemies list, effectively treating the e.p. target as if it were occupied by an enemy piece, and then remove it from the capture bitboards before move generation. Include a couple tests to exercise this functionality.
This commit is contained in:
parent
6b5a54f6b4
commit
6cca3a0f52
1 changed files with 46 additions and 3 deletions
|
@ -46,10 +46,26 @@ impl PawnMoveGenerator {
|
|||
let empty = !occupied;
|
||||
let enemies = board.enemies(color);
|
||||
|
||||
let (single_pushes, double_pushes) = Self::pushes(pawns, color, empty);
|
||||
let (left_captures, right_captures) = Self::captures(pawns, color, enemies);
|
||||
// En passant captures present a particular challenge. Include the
|
||||
// target e.p. square when computing captures (i.e. treat it like an
|
||||
// enemy piece is on that square) but do not include it when generating
|
||||
// capture moves. If it is included, a regular capture move will be
|
||||
// generated where a special e.p. move should be created instead.
|
||||
//
|
||||
// So, include it in the enemies set when computing captures, then
|
||||
// remove it from the left and right captures bitboards before passing
|
||||
// them into the move generator. Additionally, include the target e.p.
|
||||
// square in the e.p. bitboard iff the capture bitboards include it.
|
||||
|
||||
let en_passant: BitBoard = board.en_passant_target().into();
|
||||
|
||||
let (single_pushes, double_pushes) = Self::pushes(pawns, color, empty);
|
||||
let (left_captures, right_captures) = Self::captures(pawns, color, enemies | en_passant);
|
||||
|
||||
let en_passant = en_passant & (left_captures | right_captures);
|
||||
let left_captures = left_captures & !en_passant;
|
||||
let right_captures = right_captures & !en_passant;
|
||||
|
||||
Self {
|
||||
color,
|
||||
single_pushes,
|
||||
|
@ -241,7 +257,7 @@ impl MoveType {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{assert_move_list, ply, Move};
|
||||
use crate::{assert_move_list, assert_move_list_does_not_contain, ply, Move};
|
||||
use chessfriend_board::test_board;
|
||||
use chessfriend_core::{Color, Square};
|
||||
use std::collections::HashSet;
|
||||
|
@ -509,4 +525,31 @@ mod tests {
|
|||
|
||||
assert_move_list!(generated_moves, [ply!(E5 - E6), ply!(E5 x F6 e.p.),]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_no_en_passant_if_no_pawn() {
|
||||
let board = test_board!(White, [
|
||||
White Pawn on A3,
|
||||
Black Pawn on F5,
|
||||
], F6);
|
||||
|
||||
let generated_moves: HashSet<_> = PawnMoveGenerator::new(&board, None).collect();
|
||||
|
||||
assert_move_list_does_not_contain!(
|
||||
generated_moves,
|
||||
[ply!(E5 x F6 e.p.), ply!(G5 x F6 e.p.)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn black_no_en_passant_if_no_pawn() {
|
||||
let board = test_board!(Black, [
|
||||
White Pawn on A4,
|
||||
Black Pawn on D4,
|
||||
], A3);
|
||||
|
||||
let generated_moves: HashSet<_> = PawnMoveGenerator::new(&board, None).collect();
|
||||
|
||||
assert_move_list_does_not_contain!(generated_moves, [ply!(B4 x A3 e.p.)]);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue