[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.
This commit is contained in:
Eryn Wells 2025-05-23 10:00:20 -07:00
parent 05c62dcd99
commit a9268ad194
4 changed files with 81 additions and 26 deletions

View file

@ -1,5 +1,6 @@
// Eryn Wells <eryn@erynwells.me>
mod move_record;
mod position;
#[macro_use]

View file

@ -0,0 +1,15 @@
// Eryn Wells <eryn@erynwells.me>
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<Square>,
pub castling_rights: CastleRights,
pub half_move_clock: HalfMoveClock,
}
impl MoveRecord {}

View file

@ -1,14 +1,14 @@
// Eryn Wells <eryn@erynwells.me>
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<MoveRecord, MakeMoveError>;
#[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,

View file

@ -1,5 +1,6 @@
// Eryn Wells <eryn@erynwells.me>
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<Piece>; Color::NUM],
pub(crate) moves: Vec<MoveRecord>,
pub(crate) captures: [Vec<Piece>; 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() {