[explorer, moves, position] Implement bespoke make_move and unmake_move methods on Position
Instead of inheriting the MakeMove and UnmakeMove traits by being a BoardProvider, implement bespoke versions of these two methods. This gives Position a chance to do some of its own work (tracking captures, move records, etc) when making a move. Pass the move record by reference to the unmake_move() method. Saves a copy. Export the Result types for MakeMove and UnmakeMove.
This commit is contained in:
parent
724a98c2e2
commit
8f42a4c94e
5 changed files with 66 additions and 45 deletions
|
@ -1,9 +1,11 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
mod make_command;
|
||||||
|
|
||||||
use chessfriend_board::{fen::FromFenStr, Board};
|
use chessfriend_board::{fen::FromFenStr, Board};
|
||||||
use chessfriend_core::{Color, Piece, Shape, Square};
|
use chessfriend_core::{Color, Piece, Shape, Square};
|
||||||
use chessfriend_moves::{Builder as MoveBuilder, GeneratedMove};
|
use chessfriend_moves::{Builder as MoveBuilder, GeneratedMove, MakeMove, ValidateMove};
|
||||||
use chessfriend_position::{fen::ToFenStr, PlacePieceStrategy, Position, ValidateMove};
|
use chessfriend_position::{fen::ToFenStr, PlacePieceStrategy, Position};
|
||||||
|
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
|
|
|
@ -13,7 +13,7 @@ mod unmake_move;
|
||||||
pub use builder::{Builder, Error as BuildMoveError, Result as BuildMoveResult};
|
pub use builder::{Builder, Error as BuildMoveError, Result as BuildMoveResult};
|
||||||
pub use defs::{Kind, PromotionShape};
|
pub use defs::{Kind, PromotionShape};
|
||||||
pub use generators::GeneratedMove;
|
pub use generators::GeneratedMove;
|
||||||
pub use make_move::{MakeMove, MakeMoveError, ValidateMove};
|
pub use make_move::{MakeMove, MakeMoveError, MakeMoveResult, ValidateMove};
|
||||||
pub use moves::Move;
|
pub use moves::Move;
|
||||||
pub use record::MoveRecord;
|
pub use record::MoveRecord;
|
||||||
pub use unmake_move::{UnmakeMove, UnmakeMoveError};
|
pub use unmake_move::{UnmakeMove, UnmakeMoveError, UnmakeMoveResult};
|
||||||
|
|
|
@ -8,7 +8,7 @@ use chessfriend_board::{
|
||||||
use chessfriend_core::{Color, Piece, Rank, Square, Wing};
|
use chessfriend_core::{Color, Piece, Rank, Square, Wing};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
type MakeMoveResult = Result<MoveRecord, MakeMoveError>;
|
pub type MakeMoveResult = Result<MoveRecord, MakeMoveError>;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
pub enum ValidateMove {
|
pub enum ValidateMove {
|
||||||
|
|
|
@ -5,7 +5,7 @@ use chessfriend_board::{Board, BoardProvider, PlacePieceError, PlacePieceStrateg
|
||||||
use chessfriend_core::{Piece, Square};
|
use chessfriend_core::{Piece, Square};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
type UnmakeMoveResult = Result<(), UnmakeMoveError>;
|
pub type UnmakeMoveResult = Result<(), UnmakeMoveError>;
|
||||||
|
|
||||||
#[derive(Debug, Error, Eq, PartialEq)]
|
#[derive(Debug, Error, Eq, PartialEq)]
|
||||||
pub enum UnmakeMoveError {
|
pub enum UnmakeMoveError {
|
||||||
|
@ -42,7 +42,7 @@ pub trait UnmakeMove {
|
||||||
/// Returns one of [`UnmakeMoveError`] indicating why the move cannot be
|
/// Returns one of [`UnmakeMoveError`] indicating why the move cannot be
|
||||||
/// unmade.
|
/// unmade.
|
||||||
///
|
///
|
||||||
fn unmake_move(&mut self, record: MoveRecord) -> UnmakeMoveResult;
|
fn unmake_move(&mut self, record: &MoveRecord) -> UnmakeMoveResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
trait UnmakeMoveInternal {
|
trait UnmakeMoveInternal {
|
||||||
|
@ -53,17 +53,17 @@ trait UnmakeMoveInternal {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: BoardProvider> UnmakeMove for T {
|
impl<T: BoardProvider> UnmakeMove for T {
|
||||||
fn unmake_move(&mut self, record: MoveRecord) -> UnmakeMoveResult {
|
fn unmake_move(&mut self, record: &MoveRecord) -> UnmakeMoveResult {
|
||||||
let ply = record.ply;
|
let ply = record.ply;
|
||||||
|
|
||||||
if ply.is_quiet() || ply.is_double_push() {
|
if ply.is_quiet() || ply.is_double_push() {
|
||||||
self.unmake_quiet_move(&record)?;
|
self.unmake_quiet_move(record)?;
|
||||||
} else if ply.is_capture() || ply.is_en_passant() {
|
} else if ply.is_capture() || ply.is_en_passant() {
|
||||||
self.unmake_capture_move(&record)?;
|
self.unmake_capture_move(record)?;
|
||||||
} else if ply.is_promotion() {
|
} else if ply.is_promotion() {
|
||||||
self.unmake_promotion_move(&record)?;
|
self.unmake_promotion_move(record)?;
|
||||||
} else if ply.is_castle() {
|
} else if ply.is_castle() {
|
||||||
self.unmake_castle_move(&record)?;
|
self.unmake_castle_move(record)?;
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Unmake the move
|
// Unmake the move
|
||||||
initial_board.unmake_move(record)?;
|
initial_board.unmake_move(&record)?;
|
||||||
|
|
||||||
// Verify we're back to the initial state
|
// Verify we're back to the initial state
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -240,8 +240,7 @@ mod tests {
|
||||||
assert_eq!(board.get_piece(Square::C3), Some(piece!(White Pawn)));
|
assert_eq!(board.get_piece(Square::C3), Some(piece!(White Pawn)));
|
||||||
assert_eq!(board.active_color, Color::Black);
|
assert_eq!(board.active_color, Color::Black);
|
||||||
|
|
||||||
// Unmake the move
|
board.unmake_move(&record)?;
|
||||||
board.unmake_move(record)?;
|
|
||||||
|
|
||||||
// Verify original state restored
|
// Verify original state restored
|
||||||
assert_eq!(board.get_piece(Square::C2), Some(piece!(White Pawn)));
|
assert_eq!(board.get_piece(Square::C2), Some(piece!(White Pawn)));
|
||||||
|
@ -264,8 +263,7 @@ mod tests {
|
||||||
assert_eq!(board.en_passant_target, Some(Square::E3));
|
assert_eq!(board.en_passant_target, Some(Square::E3));
|
||||||
assert_eq!(board.active_color, Color::Black);
|
assert_eq!(board.active_color, Color::Black);
|
||||||
|
|
||||||
// Unmake the move
|
board.unmake_move(&record)?;
|
||||||
board.unmake_move(record)?;
|
|
||||||
|
|
||||||
// Verify original state restored
|
// Verify original state restored
|
||||||
assert_eq!(board.get_piece(Square::E2), Some(piece!(White Pawn)));
|
assert_eq!(board.get_piece(Square::E2), Some(piece!(White Pawn)));
|
||||||
|
@ -292,8 +290,7 @@ mod tests {
|
||||||
assert_eq!(record.captured_piece, Some(piece!(Black Rook)));
|
assert_eq!(record.captured_piece, Some(piece!(Black Rook)));
|
||||||
assert_eq!(board.active_color, Color::Black);
|
assert_eq!(board.active_color, Color::Black);
|
||||||
|
|
||||||
// Unmake the move
|
board.unmake_move(&record)?;
|
||||||
board.unmake_move(record)?;
|
|
||||||
|
|
||||||
// Verify original state restored
|
// Verify original state restored
|
||||||
assert_eq!(board.get_piece(Square::C2), Some(piece!(White Bishop)));
|
assert_eq!(board.get_piece(Square::C2), Some(piece!(White Bishop)));
|
||||||
|
@ -328,8 +325,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(record.captured_piece, Some(piece!(White Pawn)));
|
assert_eq!(record.captured_piece, Some(piece!(White Pawn)));
|
||||||
|
|
||||||
// Unmake the en passant capture
|
board.unmake_move(&record)?;
|
||||||
board.unmake_move(record)?;
|
|
||||||
|
|
||||||
// Verify state before en passant is restored
|
// Verify state before en passant is restored
|
||||||
assert_eq!(board.get_piece(Square::F4), Some(piece!(Black Pawn)));
|
assert_eq!(board.get_piece(Square::F4), Some(piece!(Black Pawn)));
|
||||||
|
@ -358,8 +354,7 @@ mod tests {
|
||||||
assert_eq!(board.get_piece(Square::F8), Some(piece!(White Queen)));
|
assert_eq!(board.get_piece(Square::F8), Some(piece!(White Queen)));
|
||||||
assert_eq!(board.active_color, Color::Black);
|
assert_eq!(board.active_color, Color::Black);
|
||||||
|
|
||||||
// Unmake the promotion
|
board.unmake_move(&record)?;
|
||||||
board.unmake_move(record)?;
|
|
||||||
|
|
||||||
// Verify original pawn is restored
|
// Verify original pawn is restored
|
||||||
assert_eq!(board.get_piece(Square::F7), Some(piece!(White Pawn)));
|
assert_eq!(board.get_piece(Square::F7), Some(piece!(White Pawn)));
|
||||||
|
@ -384,8 +379,7 @@ mod tests {
|
||||||
assert_eq!(board.get_piece(Square::G8), Some(piece!(White Queen)));
|
assert_eq!(board.get_piece(Square::G8), Some(piece!(White Queen)));
|
||||||
assert_eq!(record.captured_piece, Some(piece!(Black Rook)));
|
assert_eq!(record.captured_piece, Some(piece!(Black Rook)));
|
||||||
|
|
||||||
// Unmake the promotion capture
|
board.unmake_move(&record)?;
|
||||||
board.unmake_move(record)?;
|
|
||||||
|
|
||||||
// Verify original state restored
|
// Verify original state restored
|
||||||
assert_eq!(board.get_piece(Square::F7), Some(piece!(White Pawn)));
|
assert_eq!(board.get_piece(Square::F7), Some(piece!(White Pawn)));
|
||||||
|
@ -416,8 +410,7 @@ mod tests {
|
||||||
.castling_rights
|
.castling_rights
|
||||||
.color_has_right(Color::White, Wing::KingSide));
|
.color_has_right(Color::White, Wing::KingSide));
|
||||||
|
|
||||||
// Unmake the castle
|
board.unmake_move(&record)?;
|
||||||
board.unmake_move(record)?;
|
|
||||||
|
|
||||||
// Verify original state restored
|
// Verify original state restored
|
||||||
assert_eq!(board.get_piece(Square::E1), Some(piece!(White King)));
|
assert_eq!(board.get_piece(Square::E1), Some(piece!(White King)));
|
||||||
|
@ -451,8 +444,7 @@ mod tests {
|
||||||
.castling_rights
|
.castling_rights
|
||||||
.color_has_right(Color::White, Wing::QueenSide));
|
.color_has_right(Color::White, Wing::QueenSide));
|
||||||
|
|
||||||
// Unmake the castle
|
board.unmake_move(&record)?;
|
||||||
board.unmake_move(record)?;
|
|
||||||
|
|
||||||
// Verify original state restored
|
// Verify original state restored
|
||||||
assert_eq!(board.get_piece(Square::E1), Some(piece!(White King)));
|
assert_eq!(board.get_piece(Square::E1), Some(piece!(White King)));
|
||||||
|
@ -484,8 +476,7 @@ mod tests {
|
||||||
assert_eq!(board.get_piece(Square::G8), Some(piece!(Black King)));
|
assert_eq!(board.get_piece(Square::G8), Some(piece!(Black King)));
|
||||||
assert_eq!(board.get_piece(Square::F8), Some(piece!(Black Rook)));
|
assert_eq!(board.get_piece(Square::F8), Some(piece!(Black Rook)));
|
||||||
|
|
||||||
// Unmake the castle
|
board.unmake_move(&record)?;
|
||||||
board.unmake_move(record)?;
|
|
||||||
|
|
||||||
// Verify original state restored
|
// Verify original state restored
|
||||||
assert_eq!(board.get_piece(Square::E8), Some(piece!(Black King)));
|
assert_eq!(board.get_piece(Square::E8), Some(piece!(Black King)));
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
mod captures;
|
mod captures;
|
||||||
mod unmake_move;
|
|
||||||
|
|
||||||
use chessfriend_moves::{
|
use chessfriend_moves::{
|
||||||
generators::{
|
generators::{
|
||||||
AllPiecesMoveGenerator, BishopMoveGenerator, KingMoveGenerator, KnightMoveGenerator,
|
AllPiecesMoveGenerator, BishopMoveGenerator, KingMoveGenerator, KnightMoveGenerator,
|
||||||
PawnMoveGenerator, QueenMoveGenerator, RookMoveGenerator,
|
PawnMoveGenerator, QueenMoveGenerator, RookMoveGenerator,
|
||||||
},
|
},
|
||||||
GeneratedMove, MakeMove, Move, MoveRecord, ValidateMove,
|
GeneratedMove, MakeMove, MakeMoveError, Move, MoveRecord, UnmakeMove, UnmakeMoveError,
|
||||||
|
UnmakeMoveResult, ValidateMove,
|
||||||
};
|
};
|
||||||
|
|
||||||
use captures::CapturesList;
|
use captures::CapturesList;
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_board::{
|
use chessfriend_board::{
|
||||||
display::DiagramFormatter, fen::ToFenStr, Board, BoardProvider, PlacePieceError,
|
display::DiagramFormatter, fen::ToFenStr, Board, PlacePieceError, PlacePieceStrategy,
|
||||||
PlacePieceStrategy,
|
|
||||||
};
|
};
|
||||||
use chessfriend_core::{Color, Piece, Shape, Square};
|
use chessfriend_core::{Color, Piece, Shape, Square};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -127,6 +126,45 @@ impl Position {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Position {
|
||||||
|
/// Make a move on the board and record it in the move list.
|
||||||
|
///
|
||||||
|
/// ## Errors
|
||||||
|
///
|
||||||
|
/// Returns one of [`MakeMoveError`] if the move cannot be made.
|
||||||
|
///
|
||||||
|
pub fn make_move(&mut self, ply: Move, validate: ValidateMove) -> Result<(), MakeMoveError> {
|
||||||
|
let record = self.board.make_move(ply, validate)?;
|
||||||
|
|
||||||
|
if let Some(captured_piece) = record.captured_piece {
|
||||||
|
self.captures.push(record.color, captured_piece);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.moves.push(record.clone());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unmake the last move made on the board and remove its record from the
|
||||||
|
/// move list.
|
||||||
|
///
|
||||||
|
/// ## Errors
|
||||||
|
///
|
||||||
|
/// Returns one of [`UnmakeMoveError`] if the move cannot be made.
|
||||||
|
///
|
||||||
|
pub fn unmake_last_move(&mut self) -> UnmakeMoveResult {
|
||||||
|
let last_move_record = self.moves.pop().ok_or(UnmakeMoveError::NoMove)?;
|
||||||
|
|
||||||
|
let unmake_result = self.board.unmake_move(&last_move_record);
|
||||||
|
|
||||||
|
if unmake_result.is_err() {
|
||||||
|
self.moves.push(last_move_record);
|
||||||
|
}
|
||||||
|
|
||||||
|
unmake_result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Position {
|
impl Position {
|
||||||
pub fn display(&self) -> DiagramFormatter {
|
pub fn display(&self) -> DiagramFormatter {
|
||||||
self.board.display()
|
self.board.display()
|
||||||
|
@ -141,16 +179,6 @@ impl ToFenStr for Position {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BoardProvider for Position {
|
|
||||||
fn board(&self) -> &Board {
|
|
||||||
&self.board
|
|
||||||
}
|
|
||||||
|
|
||||||
fn board_mut(&mut self) -> &mut Board {
|
|
||||||
&mut self.board
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Position {
|
impl PartialEq for Position {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.board == other.board
|
self.board == other.board
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue