[moves] Implement promotions and en passant in the PawnMoveGenerator
Add another sub-iterator to the PawnMoveGenerator that produces promotion pushes or capture moves (depending on move_type) when a pawn moves to the back rank. Also implement en passant moves. Fix a bug in the Left and Right captures branches where the wrong neighbors used to calculate origin squares. Add a whole bunch of tests. Still missing several cases though.
This commit is contained in:
parent
ab587f379f
commit
09bf17d66b
4 changed files with 305 additions and 14 deletions
|
@ -24,6 +24,17 @@ pub enum PromotionShape {
|
||||||
Queen = 0b11,
|
Queen = 0b11,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PromotionShape {
|
||||||
|
pub const NUM: usize = 4;
|
||||||
|
|
||||||
|
pub const ALL: [PromotionShape; PromotionShape::NUM] = [
|
||||||
|
PromotionShape::Knight,
|
||||||
|
PromotionShape::Bishop,
|
||||||
|
PromotionShape::Rook,
|
||||||
|
PromotionShape::Queen,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
impl From<PromotionShape> for Shape {
|
impl From<PromotionShape> for Shape {
|
||||||
fn from(value: PromotionShape) -> Self {
|
fn from(value: PromotionShape) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
|
|
@ -6,7 +6,13 @@ pub use pawn::PawnMoveGenerator;
|
||||||
|
|
||||||
use crate::Move;
|
use crate::Move;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct GeneratedMove {
|
pub struct GeneratedMove {
|
||||||
pub(crate) ply: Move,
|
pub(crate) ply: Move,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Move> for GeneratedMove {
|
||||||
|
fn from(value: Move) -> Self {
|
||||||
|
GeneratedMove { ply: value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
|
|
||||||
//! Implements a move generator for pawns.
|
//! Implements a move generator for pawns.
|
||||||
|
|
||||||
use crate::Move;
|
|
||||||
|
|
||||||
use super::GeneratedMove;
|
use super::GeneratedMove;
|
||||||
|
use crate::{Move, PromotionShape};
|
||||||
use chessfriend_bitboard::{bit_scanner::TrailingBitScanner, BitBoard};
|
use chessfriend_bitboard::{bit_scanner::TrailingBitScanner, BitBoard};
|
||||||
use chessfriend_board::Board;
|
use chessfriend_board::Board;
|
||||||
use chessfriend_core::{Color, Direction, Rank, Square};
|
use chessfriend_core::{Color, Direction, Rank, Shape, Square};
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
pub struct PawnMoveGenerator {
|
pub struct PawnMoveGenerator {
|
||||||
color: Color,
|
color: Color,
|
||||||
|
@ -16,7 +16,8 @@ pub struct PawnMoveGenerator {
|
||||||
left_captures: BitBoard,
|
left_captures: BitBoard,
|
||||||
right_captures: BitBoard,
|
right_captures: BitBoard,
|
||||||
en_passant: BitBoard,
|
en_passant: BitBoard,
|
||||||
iterator: TrailingBitScanner,
|
target_iterator: TrailingBitScanner,
|
||||||
|
promotion_iterator: Option<PromotionIterator>,
|
||||||
move_type: MoveType,
|
move_type: MoveType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +30,12 @@ enum MoveType {
|
||||||
EnPassant,
|
EnPassant,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PromotionIterator {
|
||||||
|
origin: Square,
|
||||||
|
target: Square,
|
||||||
|
iterator: slice::Iter<'static, PromotionShape>,
|
||||||
|
}
|
||||||
|
|
||||||
impl PawnMoveGenerator {
|
impl PawnMoveGenerator {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(board: &Board, color: Option<Color>) -> Self {
|
pub fn new(board: &Board, color: Option<Color>) -> Self {
|
||||||
|
@ -50,7 +57,8 @@ impl PawnMoveGenerator {
|
||||||
left_captures,
|
left_captures,
|
||||||
right_captures,
|
right_captures,
|
||||||
en_passant,
|
en_passant,
|
||||||
iterator: single_pushes.occupied_squares_trailing(),
|
target_iterator: single_pushes.occupied_squares_trailing(),
|
||||||
|
promotion_iterator: None,
|
||||||
move_type: MoveType::SinglePushes,
|
move_type: MoveType::SinglePushes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,16 +110,28 @@ impl PawnMoveGenerator {
|
||||||
Color::Black => target.neighbor(Direction::North, Some(2)),
|
Color::Black => target.neighbor(Direction::North, Some(2)),
|
||||||
},
|
},
|
||||||
MoveType::LeftCaptures => match self.color {
|
MoveType::LeftCaptures => match self.color {
|
||||||
Color::White => target.neighbor(Direction::NorthWest, None),
|
Color::White => target.neighbor(Direction::SouthEast, None),
|
||||||
Color::Black => target.neighbor(Direction::SouthEast, None),
|
Color::Black => target.neighbor(Direction::NorthWest, None),
|
||||||
},
|
},
|
||||||
MoveType::RightCaptures => match self.color {
|
MoveType::RightCaptures => match self.color {
|
||||||
Color::White => target.neighbor(Direction::NorthEast, None),
|
Color::White => target.neighbor(Direction::SouthWest, None),
|
||||||
Color::Black => target.neighbor(Direction::SouthWest, None),
|
Color::Black => target.neighbor(Direction::NorthEast, None),
|
||||||
},
|
},
|
||||||
MoveType::EnPassant => match self.color {
|
MoveType::EnPassant => match self.color {
|
||||||
Color::White => unimplemented!(),
|
Color::White => {
|
||||||
Color::Black => unimplemented!(),
|
if (self.en_passant & self.left_captures).is_populated() {
|
||||||
|
target.neighbor(Direction::NorthWest, None)
|
||||||
|
} else {
|
||||||
|
target.neighbor(Direction::NorthEast, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Color::Black => {
|
||||||
|
if (self.en_passant & self.left_captures).is_populated() {
|
||||||
|
target.neighbor(Direction::SouthEast, None)
|
||||||
|
} else {
|
||||||
|
target.neighbor(Direction::SouthWest, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,23 +148,60 @@ impl PawnMoveGenerator {
|
||||||
MoveType::EnPassant => self.en_passant,
|
MoveType::EnPassant => self.en_passant,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.iterator = next_bitboard.occupied_squares_trailing();
|
self.target_iterator = next_bitboard.occupied_squares_trailing();
|
||||||
self.move_type = next_move_type;
|
self.move_type = next_move_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
next_move_type
|
next_move_type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_promotion_move(&mut self) -> Option<GeneratedMove> {
|
||||||
|
if let Some(promotion_iterator) = self.promotion_iterator.as_mut() {
|
||||||
|
if let Some(shape) = promotion_iterator.iterator.next() {
|
||||||
|
let origin = promotion_iterator.origin;
|
||||||
|
let target = promotion_iterator.target;
|
||||||
|
|
||||||
|
return if matches!(
|
||||||
|
self.move_type,
|
||||||
|
MoveType::LeftCaptures | MoveType::RightCaptures
|
||||||
|
) {
|
||||||
|
Some(Move::capture_promotion(origin, target, *shape).into())
|
||||||
|
} else {
|
||||||
|
Some(Move::promotion(origin, target, *shape).into())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::iter::Iterator for PawnMoveGenerator {
|
impl std::iter::Iterator for PawnMoveGenerator {
|
||||||
type Item = GeneratedMove;
|
type Item = GeneratedMove;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if let Some(target) = self.iterator.next() {
|
let next_promotion_move = self.next_promotion_move();
|
||||||
|
if next_promotion_move.is_some() {
|
||||||
|
return next_promotion_move;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.promotion_iterator = None;
|
||||||
|
|
||||||
|
if let Some(target) = self.target_iterator.next() {
|
||||||
let origin = self
|
let origin = self
|
||||||
.calculate_origin_square(target)
|
.calculate_origin_square(target)
|
||||||
.expect("Bogus origin square");
|
.expect("Bogus origin square");
|
||||||
|
|
||||||
|
if target.rank().is_promotable_rank() {
|
||||||
|
self.promotion_iterator = Some(PromotionIterator {
|
||||||
|
origin,
|
||||||
|
target,
|
||||||
|
iterator: PromotionShape::ALL.iter(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return self.next();
|
||||||
|
}
|
||||||
|
|
||||||
match self.move_type {
|
match self.move_type {
|
||||||
MoveType::SinglePushes => Some(GeneratedMove {
|
MoveType::SinglePushes => Some(GeneratedMove {
|
||||||
ply: Move::quiet(origin, target),
|
ply: Move::quiet(origin, target),
|
||||||
|
@ -178,3 +235,214 @@ impl MoveType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::Move;
|
||||||
|
use chessfriend_board::test_board;
|
||||||
|
use chessfriend_core::{Color, Square};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn black_b7_pushes_and_double_pushes() {
|
||||||
|
let board = test_board!(Black Pawn on B7);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&board, Some(Color::Black)).collect();
|
||||||
|
assert_eq!(
|
||||||
|
generated_moves,
|
||||||
|
[
|
||||||
|
GeneratedMove {
|
||||||
|
ply: Move::quiet(Square::B7, Square::B6)
|
||||||
|
},
|
||||||
|
GeneratedMove {
|
||||||
|
ply: Move::double_push(Square::B7, Square::B5)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn black_e2_pushes_and_double_pushes() {
|
||||||
|
let board = test_board!(White Pawn on E2);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&board, Some(Color::White)).collect();
|
||||||
|
assert_eq!(
|
||||||
|
generated_moves,
|
||||||
|
[
|
||||||
|
GeneratedMove {
|
||||||
|
ply: Move::quiet(Square::E2, Square::E3)
|
||||||
|
},
|
||||||
|
GeneratedMove {
|
||||||
|
ply: Move::double_push(Square::E2, Square::E4)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn black_b7_pushes_with_b5_blocker() {
|
||||||
|
let board = test_board!(Black Pawn on B7, White Bishop on B5);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&board, Some(Color::Black)).collect();
|
||||||
|
assert_eq!(
|
||||||
|
generated_moves,
|
||||||
|
[GeneratedMove {
|
||||||
|
ply: Move::quiet(Square::B7, Square::B6)
|
||||||
|
}]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn black_b7_pushes_with_b6_blocker() {
|
||||||
|
let board = test_board!(Black Pawn on B7, Black Bishop on B6);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&board, Some(Color::Black)).collect();
|
||||||
|
assert!(generated_moves.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn white_e2_moves_with_e4_blocker() {
|
||||||
|
let board = test_board!(White Pawn on E2, White Bishop on E4);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&board, Some(Color::White)).collect();
|
||||||
|
assert_eq!(
|
||||||
|
generated_moves,
|
||||||
|
[GeneratedMove {
|
||||||
|
ply: Move::quiet(Square::E2, Square::E3)
|
||||||
|
}]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn white_e2_moves_with_e3_blocker() {
|
||||||
|
let board = test_board!(White Pawn on E2, White Bishop on E3);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&board, Some(Color::White)).collect();
|
||||||
|
assert!(generated_moves.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn white_f6_left_captures() {
|
||||||
|
let white_captures_board = test_board!(White Pawn on F6, Black Queen on E7);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&white_captures_board, Some(Color::White)).collect();
|
||||||
|
assert_eq!(
|
||||||
|
generated_moves,
|
||||||
|
[
|
||||||
|
GeneratedMove {
|
||||||
|
ply: Move::capture(Square::F6, Square::E7)
|
||||||
|
},
|
||||||
|
GeneratedMove {
|
||||||
|
ply: Move::quiet(Square::F6, Square::F7)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn black_d5_left_captures() {
|
||||||
|
let black_captures_board = test_board!(Black Pawn on D5, White Queen on E4);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&black_captures_board, Some(Color::Black)).collect();
|
||||||
|
assert_eq!(
|
||||||
|
generated_moves,
|
||||||
|
[
|
||||||
|
GeneratedMove {
|
||||||
|
ply: Move::capture(Square::D5, Square::E4)
|
||||||
|
},
|
||||||
|
GeneratedMove {
|
||||||
|
ply: Move::quiet(Square::D5, Square::D4)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn white_g7_push_promotions() {
|
||||||
|
let board = test_board!(White Pawn on G7);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&board, Some(Color::White)).collect();
|
||||||
|
assert_eq!(
|
||||||
|
generated_moves,
|
||||||
|
[
|
||||||
|
Move::promotion(Square::G7, Square::G8, PromotionShape::Knight).into(),
|
||||||
|
Move::promotion(Square::G7, Square::G8, PromotionShape::Bishop).into(),
|
||||||
|
Move::promotion(Square::G7, Square::G8, PromotionShape::Rook).into(),
|
||||||
|
Move::promotion(Square::G7, Square::G8, PromotionShape::Queen).into(),
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn white_d7_push_and_capture_promotions() {
|
||||||
|
let board = test_board!(
|
||||||
|
White Pawn on D7,
|
||||||
|
Black Queen on E8
|
||||||
|
);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&board, Some(Color::White)).collect();
|
||||||
|
assert_eq!(
|
||||||
|
generated_moves,
|
||||||
|
[
|
||||||
|
Move::promotion(Square::D7, Square::D8, PromotionShape::Knight).into(),
|
||||||
|
Move::promotion(Square::D7, Square::D8, PromotionShape::Bishop).into(),
|
||||||
|
Move::promotion(Square::D7, Square::D8, PromotionShape::Rook).into(),
|
||||||
|
Move::promotion(Square::D7, Square::D8, PromotionShape::Queen).into(),
|
||||||
|
Move::capture_promotion(Square::D7, Square::E8, PromotionShape::Knight).into(),
|
||||||
|
Move::capture_promotion(Square::D7, Square::E8, PromotionShape::Bishop).into(),
|
||||||
|
Move::capture_promotion(Square::D7, Square::E8, PromotionShape::Rook).into(),
|
||||||
|
Move::capture_promotion(Square::D7, Square::E8, PromotionShape::Queen).into(),
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn black_a2_push_promotions() {
|
||||||
|
let board = test_board!(Black Pawn on A2);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&board, Some(Color::Black)).collect();
|
||||||
|
assert_eq!(
|
||||||
|
generated_moves,
|
||||||
|
[
|
||||||
|
Move::promotion(Square::A2, Square::A1, PromotionShape::Knight).into(),
|
||||||
|
Move::promotion(Square::A2, Square::A1, PromotionShape::Bishop).into(),
|
||||||
|
Move::promotion(Square::A2, Square::A1, PromotionShape::Rook).into(),
|
||||||
|
Move::promotion(Square::A2, Square::A1, PromotionShape::Queen).into(),
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn black_h2_push_and_capture_promotions() {
|
||||||
|
let board = test_board!(
|
||||||
|
Black Pawn on H2,
|
||||||
|
White Queen on G1,
|
||||||
|
);
|
||||||
|
let generated_moves: HashSet<GeneratedMove> =
|
||||||
|
PawnMoveGenerator::new(&board, Some(Color::Black)).collect();
|
||||||
|
assert_eq!(
|
||||||
|
generated_moves,
|
||||||
|
[
|
||||||
|
Move::promotion(Square::H2, Square::H1, PromotionShape::Knight).into(),
|
||||||
|
Move::promotion(Square::H2, Square::H1, PromotionShape::Bishop).into(),
|
||||||
|
Move::promotion(Square::H2, Square::H1, PromotionShape::Rook).into(),
|
||||||
|
Move::promotion(Square::H2, Square::H1, PromotionShape::Queen).into(),
|
||||||
|
Move::capture_promotion(Square::H2, Square::G1, PromotionShape::Knight).into(),
|
||||||
|
Move::capture_promotion(Square::H2, Square::G1, PromotionShape::Bishop).into(),
|
||||||
|
Move::capture_promotion(Square::H2, Square::G1, PromotionShape::Rook).into(),
|
||||||
|
Move::capture_promotion(Square::H2, Square::G1, PromotionShape::Queen).into(),
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -39,6 +39,12 @@ impl Move {
|
||||||
Move(origin_bits(origin) | target_bits(target) | flag_bits)
|
Move(origin_bits(origin) | target_bits(target) | flag_bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn capture_promotion(origin: Square, target: Square, shape: PromotionShape) -> Self {
|
||||||
|
let flag_bits = Kind::CapturePromotion as u16;
|
||||||
|
let shape_bits = shape as u16;
|
||||||
|
Move(origin_bits(origin) | target_bits(target) | flag_bits | shape_bits)
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn en_passant_capture(origin: Square, target: Square) -> Self {
|
pub fn en_passant_capture(origin: Square, target: Square) -> Self {
|
||||||
let flag_bits = Kind::EnPassantCapture as u16;
|
let flag_bits = Kind::EnPassantCapture as u16;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue