chessfriend/moves/src/generators/pawn.rs

486 lines
17 KiB
Rust

// Eryn Wells <eryn@erynwells.me>
//! Implements a move generator for pawns.
use super::GeneratedMove;
use crate::{Move, PromotionShape};
use chessfriend_bitboard::{bit_scanner::TrailingBitScanner, BitBoard};
use chessfriend_board::Board;
use chessfriend_core::{Color, Direction, Rank, Square};
use std::slice;
pub struct PawnMoveGenerator {
color: Color,
single_pushes: BitBoard,
double_pushes: BitBoard,
left_captures: BitBoard,
right_captures: BitBoard,
en_passant: BitBoard,
target_iterator: TrailingBitScanner,
promotion_iterator: Option<PromotionIterator>,
move_type: MoveType,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum MoveType {
SinglePushes,
DoublePushes,
LeftCaptures,
RightCaptures,
EnPassant,
}
struct PromotionIterator {
origin: Square,
target: Square,
iterator: slice::Iter<'static, PromotionShape>,
}
impl PawnMoveGenerator {
#[must_use]
pub fn new(board: &Board, color: Option<Color>) -> Self {
let color = board.unwrap_color(color);
let pawns = board.pawns(color);
let occupied = board.occupancy();
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);
let en_passant: BitBoard = board.en_passant_target.into();
Self {
color,
single_pushes,
double_pushes,
left_captures,
right_captures,
en_passant,
target_iterator: single_pushes.occupied_squares_trailing(),
promotion_iterator: None,
move_type: MoveType::SinglePushes,
}
}
fn pushes(pawns: BitBoard, color: Color, empty: BitBoard) -> (BitBoard, BitBoard) {
match color {
Color::White => {
const THIRD_RANK: BitBoard = BitBoard::rank(Rank::THREE);
let single_pushes = pawns.shift_north_one() & empty;
let double_pushes = (single_pushes & THIRD_RANK).shift_north_one() & empty;
(single_pushes, double_pushes)
}
Color::Black => {
const SIXTH_RANK: BitBoard = BitBoard::rank(Rank::SIX);
let single_pushes = pawns.shift_south_one() & empty;
let double_pushes = (single_pushes & SIXTH_RANK).shift_south_one() & empty;
(single_pushes, double_pushes)
}
}
}
fn captures(pawns: BitBoard, color: Color, enemies: BitBoard) -> (BitBoard, BitBoard) {
match color {
Color::White => {
let left_captures = pawns.shift_north_west_one() & enemies;
let right_captures = pawns.shift_north_east_one() & enemies;
(left_captures, right_captures)
}
Color::Black => {
let left_captures = pawns.shift_south_east_one() & enemies;
let right_captures = pawns.shift_south_west_one() & enemies;
(left_captures, right_captures)
}
}
}
fn calculate_origin_square(&self, target: Square) -> Option<Square> {
match self.move_type {
MoveType::SinglePushes => match self.color {
Color::White => target.neighbor(Direction::South, None),
Color::Black => target.neighbor(Direction::North, None),
},
MoveType::DoublePushes => match self.color {
Color::White => target.neighbor(Direction::South, Some(2)),
Color::Black => target.neighbor(Direction::North, Some(2)),
},
MoveType::LeftCaptures => match self.color {
Color::White => target.neighbor(Direction::SouthEast, None),
Color::Black => target.neighbor(Direction::NorthWest, None),
},
MoveType::RightCaptures => match self.color {
Color::White => target.neighbor(Direction::SouthWest, None),
Color::Black => target.neighbor(Direction::NorthEast, None),
},
MoveType::EnPassant => match self.color {
Color::White => {
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)
}
}
},
}
}
fn next_move_type(&mut self) -> Option<MoveType> {
let next_move_type = self.move_type.next();
if let Some(next_move_type) = next_move_type {
let next_bitboard = match next_move_type {
MoveType::SinglePushes => self.single_pushes,
MoveType::DoublePushes => self.double_pushes,
MoveType::LeftCaptures => self.left_captures,
MoveType::RightCaptures => self.right_captures,
MoveType::EnPassant => self.en_passant,
};
self.target_iterator = next_bitboard.occupied_squares_trailing();
self.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 {
type Item = GeneratedMove;
fn next(&mut self) -> Option<Self::Item> {
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
.calculate_origin_square(target)
.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 {
MoveType::SinglePushes => Some(GeneratedMove {
ply: Move::quiet(origin, target),
}),
MoveType::DoublePushes => Some(GeneratedMove {
ply: Move::double_push(origin, target),
}),
MoveType::LeftCaptures | MoveType::RightCaptures => Some(GeneratedMove {
ply: Move::capture(origin, target),
}),
MoveType::EnPassant => Some(GeneratedMove {
ply: Move::en_passant_capture(origin, target),
}),
}
} else if self.next_move_type().is_some() {
self.next()
} else {
None
}
}
}
impl MoveType {
fn next(self) -> Option<Self> {
match self {
MoveType::SinglePushes => Some(MoveType::DoublePushes),
MoveType::DoublePushes => Some(MoveType::LeftCaptures),
MoveType::LeftCaptures => Some(MoveType::RightCaptures),
MoveType::RightCaptures => Some(MoveType::EnPassant),
MoveType::EnPassant => None,
}
}
}
#[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_f6_right_captures() {
let white_captures_board = test_board!(White Pawn on F6, Black Queen on G7);
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::G7)
},
GeneratedMove {
ply: Move::quiet(Square::F6, Square::F7)
}
]
.into()
);
}
#[test]
fn black_d5_right_captures() {
let black_captures_board = test_board!(Black Pawn on D5, White Queen on C4);
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::C4)
},
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()
);
}
}