[board, moves, position] Move make_move routines to moves crate
Declare a MakeMove trait and export it from chessfriend_moves. Declare a BoardProvider trait that both Board and Position implement. Implement the MakeMove trait for all types that implement BoardProvider, and move all the move making code to the moves crate. This change makes it possible to make moves directly on a Board, rather than requiring a Position. The indirection of declaring and implementing the trait in the moves crate is required because chessfriend_board is a dependency of chessfriend_moves. So, it would be a layering violation for Board to implement make_move() directly. The board crate cannot link the moves crate because that would introduce a circular dependency.
This commit is contained in:
parent
ecde338602
commit
40e8e055f9
7 changed files with 202 additions and 163 deletions
18
board/src/board_provider.rs
Normal file
18
board/src/board_provider.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use crate::Board;
|
||||||
|
|
||||||
|
pub trait BoardProvider {
|
||||||
|
fn board(&self) -> &Board;
|
||||||
|
fn board_mut(&mut self) -> &mut Board;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoardProvider for Board {
|
||||||
|
fn board(&self) -> &Board {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn board_mut(&mut self) -> &mut Board {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,10 +9,12 @@ pub mod macros;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
pub mod sight;
|
pub mod sight;
|
||||||
|
|
||||||
|
mod board_provider;
|
||||||
mod check;
|
mod check;
|
||||||
mod piece_sets;
|
mod piece_sets;
|
||||||
|
|
||||||
pub use board::Board;
|
pub use board::Board;
|
||||||
|
pub use board_provider::BoardProvider;
|
||||||
pub use castle::Parameters as CastleParameters;
|
pub use castle::Parameters as CastleParameters;
|
||||||
pub use castle::Rights as CastleRights;
|
pub use castle::Rights as CastleRights;
|
||||||
pub use piece_sets::{PlacePieceError, PlacePieceStrategy};
|
pub use piece_sets::{PlacePieceError, PlacePieceStrategy};
|
||||||
|
|
|
@ -5,11 +5,13 @@ pub mod testing;
|
||||||
|
|
||||||
mod builder;
|
mod builder;
|
||||||
mod defs;
|
mod defs;
|
||||||
|
mod make_move;
|
||||||
mod moves;
|
mod moves;
|
||||||
mod record;
|
mod record;
|
||||||
|
|
||||||
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 moves::Move;
|
pub use moves::Move;
|
||||||
pub use record::MoveRecord;
|
pub use record::MoveRecord;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::Position;
|
use crate::{Move, MoveRecord};
|
||||||
use chessfriend_board::{
|
use chessfriend_board::{
|
||||||
castle::CastleEvaluationError, movement::Movement, Board, PlacePieceError, PlacePieceStrategy,
|
castle::CastleEvaluationError, movement::Movement, Board, BoardProvider, PlacePieceError,
|
||||||
|
PlacePieceStrategy,
|
||||||
};
|
};
|
||||||
use chessfriend_core::{Color, Piece, Rank, Square, Wing};
|
use chessfriend_core::{Color, Piece, Rank, Square, Wing};
|
||||||
use chessfriend_moves::{Move, MoveRecord};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
type MakeMoveResult = Result<MoveRecord, MakeMoveError>;
|
type MakeMoveResult = Result<MoveRecord, MakeMoveError>;
|
||||||
|
@ -60,23 +60,35 @@ pub enum MakeMoveError {
|
||||||
PromotionRequired(Square),
|
PromotionRequired(Square),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Position {
|
pub trait MakeMove {
|
||||||
|
fn make_move(&mut self, ply: Move, validate: ValidateMove) -> MakeMoveResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait MakeMoveInternal {
|
||||||
|
fn make_quiet_move(&mut self, ply: Move) -> MakeMoveResult;
|
||||||
|
fn make_double_push_move(&mut self, ply: Move) -> MakeMoveResult;
|
||||||
|
fn make_capture_move(&mut self, ply: Move) -> MakeMoveResult;
|
||||||
|
fn make_castle_move(&mut self, ply: Move, wing: Wing) -> MakeMoveResult;
|
||||||
|
fn make_promotion_move(&mut self, ply: Move) -> MakeMoveResult;
|
||||||
|
|
||||||
|
fn validate_move(&self, ply: Move, validate: ValidateMove) -> Result<(), MakeMoveError>;
|
||||||
|
fn validate_active_piece(&self, ply: Move) -> Result<Piece, MakeMoveError>;
|
||||||
|
|
||||||
|
fn advance_clocks(&mut self, half_move_clock: HalfMoveClock);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: BoardProvider> MakeMove for T {
|
||||||
/// Make a move in the position.
|
/// Make a move in the position.
|
||||||
///
|
///
|
||||||
/// ## Errors
|
/// ## Errors
|
||||||
///
|
///
|
||||||
/// 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) -> Result<(), MakeMoveError> {
|
fn make_move(
|
||||||
self.make_move_internal(ply, validate)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn make_move_internal(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
ply: Move,
|
ply: Move,
|
||||||
validate: ValidateMove,
|
validate: ValidateMove,
|
||||||
) -> MakeMoveResult {
|
) -> Result<MoveRecord, MakeMoveError> {
|
||||||
if ply.is_quiet() {
|
if ply.is_quiet() {
|
||||||
self.validate_move(ply, validate)?;
|
self.validate_move(ply, validate)?;
|
||||||
return self.make_quiet_move(ply);
|
return self.make_quiet_move(ply);
|
||||||
|
@ -105,20 +117,28 @@ impl Position {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Position {
|
impl<T: BoardProvider> MakeMoveInternal for T {
|
||||||
fn make_quiet_move(&mut self, ply: Move) -> MakeMoveResult {
|
fn make_quiet_move(&mut self, ply: Move) -> MakeMoveResult {
|
||||||
|
let board = self.board_mut();
|
||||||
|
|
||||||
let origin = ply.origin_square();
|
let origin = ply.origin_square();
|
||||||
|
|
||||||
let piece = self
|
let piece = board
|
||||||
.get_piece(origin)
|
.get_piece(origin)
|
||||||
.ok_or(MakeMoveError::NoPiece(origin))?;
|
.ok_or(MakeMoveError::NoPiece(origin))?;
|
||||||
|
|
||||||
let target = ply.target_square();
|
let target = ply.target_square();
|
||||||
self.place_piece_for_move(piece, target)?;
|
if piece.is_pawn() && target.rank().is_promotable_rank() {
|
||||||
|
return Err(MakeMoveError::PromotionRequired(target));
|
||||||
|
}
|
||||||
|
|
||||||
self.remove_piece(origin);
|
board
|
||||||
|
.place_piece(piece, target, PlacePieceStrategy::PreserveExisting)
|
||||||
|
.map_err(MakeMoveError::PlacePieceError)?;
|
||||||
|
|
||||||
let record = self.register_move_record(ply, None);
|
board.remove_piece(origin);
|
||||||
|
|
||||||
|
let record = MoveRecord::new(board, ply, None);
|
||||||
|
|
||||||
self.advance_clocks(HalfMoveClock::Advance);
|
self.advance_clocks(HalfMoveClock::Advance);
|
||||||
|
|
||||||
|
@ -126,22 +146,25 @@ impl Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_double_push_move(&mut self, ply: Move) -> MakeMoveResult {
|
fn make_double_push_move(&mut self, ply: Move) -> MakeMoveResult {
|
||||||
|
let board = self.board_mut();
|
||||||
|
|
||||||
let origin = ply.origin_square();
|
let origin = ply.origin_square();
|
||||||
let piece = self
|
let piece = board
|
||||||
.board
|
|
||||||
.remove_piece(origin)
|
.remove_piece(origin)
|
||||||
.ok_or(MakeMoveError::NoPiece(origin))?;
|
.ok_or(MakeMoveError::NoPiece(origin))?;
|
||||||
|
|
||||||
let target = ply.target_square();
|
let target = ply.target_square();
|
||||||
self.place_piece_for_move(piece, target)?;
|
board
|
||||||
|
.place_piece(piece, target, PlacePieceStrategy::PreserveExisting)
|
||||||
|
.map_err(MakeMoveError::PlacePieceError)?;
|
||||||
|
|
||||||
self.board.en_passant_target = match target.rank() {
|
board.en_passant_target = match target.rank() {
|
||||||
Rank::FOUR => Some(Square::from_file_rank(target.file(), Rank::THREE)),
|
Rank::FOUR => Some(Square::from_file_rank(target.file(), Rank::THREE)),
|
||||||
Rank::FIVE => Some(Square::from_file_rank(target.file(), Rank::SIX)),
|
Rank::FIVE => Some(Square::from_file_rank(target.file(), Rank::SIX)),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let record = self.register_move_record(ply, None);
|
let record = MoveRecord::new(board, ply, None);
|
||||||
|
|
||||||
self.advance_clocks(HalfMoveClock::Advance);
|
self.advance_clocks(HalfMoveClock::Advance);
|
||||||
|
|
||||||
|
@ -152,11 +175,14 @@ impl Position {
|
||||||
let origin_square = ply.origin_square();
|
let origin_square = ply.origin_square();
|
||||||
let target_square = ply.target_square();
|
let target_square = ply.target_square();
|
||||||
|
|
||||||
let piece = self.get_piece_for_move(origin_square)?;
|
let board = self.board_mut();
|
||||||
|
|
||||||
|
let piece = board
|
||||||
|
.get_piece(origin_square)
|
||||||
|
.ok_or(MakeMoveError::NoPiece(origin_square))?;
|
||||||
|
|
||||||
if ply.is_en_passant() {
|
if ply.is_en_passant() {
|
||||||
let en_passant_square = self
|
let en_passant_square = board
|
||||||
.board
|
|
||||||
.en_passant_target
|
.en_passant_target
|
||||||
.ok_or(MakeMoveError::NoCaptureSquare)?;
|
.ok_or(MakeMoveError::NoCaptureSquare)?;
|
||||||
if target_square != en_passant_square {
|
if target_square != en_passant_square {
|
||||||
|
@ -164,24 +190,23 @@ impl Position {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let board = self.board_mut();
|
||||||
|
|
||||||
let capture_square = ply.capture_square().ok_or(MakeMoveError::NoCaptureSquare)?;
|
let capture_square = ply.capture_square().ok_or(MakeMoveError::NoCaptureSquare)?;
|
||||||
let captured_piece = self
|
let captured_piece = board
|
||||||
.remove_piece(capture_square)
|
.remove_piece(capture_square)
|
||||||
.ok_or(MakeMoveError::NoCapturePiece(capture_square))?;
|
.ok_or(MakeMoveError::NoCapturePiece(capture_square))?;
|
||||||
|
|
||||||
// Register the capture
|
board.remove_piece(origin_square).unwrap();
|
||||||
self.captures.push(piece.color, captured_piece);
|
|
||||||
|
|
||||||
self.remove_piece(origin_square).unwrap();
|
|
||||||
|
|
||||||
if let Some(promotion_shape) = ply.promotion_shape() {
|
if let Some(promotion_shape) = ply.promotion_shape() {
|
||||||
let promoted_piece = Piece::new(piece.color, promotion_shape);
|
let promoted_piece = Piece::new(piece.color, promotion_shape);
|
||||||
self.place_piece(promoted_piece, target_square, PlacePieceStrategy::Replace)?;
|
board.place_piece(promoted_piece, target_square, PlacePieceStrategy::Replace)?;
|
||||||
} else {
|
} else {
|
||||||
self.place_piece(piece, target_square, PlacePieceStrategy::Replace)?;
|
board.place_piece(piece, target_square, PlacePieceStrategy::Replace)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let record = self.register_move_record(ply, Some(captured_piece));
|
let record = MoveRecord::new(board, ply, Some(captured_piece));
|
||||||
|
|
||||||
self.advance_clocks(HalfMoveClock::Reset);
|
self.advance_clocks(HalfMoveClock::Reset);
|
||||||
|
|
||||||
|
@ -189,20 +214,22 @@ impl Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_castle_move(&mut self, ply: Move, wing: Wing) -> MakeMoveResult {
|
fn make_castle_move(&mut self, ply: Move, wing: Wing) -> MakeMoveResult {
|
||||||
self.board.color_can_castle(wing, None)?;
|
let board = self.board_mut();
|
||||||
|
|
||||||
let active_color = self.board.active_color;
|
board.color_can_castle(wing, None)?;
|
||||||
|
|
||||||
|
let active_color = board.active_color;
|
||||||
let parameters = Board::castling_parameters(wing, active_color);
|
let parameters = Board::castling_parameters(wing, active_color);
|
||||||
|
|
||||||
let king = self.board.remove_piece(parameters.origin.king).unwrap();
|
let king = board.remove_piece(parameters.origin.king).unwrap();
|
||||||
self.place_piece(king, parameters.target.king, PlacePieceStrategy::default())?;
|
board.place_piece(king, parameters.target.king, PlacePieceStrategy::default())?;
|
||||||
|
|
||||||
let rook = self.board.remove_piece(parameters.origin.rook).unwrap();
|
let rook = board.remove_piece(parameters.origin.rook).unwrap();
|
||||||
self.place_piece(rook, parameters.target.rook, PlacePieceStrategy::default())?;
|
board.place_piece(rook, parameters.target.rook, PlacePieceStrategy::default())?;
|
||||||
|
|
||||||
self.board.castling_rights.revoke(active_color, wing);
|
board.castling_rights.revoke(active_color, wing);
|
||||||
|
|
||||||
let record = self.register_move_record(ply, None);
|
let record = MoveRecord::new(board, ply, None);
|
||||||
|
|
||||||
self.advance_clocks(HalfMoveClock::Advance);
|
self.advance_clocks(HalfMoveClock::Advance);
|
||||||
|
|
||||||
|
@ -210,9 +237,13 @@ impl Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_promotion_move(&mut self, ply: Move) -> MakeMoveResult {
|
fn make_promotion_move(&mut self, ply: Move) -> MakeMoveResult {
|
||||||
|
let board = self.board_mut();
|
||||||
|
|
||||||
let origin = ply.origin_square();
|
let origin = ply.origin_square();
|
||||||
|
|
||||||
let piece = self.get_piece_for_move(origin)?;
|
let piece = board
|
||||||
|
.get_piece(origin)
|
||||||
|
.ok_or(MakeMoveError::NoPiece(origin))?;
|
||||||
if !piece.is_pawn() {
|
if !piece.is_pawn() {
|
||||||
return Err(MakeMoveError::InvalidPiece(piece));
|
return Err(MakeMoveError::InvalidPiece(piece));
|
||||||
}
|
}
|
||||||
|
@ -223,68 +254,37 @@ impl Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(promotion_shape) = ply.promotion_shape() {
|
if let Some(promotion_shape) = ply.promotion_shape() {
|
||||||
self.remove_piece(origin);
|
board.remove_piece(origin);
|
||||||
let promoted_piece = Piece::new(piece.color, promotion_shape);
|
let promoted_piece = Piece::new(piece.color, promotion_shape);
|
||||||
self.place_piece(promoted_piece, target, PlacePieceStrategy::PreserveExisting)?;
|
board.place_piece(promoted_piece, target, PlacePieceStrategy::PreserveExisting)?;
|
||||||
} else {
|
} else {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
"Cannot make a promotion move with a ply that has no promotion shape: {ply:?}",
|
"Cannot make a promotion move with a ply that has no promotion shape: {ply:?}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let record = self.register_move_record(ply, None);
|
let record = MoveRecord::new(board, ply, None);
|
||||||
|
|
||||||
self.advance_clocks(HalfMoveClock::Reset);
|
self.advance_clocks(HalfMoveClock::Reset);
|
||||||
|
|
||||||
Ok(record)
|
Ok(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_move_record(&mut self, ply: Move, capture: Option<Piece>) -> MoveRecord {
|
|
||||||
let record = MoveRecord::new(&self.board, ply, capture);
|
|
||||||
self.moves.push(record.clone());
|
|
||||||
|
|
||||||
record
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Position {
|
|
||||||
fn get_piece_for_move(&mut self, square: Square) -> Result<Piece, MakeMoveError> {
|
|
||||||
self.get_piece(square).ok_or(MakeMoveError::NoPiece(square))
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.place_piece(piece, square, PlacePieceStrategy::PreserveExisting)
|
|
||||||
.map_err(MakeMoveError::PlacePieceError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
||||||
enum HalfMoveClock {
|
|
||||||
Reset,
|
|
||||||
#[default]
|
|
||||||
Advance,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Position {
|
|
||||||
fn advance_clocks(&mut self, half_move_clock: HalfMoveClock) {
|
fn advance_clocks(&mut self, half_move_clock: HalfMoveClock) {
|
||||||
|
let board = self.board_mut();
|
||||||
|
|
||||||
match half_move_clock {
|
match half_move_clock {
|
||||||
HalfMoveClock::Reset => self.board.half_move_clock = 0,
|
HalfMoveClock::Reset => board.half_move_clock = 0,
|
||||||
HalfMoveClock::Advance => self.board.half_move_clock += 1,
|
HalfMoveClock::Advance => board.half_move_clock += 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.board.active_color = self.board.active_color.next();
|
board.active_color = board.active_color.next();
|
||||||
|
|
||||||
if self.board.active_color == Color::White {
|
if board.active_color == Color::White {
|
||||||
self.board.full_move_number += 1;
|
board.full_move_number += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Position {
|
|
||||||
fn validate_move(&self, ply: Move, validate: ValidateMove) -> Result<(), MakeMoveError> {
|
fn validate_move(&self, ply: Move, validate: ValidateMove) -> Result<(), MakeMoveError> {
|
||||||
if validate == ValidateMove::No {
|
if validate == ValidateMove::No {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -292,13 +292,14 @@ impl Position {
|
||||||
|
|
||||||
let active_piece = self.validate_active_piece(ply)?;
|
let active_piece = self.validate_active_piece(ply)?;
|
||||||
|
|
||||||
|
let board = self.board();
|
||||||
let origin_square = ply.origin_square();
|
let origin_square = ply.origin_square();
|
||||||
let target_square = ply.target_square();
|
let target_square = ply.target_square();
|
||||||
|
|
||||||
// Pawns can see squares they can't move to. So, calculating valid
|
// Pawns can see squares they can't move to. So, calculating valid
|
||||||
// squares requires a concept that includes Sight, but adds pawn pushes.
|
// squares requires a concept that includes Sight, but adds pawn pushes.
|
||||||
// In ChessFriend, that concept is Movement.
|
// In ChessFriend, that concept is Movement.
|
||||||
let movement = active_piece.movement(origin_square, &self.board);
|
let movement = active_piece.movement(origin_square, board);
|
||||||
if !movement.contains(target_square) {
|
if !movement.contains(target_square) {
|
||||||
return Err(MakeMoveError::NoMove {
|
return Err(MakeMoveError::NoMove {
|
||||||
piece: active_piece,
|
piece: active_piece,
|
||||||
|
@ -310,7 +311,7 @@ impl Position {
|
||||||
// TODO: En Passant capture.
|
// TODO: En Passant capture.
|
||||||
|
|
||||||
if let Some(capture_square) = ply.capture_square() {
|
if let Some(capture_square) = ply.capture_square() {
|
||||||
if let Some(captured_piece) = self.board.get_piece(capture_square) {
|
if let Some(captured_piece) = board.get_piece(capture_square) {
|
||||||
if captured_piece.color == active_piece.color {
|
if captured_piece.color == active_piece.color {
|
||||||
return Err(MakeMoveError::InvalidCapture(capture_square));
|
return Err(MakeMoveError::InvalidCapture(capture_square));
|
||||||
}
|
}
|
||||||
|
@ -325,12 +326,13 @@ impl Position {
|
||||||
fn validate_active_piece(&self, ply: Move) -> Result<Piece, MakeMoveError> {
|
fn validate_active_piece(&self, ply: Move) -> Result<Piece, MakeMoveError> {
|
||||||
let origin_square = ply.origin_square();
|
let origin_square = ply.origin_square();
|
||||||
|
|
||||||
let active_piece = self
|
let board = self.board();
|
||||||
.board
|
|
||||||
|
let active_piece = board
|
||||||
.get_piece(origin_square)
|
.get_piece(origin_square)
|
||||||
.ok_or(MakeMoveError::NoPiece(origin_square))?;
|
.ok_or(MakeMoveError::NoPiece(origin_square))?;
|
||||||
|
|
||||||
if active_piece.color != self.board.active_color {
|
if active_piece.color != board.active_color {
|
||||||
return Err(MakeMoveError::NonActiveColor {
|
return Err(MakeMoveError::NonActiveColor {
|
||||||
piece: active_piece,
|
piece: active_piece,
|
||||||
square: origin_square,
|
square: origin_square,
|
||||||
|
@ -341,159 +343,167 @@ impl Position {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
enum HalfMoveClock {
|
||||||
|
Reset,
|
||||||
|
#[default]
|
||||||
|
Advance,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{test_position, ValidateMove};
|
use crate::{Move, PromotionShape};
|
||||||
|
use chessfriend_board::test_board;
|
||||||
use chessfriend_core::{piece, Color, Square};
|
use chessfriend_core::{piece, Color, Square};
|
||||||
use chessfriend_moves::{Move, PromotionShape};
|
|
||||||
|
|
||||||
type TestResult = Result<(), MakeMoveError>;
|
type TestResult = Result<(), MakeMoveError>;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_quiet_move() -> TestResult {
|
fn make_quiet_move() -> TestResult {
|
||||||
let mut pos = test_position!(White Pawn on C2);
|
let mut board = test_board!(White Pawn on C2);
|
||||||
|
|
||||||
let ply = Move::quiet(Square::C2, Square::C3);
|
let ply = Move::quiet(Square::C2, Square::C3);
|
||||||
pos.make_move(ply, ValidateMove::Yes)?;
|
board.make_move(ply, ValidateMove::Yes)?;
|
||||||
|
|
||||||
assert_eq!(pos.get_piece(Square::C2), None);
|
assert_eq!(board.get_piece(Square::C2), None);
|
||||||
assert_eq!(pos.get_piece(Square::C3), Some(piece!(White Pawn)));
|
assert_eq!(board.get_piece(Square::C3), Some(piece!(White Pawn)));
|
||||||
assert_eq!(pos.board.active_color, Color::Black);
|
assert_eq!(board.active_color, Color::Black);
|
||||||
assert_eq!(pos.board.half_move_clock, 1);
|
assert_eq!(board.half_move_clock, 1);
|
||||||
|
|
||||||
pos.board.active_color = Color::White;
|
board.active_color = Color::White;
|
||||||
|
|
||||||
let ply = Move::quiet(Square::C3, Square::C4);
|
let ply = Move::quiet(Square::C3, Square::C4);
|
||||||
pos.make_move(ply, ValidateMove::Yes)?;
|
board.make_move(ply, ValidateMove::Yes)?;
|
||||||
|
|
||||||
assert_eq!(pos.get_piece(Square::C3), None);
|
assert_eq!(board.get_piece(Square::C3), None);
|
||||||
assert_eq!(pos.get_piece(Square::C4), Some(piece!(White Pawn)));
|
assert_eq!(board.get_piece(Square::C4), Some(piece!(White Pawn)));
|
||||||
assert_eq!(pos.board.active_color, Color::Black);
|
assert_eq!(board.active_color, Color::Black);
|
||||||
assert_eq!(pos.board.half_move_clock, 2);
|
assert_eq!(board.half_move_clock, 2);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_invalid_quiet_pawn_move() {
|
fn make_invalid_quiet_pawn_move() {
|
||||||
let mut pos = test_position!(White Pawn on C2);
|
let mut board = test_board!(White Pawn on C2);
|
||||||
|
|
||||||
let ply = Move::quiet(Square::C2, Square::D2);
|
let ply = Move::quiet(Square::C2, Square::D2);
|
||||||
let result = pos.make_move(ply, ValidateMove::Yes);
|
let result = board.make_move(ply, ValidateMove::Yes);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(pos.get_piece(Square::C2), Some(piece!(White Pawn)));
|
assert_eq!(board.get_piece(Square::C2), Some(piece!(White Pawn)));
|
||||||
assert_eq!(pos.get_piece(Square::D2), None);
|
assert_eq!(board.get_piece(Square::D2), None);
|
||||||
assert_eq!(pos.board.active_color, Color::White);
|
assert_eq!(board.active_color, Color::White);
|
||||||
assert_eq!(pos.board.half_move_clock, 0);
|
assert_eq!(board.half_move_clock, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_capture_move() -> TestResult {
|
fn make_capture_move() -> TestResult {
|
||||||
let mut pos = test_position![
|
let mut board = test_board![
|
||||||
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);
|
||||||
pos.make_move(ply, ValidateMove::Yes)?;
|
let result = board.make_move(ply, ValidateMove::Yes)?;
|
||||||
|
|
||||||
assert_eq!(pos.get_piece(Square::C2), None);
|
assert_eq!(result.captured_piece, Some(piece!(Black Rook)));
|
||||||
assert_eq!(pos.get_piece(Square::F5), Some(piece!(White Bishop)));
|
|
||||||
assert_eq!(pos.captures.last(Color::White), Some(&piece!(Black Rook)));
|
assert_eq!(board.get_piece(Square::C2), None);
|
||||||
assert_eq!(pos.board.active_color, Color::Black);
|
assert_eq!(board.get_piece(Square::F5), Some(piece!(White Bishop)));
|
||||||
assert_eq!(pos.board.half_move_clock, 0);
|
assert_eq!(board.active_color, Color::Black);
|
||||||
|
assert_eq!(board.half_move_clock, 0);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_en_passant_capture_move() -> TestResult {
|
fn make_en_passant_capture_move() -> TestResult {
|
||||||
let mut pos = test_position![
|
let mut board = test_board![
|
||||||
Black Pawn on F4,
|
Black Pawn on F4,
|
||||||
White Pawn on E2
|
White Pawn on E2
|
||||||
];
|
];
|
||||||
|
|
||||||
let ply = Move::double_push(Square::E2, Square::E4);
|
let ply = Move::double_push(Square::E2, Square::E4);
|
||||||
pos.make_move(ply, ValidateMove::Yes)?;
|
board.make_move(ply, ValidateMove::Yes)?;
|
||||||
|
|
||||||
assert_eq!(pos.get_piece(Square::E2), None);
|
assert_eq!(board.get_piece(Square::E2), None);
|
||||||
assert_eq!(pos.get_piece(Square::E4), Some(piece!(White Pawn)));
|
assert_eq!(board.get_piece(Square::E4), Some(piece!(White Pawn)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pos.board.en_passant_target,
|
board.en_passant_target,
|
||||||
Some(Square::E3),
|
Some(Square::E3),
|
||||||
"en passant square not set"
|
"en passant square not set"
|
||||||
);
|
);
|
||||||
assert_eq!(pos.board.active_color, Color::Black);
|
assert_eq!(board.active_color, Color::Black);
|
||||||
assert_eq!(pos.board.half_move_clock, 1);
|
assert_eq!(board.half_move_clock, 1);
|
||||||
|
|
||||||
let ply = Move::en_passant_capture(Square::F4, Square::E3);
|
let ply = Move::en_passant_capture(Square::F4, Square::E3);
|
||||||
pos.make_move(ply, ValidateMove::Yes)?;
|
let result = board.make_move(ply, ValidateMove::Yes)?;
|
||||||
|
|
||||||
assert_eq!(pos.get_piece(Square::F4), None);
|
assert_eq!(result.captured_piece, Some(piece!(White Pawn)));
|
||||||
assert_eq!(pos.get_piece(Square::E3), Some(piece!(Black Pawn)));
|
|
||||||
|
assert_eq!(board.get_piece(Square::F4), None);
|
||||||
|
assert_eq!(board.get_piece(Square::E3), Some(piece!(Black Pawn)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pos.get_piece(Square::E4),
|
board.get_piece(Square::E4),
|
||||||
None,
|
None,
|
||||||
"capture target pawn not removed"
|
"capture target pawn not removed"
|
||||||
);
|
);
|
||||||
assert_eq!(pos.captures.last(Color::Black), Some(&piece!(White Pawn)));
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_last_rank_quiet_move_without_promotion() {
|
fn make_last_rank_quiet_move_without_promotion() {
|
||||||
let mut pos = test_position!(
|
let mut board = test_board!(
|
||||||
White Pawn on A7
|
White Pawn on A7
|
||||||
);
|
);
|
||||||
|
|
||||||
let ply = Move::quiet(Square::A7, Square::A8);
|
let ply = Move::quiet(Square::A7, Square::A8);
|
||||||
let result = pos.make_move(ply, ValidateMove::Yes);
|
let result = board.make_move(ply, ValidateMove::Yes);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(pos.board.active_color, Color::White);
|
assert_eq!(board.active_color, Color::White);
|
||||||
assert_eq!(pos.get_piece(Square::A7), Some(piece!(White Pawn)));
|
assert_eq!(board.get_piece(Square::A7), Some(piece!(White Pawn)));
|
||||||
assert_eq!(pos.get_piece(Square::A8), None);
|
assert_eq!(board.get_piece(Square::A8), None);
|
||||||
assert_eq!(pos.board.half_move_clock, 0);
|
assert_eq!(board.half_move_clock, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_promotion_move() -> TestResult {
|
fn make_promotion_move() -> TestResult {
|
||||||
let mut pos = test_position![
|
let mut board = test_board![
|
||||||
Black Pawn on E7,
|
Black Pawn on E7,
|
||||||
White Pawn on F7,
|
White Pawn on F7,
|
||||||
];
|
];
|
||||||
|
|
||||||
let ply = Move::promotion(Square::F7, Square::F8, PromotionShape::Queen);
|
let ply = Move::promotion(Square::F7, Square::F8, PromotionShape::Queen);
|
||||||
pos.make_move(ply, ValidateMove::Yes)?;
|
board.make_move(ply, ValidateMove::Yes)?;
|
||||||
|
|
||||||
assert_eq!(pos.get_piece(Square::F7), None);
|
assert_eq!(board.get_piece(Square::F7), None);
|
||||||
assert_eq!(pos.get_piece(Square::F8), Some(piece!(White Queen)));
|
assert_eq!(board.get_piece(Square::F8), Some(piece!(White Queen)));
|
||||||
assert_eq!(pos.board.active_color, Color::Black);
|
assert_eq!(board.active_color, Color::Black);
|
||||||
assert_eq!(pos.board.half_move_clock, 0);
|
assert_eq!(board.half_move_clock, 0);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_white_kingside_castle() -> TestResult {
|
fn make_white_kingside_castle() -> TestResult {
|
||||||
let mut pos = test_position![
|
let mut board = test_board![
|
||||||
White Rook on H1,
|
White Rook on H1,
|
||||||
White King on E1,
|
White King on E1,
|
||||||
];
|
];
|
||||||
|
|
||||||
let ply = Move::castle(Wing::KingSide);
|
let ply = Move::castle(Wing::KingSide);
|
||||||
pos.make_move(ply, ValidateMove::Yes)?;
|
board.make_move(ply, ValidateMove::Yes)?;
|
||||||
|
|
||||||
assert_eq!(pos.board.active_color, Color::Black);
|
assert_eq!(board.active_color, Color::Black);
|
||||||
assert_eq!(pos.get_piece(Square::E1), None);
|
assert_eq!(board.get_piece(Square::E1), None);
|
||||||
assert_eq!(pos.get_piece(Square::H1), None);
|
assert_eq!(board.get_piece(Square::H1), None);
|
||||||
assert_eq!(pos.get_piece(Square::G1), Some(piece!(White King)));
|
assert_eq!(board.get_piece(Square::G1), Some(piece!(White King)));
|
||||||
assert_eq!(pos.get_piece(Square::F1), Some(piece!(White Rook)));
|
assert_eq!(board.get_piece(Square::F1), Some(piece!(White Rook)));
|
||||||
assert!(!pos
|
assert!(!board
|
||||||
.board
|
|
||||||
.castling_rights
|
.castling_rights
|
||||||
.color_has_right(Color::White, Wing::KingSide));
|
.color_has_right(Color::White, Wing::KingSide));
|
||||||
|
|
||||||
|
@ -502,21 +512,20 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_white_queenside_castle() -> TestResult {
|
fn make_white_queenside_castle() -> TestResult {
|
||||||
let mut pos = test_position![
|
let mut board = test_board![
|
||||||
White King on E1,
|
White King on E1,
|
||||||
White Rook on A1,
|
White Rook on A1,
|
||||||
];
|
];
|
||||||
|
|
||||||
let ply = Move::castle(Wing::QueenSide);
|
let ply = Move::castle(Wing::QueenSide);
|
||||||
pos.make_move(ply, ValidateMove::Yes)?;
|
board.make_move(ply, ValidateMove::Yes)?;
|
||||||
|
|
||||||
assert_eq!(pos.board.active_color, Color::Black);
|
assert_eq!(board.active_color, Color::Black);
|
||||||
assert_eq!(pos.get_piece(Square::E1), None);
|
assert_eq!(board.get_piece(Square::E1), None);
|
||||||
assert_eq!(pos.get_piece(Square::A1), None);
|
assert_eq!(board.get_piece(Square::A1), None);
|
||||||
assert_eq!(pos.get_piece(Square::C1), Some(piece!(White King)));
|
assert_eq!(board.get_piece(Square::C1), Some(piece!(White King)));
|
||||||
assert_eq!(pos.get_piece(Square::D1), Some(piece!(White Rook)));
|
assert_eq!(board.get_piece(Square::D1), Some(piece!(White Rook)));
|
||||||
assert!(!pos
|
assert!(!board
|
||||||
.board
|
|
||||||
.castling_rights
|
.castling_rights
|
||||||
.color_has_right(Color::White, Wing::QueenSide));
|
.color_has_right(Color::White, Wing::QueenSide));
|
||||||
|
|
|
@ -11,4 +11,4 @@ mod testing;
|
||||||
|
|
||||||
pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy};
|
pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy};
|
||||||
pub use chessfriend_moves::GeneratedMove;
|
pub use chessfriend_moves::GeneratedMove;
|
||||||
pub use position::{Position, ValidateMove};
|
pub use position::Position;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
mod captures;
|
mod captures;
|
||||||
mod make_move;
|
|
||||||
mod unmake_move;
|
mod unmake_move;
|
||||||
|
|
||||||
use chessfriend_moves::{
|
use chessfriend_moves::{
|
||||||
|
@ -9,14 +8,14 @@ use chessfriend_moves::{
|
||||||
AllPiecesMoveGenerator, BishopMoveGenerator, KingMoveGenerator, KnightMoveGenerator,
|
AllPiecesMoveGenerator, BishopMoveGenerator, KingMoveGenerator, KnightMoveGenerator,
|
||||||
PawnMoveGenerator, QueenMoveGenerator, RookMoveGenerator,
|
PawnMoveGenerator, QueenMoveGenerator, RookMoveGenerator,
|
||||||
},
|
},
|
||||||
GeneratedMove, Move, MoveRecord,
|
GeneratedMove, MakeMove, Move, MoveRecord, ValidateMove,
|
||||||
};
|
};
|
||||||
pub use make_move::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, PlacePieceError, PlacePieceStrategy,
|
display::DiagramFormatter, fen::ToFenStr, Board, BoardProvider, PlacePieceError,
|
||||||
|
PlacePieceStrategy,
|
||||||
};
|
};
|
||||||
use chessfriend_core::{Color, Piece, Shape, Square};
|
use chessfriend_core::{Color, Piece, Shape, Square};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -142,6 +141,16 @@ 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
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::position::MakeMoveError;
|
use chessfriend_moves::{BuildMoveError, MakeMoveError};
|
||||||
use chessfriend_moves::BuildMoveError;
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_move_list {
|
macro_rules! assert_move_list {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue