[moves] Implement a move generator for pawns

I used Claude to help me figure this out. First time using AI for coding. It was
actually rather helpful!

Calculate BitBoards representing the various kinds of pawn moves when the move
generator is created, and then iterate through them.

En passant still isn't implemented here. This code has not been tested yet either.
This commit is contained in:
Eryn Wells 2025-05-23 18:36:22 -07:00
parent af2bff348f
commit 574ab803dd
3 changed files with 193 additions and 0 deletions

12
moves/src/generators.rs Normal file
View file

@ -0,0 +1,12 @@
// Eryn Wells <eryn@erynwells.me>
mod pawn;
pub use pawn::PawnMoveGenerator;
use crate::Move;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GeneratedMove {
pub(crate) ply: Move,
}

View file

@ -0,0 +1,180 @@
// Eryn Wells <eryn@erynwells.me>
//! Implements a move generator for pawns.
use crate::Move;
use super::GeneratedMove;
use chessfriend_bitboard::{bit_scanner::TrailingBitScanner, BitBoard};
use chessfriend_board::Board;
use chessfriend_core::{Color, Direction, Rank, Square};
pub struct PawnMoveGenerator {
color: Color,
single_pushes: BitBoard,
double_pushes: BitBoard,
left_captures: BitBoard,
right_captures: BitBoard,
en_passant: BitBoard,
iterator: TrailingBitScanner,
move_type: MoveType,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum MoveType {
SinglePushes,
DoublePushes,
LeftCaptures,
RightCaptures,
EnPassant,
}
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,
iterator: single_pushes.occupied_squares_trailing(),
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::NorthWest, None),
Color::Black => target.neighbor(Direction::SouthEast, None),
},
MoveType::RightCaptures => match self.color {
Color::White => target.neighbor(Direction::NorthEast, None),
Color::Black => target.neighbor(Direction::SouthWest, None),
},
MoveType::EnPassant => match self.color {
Color::White => unimplemented!(),
Color::Black => unimplemented!(),
},
}
}
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.iterator = next_bitboard.occupied_squares_trailing();
self.move_type = next_move_type;
}
next_move_type
}
}
impl std::iter::Iterator for PawnMoveGenerator {
type Item = GeneratedMove;
fn next(&mut self) -> Option<Self::Item> {
if let Some(target) = self.iterator.next() {
let origin = self
.calculate_origin_square(target)
.expect("Bogus origin square");
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,
}
}
}

View file

@ -1,5 +1,6 @@
// Eryn Wells <eryn@erynwells.me>
pub mod generators;
pub mod testing;
mod builder;