From 574ab803dd915810ef95dacab86b1a3a7dae8226 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 23 May 2025 18:36:22 -0700 Subject: [PATCH] [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. --- moves/src/generators.rs | 12 +++ moves/src/generators/pawn.rs | 180 +++++++++++++++++++++++++++++++++++ moves/src/lib.rs | 1 + 3 files changed, 193 insertions(+) create mode 100644 moves/src/generators.rs create mode 100644 moves/src/generators/pawn.rs diff --git a/moves/src/generators.rs b/moves/src/generators.rs new file mode 100644 index 0000000..dda0a5a --- /dev/null +++ b/moves/src/generators.rs @@ -0,0 +1,12 @@ +// Eryn Wells + +mod pawn; + +pub use pawn::PawnMoveGenerator; + +use crate::Move; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct GeneratedMove { + pub(crate) ply: Move, +} diff --git a/moves/src/generators/pawn.rs b/moves/src/generators/pawn.rs new file mode 100644 index 0000000..d0a39af --- /dev/null +++ b/moves/src/generators/pawn.rs @@ -0,0 +1,180 @@ +// Eryn Wells + +//! 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) -> 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 { + 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 { + 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 { + 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 { + 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, + } + } +} diff --git a/moves/src/lib.rs b/moves/src/lib.rs index 09ccb0d..dd44db5 100644 --- a/moves/src/lib.rs +++ b/moves/src/lib.rs @@ -1,5 +1,6 @@ // Eryn Wells +pub mod generators; pub mod testing; mod builder;