[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:
parent
05c62dcd99
commit
a9268ad194
4 changed files with 81 additions and 26 deletions
|
@ -1,5 +1,6 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
mod move_record;
|
||||||
mod position;
|
mod position;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
15
position/src/move_record.rs
Normal file
15
position/src/move_record.rs
Normal 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 {}
|
|
@ -1,14 +1,14 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::Position;
|
use crate::{move_record::MoveRecord, Position};
|
||||||
use chessfriend_board::{movement::Movement, PlacePieceError, PlacePieceStrategy};
|
use chessfriend_board::{
|
||||||
|
castle::CastleEvaluationError, movement::Movement, PlacePieceError, PlacePieceStrategy,
|
||||||
|
};
|
||||||
use chessfriend_core::{Color, Piece, Rank, Square, Wing};
|
use chessfriend_core::{Color, Piece, Rank, Square, Wing};
|
||||||
use chessfriend_moves::Move;
|
use chessfriend_moves::Move;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::CastleEvaluationError;
|
type MakeMoveResult = Result<MoveRecord, MakeMoveError>;
|
||||||
|
|
||||||
type MakeMoveResult = Result<(), MakeMoveError>;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
pub enum ValidateMove {
|
pub enum ValidateMove {
|
||||||
|
@ -69,7 +69,16 @@ impl Position {
|
||||||
///
|
///
|
||||||
/// If `validate` is [`ValidateMove::Yes`], perform validation of move correctness prior to
|
/// If `validate` is [`ValidateMove::Yes`], perform validation of move correctness prior to
|
||||||
/// applying the move. See [`Position::validate_move`].
|
/// 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)?;
|
self.validate_move(ply, validate)?;
|
||||||
|
|
||||||
if ply.is_quiet() {
|
if ply.is_quiet() {
|
||||||
|
@ -85,14 +94,14 @@ impl Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(wing) = ply.castle_wing() {
|
if let Some(wing) = ply.castle_wing() {
|
||||||
return self.make_castle_move(wing);
|
return self.make_castle_move(ply, wing);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ply.is_promotion() {
|
if ply.is_promotion() {
|
||||||
return self.make_promotion_move(ply);
|
return self.make_promotion_move(ply);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unmake_move(&mut self, ply: &Move) -> Result<(), UnmakeMoveError> {
|
pub fn unmake_move(&mut self, ply: &Move) -> Result<(), UnmakeMoveError> {
|
||||||
|
@ -113,9 +122,11 @@ impl Position {
|
||||||
|
|
||||||
self.remove_piece(origin);
|
self.remove_piece(origin);
|
||||||
|
|
||||||
|
let record = self.register_move_record(ply);
|
||||||
|
|
||||||
self.advance_clocks(HalfMoveClock::Advance);
|
self.advance_clocks(HalfMoveClock::Advance);
|
||||||
|
|
||||||
Ok(())
|
Ok(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_double_push_move(&mut self, ply: Move) -> MakeMoveResult {
|
fn make_double_push_move(&mut self, ply: Move) -> MakeMoveResult {
|
||||||
|
@ -134,9 +145,11 @@ impl Position {
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let record = self.register_move_record(ply);
|
||||||
|
|
||||||
self.advance_clocks(HalfMoveClock::Advance);
|
self.advance_clocks(HalfMoveClock::Advance);
|
||||||
|
|
||||||
Ok(())
|
Ok(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_capture_move(&mut self, ply: Move) -> MakeMoveResult {
|
fn make_capture_move(&mut self, ply: Move) -> MakeMoveResult {
|
||||||
|
@ -172,13 +185,15 @@ impl Position {
|
||||||
self.place_piece(piece, target_square, PlacePieceStrategy::Replace)?;
|
self.place_piece(piece, target_square, PlacePieceStrategy::Replace)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let record = self.register_move_record(ply);
|
||||||
|
|
||||||
self.advance_clocks(HalfMoveClock::Reset);
|
self.advance_clocks(HalfMoveClock::Reset);
|
||||||
|
|
||||||
Ok(())
|
Ok(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_castle_move(&mut self, wing: Wing) -> MakeMoveResult {
|
fn make_castle_move(&mut self, ply: Move, wing: Wing) -> MakeMoveResult {
|
||||||
self.active_color_can_castle(wing)?;
|
self.board.active_color_can_castle(wing)?;
|
||||||
|
|
||||||
let active_color = self.board.active_color;
|
let active_color = self.board.active_color;
|
||||||
let parameters = self.board.castling_parameters(wing);
|
let parameters = self.board.castling_parameters(wing);
|
||||||
|
@ -191,9 +206,11 @@ impl Position {
|
||||||
|
|
||||||
self.board.castling_rights.revoke(active_color, wing);
|
self.board.castling_rights.revoke(active_color, wing);
|
||||||
|
|
||||||
|
let record = self.register_move_record(ply);
|
||||||
|
|
||||||
self.advance_clocks(HalfMoveClock::Advance);
|
self.advance_clocks(HalfMoveClock::Advance);
|
||||||
|
|
||||||
Ok(())
|
Ok(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_promotion_move(&mut self, ply: Move) -> MakeMoveResult {
|
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);
|
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))
|
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() {
|
if piece.is_pawn() && square.rank().is_promotable_rank() {
|
||||||
return Err(MakeMoveError::PromotionRequired(square));
|
return Err(MakeMoveError::PromotionRequired(square));
|
||||||
}
|
}
|
||||||
|
@ -326,8 +358,10 @@ mod tests {
|
||||||
use chessfriend_core::{piece, Color, Square};
|
use chessfriend_core::{piece, Color, Square};
|
||||||
use chessfriend_moves::{Move, PromotionShape};
|
use chessfriend_moves::{Move, PromotionShape};
|
||||||
|
|
||||||
|
type TestResult = Result<(), MakeMoveError>;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_quiet_move() -> MakeMoveResult {
|
fn make_quiet_move() -> TestResult {
|
||||||
let mut pos = test_position!(White Pawn on C2);
|
let mut pos = test_position!(White Pawn on C2);
|
||||||
|
|
||||||
let ply = Move::quiet(Square::C2, Square::C3);
|
let ply = Move::quiet(Square::C2, Square::C3);
|
||||||
|
@ -366,23 +400,26 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_capture_move() {
|
fn make_capture_move() -> TestResult {
|
||||||
let mut pos = test_position![
|
let mut pos = test_position![
|
||||||
White Bishop on C2,
|
White Bishop on C2,
|
||||||
Black Rook on F5,
|
Black Rook on F5,
|
||||||
];
|
];
|
||||||
|
|
||||||
let ply = Move::capture(Square::C2, Square::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::C2), None);
|
||||||
assert_eq!(pos.get_piece(Square::F5), Some(piece!(White Bishop)));
|
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.captures[Color::White as usize][0], piece!(Black Rook));
|
||||||
assert_eq!(pos.board.active_color, Color::Black);
|
assert_eq!(pos.board.active_color, Color::Black);
|
||||||
assert_eq!(pos.board.half_move_clock, 0);
|
assert_eq!(pos.board.half_move_clock, 0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_en_passant_capture_move() -> MakeMoveResult {
|
fn make_en_passant_capture_move() -> TestResult {
|
||||||
let mut pos = test_position![
|
let mut pos = test_position![
|
||||||
Black Pawn on F4,
|
Black Pawn on F4,
|
||||||
White Pawn on E2
|
White Pawn on E2
|
||||||
|
@ -433,7 +470,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_promotion_move() -> MakeMoveResult {
|
fn make_promotion_move() -> TestResult {
|
||||||
let mut pos = test_position![
|
let mut pos = test_position![
|
||||||
Black Pawn on E7,
|
Black Pawn on E7,
|
||||||
White Pawn on F7,
|
White Pawn on F7,
|
||||||
|
@ -451,7 +488,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_white_kingside_castle() -> MakeMoveResult {
|
fn make_white_kingside_castle() -> TestResult {
|
||||||
let mut pos = test_position![
|
let mut pos = test_position![
|
||||||
White Rook on H1,
|
White Rook on H1,
|
||||||
White King on E1,
|
White King on E1,
|
||||||
|
@ -474,7 +511,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_white_queenside_castle() -> MakeMoveResult {
|
fn make_white_queenside_castle() -> TestResult {
|
||||||
let mut pos = test_position![
|
let mut pos = test_position![
|
||||||
White King on E1,
|
White King on E1,
|
||||||
White Rook on A1,
|
White Rook on A1,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use crate::move_record::MoveRecord;
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_board::{
|
use chessfriend_board::{
|
||||||
display::DiagramFormatter, fen::ToFenStr, Board, PlacePieceError, PlacePieceStrategy,
|
display::DiagramFormatter, fen::ToFenStr, Board, PlacePieceError, PlacePieceStrategy,
|
||||||
|
@ -11,7 +12,8 @@ use std::{cell::OnceCell, fmt};
|
||||||
#[derive(Clone, Debug, Eq)]
|
#[derive(Clone, Debug, Eq)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
pub board: Board,
|
pub board: Board,
|
||||||
pub(super) captures: [Vec<Piece>; Color::NUM],
|
pub(crate) moves: Vec<MoveRecord>,
|
||||||
|
pub(crate) captures: [Vec<Piece>; Color::NUM],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Position {
|
impl Position {
|
||||||
|
@ -181,6 +183,7 @@ impl Default for Position {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
board: Board::default(),
|
board: Board::default(),
|
||||||
|
moves: Vec::default(),
|
||||||
captures: Default::default(),
|
captures: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,8 +205,7 @@ impl fmt::Display for Position {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{test_position, Position};
|
use crate::{test_position, Position};
|
||||||
use chessfriend_bitboard::bitboard;
|
use chessfriend_core::piece;
|
||||||
use chessfriend_core::{piece, Wing};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn piece_on_square() {
|
fn piece_on_square() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue