From a9268ad194a87a562fe1d5497234a6e2370eb7dd Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 23 May 2025 10:00:20 -0700 Subject: [PATCH] [position] Add move tracking to Position Create a new MoveRecord struct that tracks the move (aka ply) and irreversible board properties. This should make it easier to unmake moves in the future. --- position/src/lib.rs | 1 + position/src/move_record.rs | 15 ++++++ position/src/position/make_move.rs | 83 +++++++++++++++++++++--------- position/src/position/position.rs | 8 +-- 4 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 position/src/move_record.rs diff --git a/position/src/lib.rs b/position/src/lib.rs index 9bce691..a62da34 100644 --- a/position/src/lib.rs +++ b/position/src/lib.rs @@ -1,5 +1,6 @@ // Eryn Wells +mod move_record; mod position; #[macro_use] diff --git a/position/src/move_record.rs b/position/src/move_record.rs new file mode 100644 index 0000000..72af258 --- /dev/null +++ b/position/src/move_record.rs @@ -0,0 +1,15 @@ +// Eryn Wells + +use chessfriend_board::{board::HalfMoveClock, CastleRights}; +use chessfriend_core::Square; +use chessfriend_moves::Move; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct MoveRecord { + pub ply: Move, + pub en_passant_target: Option, + pub castling_rights: CastleRights, + pub half_move_clock: HalfMoveClock, +} + +impl MoveRecord {} diff --git a/position/src/position/make_move.rs b/position/src/position/make_move.rs index 12c45d9..a65faf2 100644 --- a/position/src/position/make_move.rs +++ b/position/src/position/make_move.rs @@ -1,14 +1,14 @@ // Eryn Wells -use crate::Position; -use chessfriend_board::{movement::Movement, PlacePieceError, PlacePieceStrategy}; +use crate::{move_record::MoveRecord, Position}; +use chessfriend_board::{ + castle::CastleEvaluationError, movement::Movement, PlacePieceError, PlacePieceStrategy, +}; use chessfriend_core::{Color, Piece, Rank, Square, Wing}; use chessfriend_moves::Move; use thiserror::Error; -use super::CastleEvaluationError; - -type MakeMoveResult = Result<(), MakeMoveError>; +type MakeMoveResult = Result; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum ValidateMove { @@ -69,7 +69,16 @@ impl Position { /// /// If `validate` is [`ValidateMove::Yes`], perform validation of move correctness prior to /// applying the move. See [`Position::validate_move`]. - pub fn make_move(&mut self, ply: Move, validate: ValidateMove) -> MakeMoveResult { + pub fn make_move(&mut self, ply: Move, validate: ValidateMove) -> Result<(), MakeMoveError> { + self.make_move_internal(ply, validate)?; + Ok(()) + } + + pub(crate) fn make_move_internal( + &mut self, + ply: Move, + validate: ValidateMove, + ) -> MakeMoveResult { self.validate_move(ply, validate)?; if ply.is_quiet() { @@ -85,14 +94,14 @@ impl Position { } if let Some(wing) = ply.castle_wing() { - return self.make_castle_move(wing); + return self.make_castle_move(ply, wing); } if ply.is_promotion() { return self.make_promotion_move(ply); } - Ok(()) + unreachable!(); } pub fn unmake_move(&mut self, ply: &Move) -> Result<(), UnmakeMoveError> { @@ -113,9 +122,11 @@ impl Position { self.remove_piece(origin); + let record = self.register_move_record(ply); + self.advance_clocks(HalfMoveClock::Advance); - Ok(()) + Ok(record) } fn make_double_push_move(&mut self, ply: Move) -> MakeMoveResult { @@ -134,9 +145,11 @@ impl Position { _ => unreachable!(), }; + let record = self.register_move_record(ply); + self.advance_clocks(HalfMoveClock::Advance); - Ok(()) + Ok(record) } fn make_capture_move(&mut self, ply: Move) -> MakeMoveResult { @@ -172,13 +185,15 @@ impl Position { self.place_piece(piece, target_square, PlacePieceStrategy::Replace)?; } + let record = self.register_move_record(ply); + self.advance_clocks(HalfMoveClock::Reset); - Ok(()) + Ok(record) } - fn make_castle_move(&mut self, wing: Wing) -> MakeMoveResult { - self.active_color_can_castle(wing)?; + fn make_castle_move(&mut self, ply: Move, wing: Wing) -> MakeMoveResult { + self.board.active_color_can_castle(wing)?; let active_color = self.board.active_color; let parameters = self.board.castling_parameters(wing); @@ -191,9 +206,11 @@ impl Position { self.board.castling_rights.revoke(active_color, wing); + let record = self.register_move_record(ply); + self.advance_clocks(HalfMoveClock::Advance); - Ok(()) + Ok(record) } fn make_promotion_move(&mut self, ply: Move) -> MakeMoveResult { @@ -219,9 +236,24 @@ impl Position { ); } + let record = self.register_move_record(ply); + self.advance_clocks(HalfMoveClock::Reset); - Ok(()) + Ok(record) + } + + fn register_move_record(&mut self, ply: Move) -> MoveRecord { + let record = MoveRecord { + ply, + en_passant_target: self.board.en_passant_target, + castling_rights: self.board.castling_rights, + half_move_clock: self.board.half_move_clock, + }; + + self.moves.push(record.clone()); + + record } } @@ -230,7 +262,7 @@ impl Position { self.get_piece(square).ok_or(MakeMoveError::NoPiece(square)) } - fn place_piece_for_move(&mut self, piece: Piece, square: Square) -> MakeMoveResult { + fn place_piece_for_move(&mut self, piece: Piece, square: Square) -> Result<(), MakeMoveError> { if piece.is_pawn() && square.rank().is_promotable_rank() { return Err(MakeMoveError::PromotionRequired(square)); } @@ -326,8 +358,10 @@ mod tests { use chessfriend_core::{piece, Color, Square}; use chessfriend_moves::{Move, PromotionShape}; + type TestResult = Result<(), MakeMoveError>; + #[test] - fn make_quiet_move() -> MakeMoveResult { + fn make_quiet_move() -> TestResult { let mut pos = test_position!(White Pawn on C2); let ply = Move::quiet(Square::C2, Square::C3); @@ -366,23 +400,26 @@ mod tests { } #[test] - fn make_capture_move() { + fn make_capture_move() -> TestResult { let mut pos = test_position![ White Bishop on C2, Black Rook on F5, ]; let ply = Move::capture(Square::C2, Square::F5); - assert_eq!(pos.make_move(ply, ValidateMove::Yes), Ok(())); + pos.make_move(ply, ValidateMove::Yes)?; + assert_eq!(pos.get_piece(Square::C2), None); assert_eq!(pos.get_piece(Square::F5), Some(piece!(White Bishop))); assert_eq!(pos.captures[Color::White as usize][0], piece!(Black Rook)); assert_eq!(pos.board.active_color, Color::Black); assert_eq!(pos.board.half_move_clock, 0); + + Ok(()) } #[test] - fn make_en_passant_capture_move() -> MakeMoveResult { + fn make_en_passant_capture_move() -> TestResult { let mut pos = test_position![ Black Pawn on F4, White Pawn on E2 @@ -433,7 +470,7 @@ mod tests { } #[test] - fn make_promotion_move() -> MakeMoveResult { + fn make_promotion_move() -> TestResult { let mut pos = test_position![ Black Pawn on E7, White Pawn on F7, @@ -451,7 +488,7 @@ mod tests { } #[test] - fn make_white_kingside_castle() -> MakeMoveResult { + fn make_white_kingside_castle() -> TestResult { let mut pos = test_position![ White Rook on H1, White King on E1, @@ -474,7 +511,7 @@ mod tests { } #[test] - fn make_white_queenside_castle() -> MakeMoveResult { + fn make_white_queenside_castle() -> TestResult { let mut pos = test_position![ White King on E1, White Rook on A1, diff --git a/position/src/position/position.rs b/position/src/position/position.rs index c3d5793..9fe5e26 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -1,5 +1,6 @@ // Eryn Wells +use crate::move_record::MoveRecord; use chessfriend_bitboard::BitBoard; use chessfriend_board::{ display::DiagramFormatter, fen::ToFenStr, Board, PlacePieceError, PlacePieceStrategy, @@ -11,7 +12,8 @@ use std::{cell::OnceCell, fmt}; #[derive(Clone, Debug, Eq)] pub struct Position { pub board: Board, - pub(super) captures: [Vec; Color::NUM], + pub(crate) moves: Vec, + pub(crate) captures: [Vec; Color::NUM], } impl Position { @@ -181,6 +183,7 @@ impl Default for Position { fn default() -> Self { Self { board: Board::default(), + moves: Vec::default(), captures: Default::default(), } } @@ -202,8 +205,7 @@ impl fmt::Display for Position { mod tests { use super::*; use crate::{test_position, Position}; - use chessfriend_bitboard::bitboard; - use chessfriend_core::{piece, Wing}; + use chessfriend_core::piece; #[test] fn piece_on_square() {