[board, core, moves, position] Implement castling
Implement a new method on Position that evaluates whether the active color can castle on a given wing of the board. Then, implement making a castling move in the position. Make a new Wing enum in the core crate to specify kingside or queenside. Replace the Castle enum from the board crate with this one. This caused a lot of churn... Along the way fix a bunch of tests. Note: there's still no way to actually make a castling move in explorer.
This commit is contained in:
parent
6816e350eb
commit
0c1863acb9
18 changed files with 499 additions and 258 deletions
|
@ -7,7 +7,7 @@ use crate::{
|
|||
PieceSet,
|
||||
};
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Color, Piece, Shape, Square};
|
||||
use chessfriend_core::{Color, Piece, Shape, Square, Wing};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Board {
|
||||
|
@ -82,10 +82,12 @@ impl Board {
|
|||
}
|
||||
|
||||
impl Board {
|
||||
/// A [`BitBoard`] of squares occupied by pieces of all colors.
|
||||
pub fn occupancy(&self) -> BitBoard {
|
||||
self.pieces.occpuancy()
|
||||
}
|
||||
|
||||
/// A [`BitBoard`] of squares that are vacant.
|
||||
pub fn vacancy(&self) -> BitBoard {
|
||||
!self.occupancy()
|
||||
}
|
||||
|
@ -99,6 +101,13 @@ impl Board {
|
|||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
#[must_use]
|
||||
pub fn castling_parameters(&self, wing: Wing) -> &'static castle::Parameters {
|
||||
&castle::Parameters::BY_COLOR[self.active_color as usize][wing as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
pub fn display(&self) -> DiagramFormatter<'_> {
|
||||
DiagramFormatter::new(self)
|
||||
|
|
|
@ -3,22 +3,5 @@
|
|||
mod parameters;
|
||||
mod rights;
|
||||
|
||||
pub use parameters::Parameters;
|
||||
pub use rights::Rights;
|
||||
|
||||
use chessfriend_core::Color;
|
||||
use parameters::Parameters;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Castle {
|
||||
KingSide = 0,
|
||||
QueenSide = 1,
|
||||
}
|
||||
|
||||
impl Castle {
|
||||
pub const ALL: [Castle; 2] = [Castle::KingSide, Castle::QueenSide];
|
||||
|
||||
pub fn parameters(self, color: Color) -> &'static Parameters {
|
||||
&Parameters::BY_COLOR[color as usize][self as usize]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,32 @@
|
|||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Color, Square};
|
||||
use chessfriend_core::{Color, Square, Wing};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Parameters {
|
||||
/// Origin squares of the king and rook.
|
||||
origin: Squares,
|
||||
pub origin: Squares,
|
||||
|
||||
/// Target or destination squares for the king and rook.
|
||||
target: Squares,
|
||||
pub target: Squares,
|
||||
|
||||
/// The set of squares that must be clear of any pieces in order to perform
|
||||
/// this castle.
|
||||
clear: BitBoard,
|
||||
pub clear: BitBoard,
|
||||
|
||||
/// The set of squares that must not be attacked (i.e. visible to opposing
|
||||
/// pieces) in order to perform this castle.
|
||||
check: BitBoard,
|
||||
pub check: BitBoard,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Squares {
|
||||
pub struct Squares {
|
||||
pub king: Square,
|
||||
pub rook: Square,
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
/// Parameters for each castling move, organized by color and board-side.
|
||||
pub(super) const BY_COLOR: [[Self; 2]; Color::NUM] = [
|
||||
pub(crate) const BY_COLOR: [[Self; Wing::NUM]; Color::NUM] = [
|
||||
[
|
||||
Parameters {
|
||||
origin: Squares {
|
||||
|
@ -80,31 +81,8 @@ impl Parameters {
|
|||
],
|
||||
];
|
||||
|
||||
pub fn king_origin_square(&self) -> Square {
|
||||
self.origin.king
|
||||
}
|
||||
|
||||
pub fn rook_origin_square(&self) -> Square {
|
||||
self.origin.rook
|
||||
}
|
||||
|
||||
pub fn king_target_square(&self) -> Square {
|
||||
self.target.king
|
||||
}
|
||||
|
||||
pub fn rook_target_square(&self) -> Square {
|
||||
self.target.rook
|
||||
}
|
||||
|
||||
/// A [`BitBoard`] of the squares that must be clear of any piece in order
|
||||
/// to perform this castle move.
|
||||
pub fn clear_squares(&self) -> &BitBoard {
|
||||
&self.clear
|
||||
}
|
||||
|
||||
/// A [`BitBoard`] of the squares that must not be visible to opposing
|
||||
/// pieces in order to perform this castle move.
|
||||
pub fn check_squares(&self) -> &BitBoard {
|
||||
&self.check
|
||||
#[must_use]
|
||||
pub fn get(color: Color, wing: Wing) -> &'static Parameters {
|
||||
&Self::BY_COLOR[color as usize][wing as usize]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use super::Castle;
|
||||
use chessfriend_core::Color;
|
||||
use chessfriend_core::{Color, Wing};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
|
@ -13,16 +12,16 @@ impl Rights {
|
|||
/// as long as they have not moved their king, or the rook on that side of
|
||||
/// the board.
|
||||
#[must_use]
|
||||
pub fn color_has_right(self, color: Color, castle: Castle) -> bool {
|
||||
(self.0 & (1 << Self::flag_offset(color, castle))) != 0
|
||||
pub fn color_has_right(self, color: Color, wing: Wing) -> bool {
|
||||
(self.0 & (1 << Self::flag_offset(color, wing))) != 0
|
||||
}
|
||||
|
||||
pub fn grant(&mut self, color: Color, castle: Castle) {
|
||||
self.0 |= 1 << Self::flag_offset(color, castle);
|
||||
pub fn grant(&mut self, color: Color, wing: Wing) {
|
||||
self.0 |= 1 << Self::flag_offset(color, wing);
|
||||
}
|
||||
|
||||
pub fn revoke(&mut self, color: Color, castle: Castle) {
|
||||
self.0 &= !(1 << Self::flag_offset(color, castle));
|
||||
pub fn revoke(&mut self, color: Color, wing: Wing) {
|
||||
self.0 &= !(1 << Self::flag_offset(color, wing));
|
||||
}
|
||||
|
||||
/// Revoke castling rights for all colors and all sides of the board.
|
||||
|
@ -32,8 +31,8 @@ impl Rights {
|
|||
}
|
||||
|
||||
impl Rights {
|
||||
fn flag_offset(color: Color, castle: Castle) -> usize {
|
||||
((color as usize) << 1) + castle as usize
|
||||
fn flag_offset(color: Color, wing: Wing) -> usize {
|
||||
((color as usize) << 1) + wing as usize
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,30 +54,30 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn bitfield_offsets() {
|
||||
assert_eq!(Rights::flag_offset(Color::White, Castle::KingSide), 0);
|
||||
assert_eq!(Rights::flag_offset(Color::White, Castle::QueenSide), 1);
|
||||
assert_eq!(Rights::flag_offset(Color::Black, Castle::KingSide), 2);
|
||||
assert_eq!(Rights::flag_offset(Color::Black, Castle::QueenSide), 3);
|
||||
assert_eq!(Rights::flag_offset(Color::White, Wing::KingSide), 0);
|
||||
assert_eq!(Rights::flag_offset(Color::White, Wing::QueenSide), 1);
|
||||
assert_eq!(Rights::flag_offset(Color::Black, Wing::KingSide), 2);
|
||||
assert_eq!(Rights::flag_offset(Color::Black, Wing::QueenSide), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_rights() {
|
||||
let mut rights = Rights::default();
|
||||
assert!(rights.color_has_right(Color::White, Castle::KingSide));
|
||||
assert!(rights.color_has_right(Color::White, Castle::QueenSide));
|
||||
assert!(rights.color_has_right(Color::Black, Castle::KingSide));
|
||||
assert!(rights.color_has_right(Color::Black, Castle::QueenSide));
|
||||
assert!(rights.color_has_right(Color::White, Wing::KingSide));
|
||||
assert!(rights.color_has_right(Color::White, Wing::QueenSide));
|
||||
assert!(rights.color_has_right(Color::Black, Wing::KingSide));
|
||||
assert!(rights.color_has_right(Color::Black, Wing::QueenSide));
|
||||
|
||||
rights.revoke(Color::White, Castle::QueenSide);
|
||||
assert!(rights.color_has_right(Color::White, Castle::KingSide));
|
||||
assert!(!rights.color_has_right(Color::White, Castle::QueenSide));
|
||||
assert!(rights.color_has_right(Color::Black, Castle::KingSide));
|
||||
assert!(rights.color_has_right(Color::Black, Castle::QueenSide));
|
||||
rights.revoke(Color::White, Wing::QueenSide);
|
||||
assert!(rights.color_has_right(Color::White, Wing::KingSide));
|
||||
assert!(!rights.color_has_right(Color::White, Wing::QueenSide));
|
||||
assert!(rights.color_has_right(Color::Black, Wing::KingSide));
|
||||
assert!(rights.color_has_right(Color::Black, Wing::QueenSide));
|
||||
|
||||
rights.grant(Color::White, Castle::QueenSide);
|
||||
assert!(rights.color_has_right(Color::White, Castle::KingSide));
|
||||
assert!(rights.color_has_right(Color::White, Castle::QueenSide));
|
||||
assert!(rights.color_has_right(Color::Black, Castle::KingSide));
|
||||
assert!(rights.color_has_right(Color::Black, Castle::QueenSide));
|
||||
rights.grant(Color::White, Wing::QueenSide);
|
||||
assert!(rights.color_has_right(Color::White, Wing::KingSide));
|
||||
assert!(rights.color_has_right(Color::White, Wing::QueenSide));
|
||||
assert!(rights.color_has_right(Color::Black, Wing::KingSide));
|
||||
assert!(rights.color_has_right(Color::Black, Wing::QueenSide));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::{piece_sets::PlacePieceStrategy, Board, Castle};
|
||||
use chessfriend_core::{coordinates::ParseSquareError, piece, Color, File, Piece, Rank, Square};
|
||||
use crate::{piece_sets::PlacePieceStrategy, Board};
|
||||
use chessfriend_core::{
|
||||
coordinates::ParseSquareError, piece, Color, File, Piece, Rank, Square, Wing,
|
||||
};
|
||||
use std::fmt::Write;
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -115,10 +117,10 @@ impl ToFenStr for Board {
|
|||
.map_err(ToFenStrError::FmtError)?;
|
||||
|
||||
let castling = [
|
||||
(Color::White, Castle::KingSide),
|
||||
(Color::White, Castle::QueenSide),
|
||||
(Color::Black, Castle::KingSide),
|
||||
(Color::Black, Castle::QueenSide),
|
||||
(Color::White, Wing::KingSide),
|
||||
(Color::White, Wing::QueenSide),
|
||||
(Color::Black, Wing::KingSide),
|
||||
(Color::Black, Wing::QueenSide),
|
||||
]
|
||||
.map(|(color, castle)| {
|
||||
if !self.castling_rights.color_has_right(color, castle) {
|
||||
|
@ -126,10 +128,10 @@ impl ToFenStr for Board {
|
|||
}
|
||||
|
||||
match (color, castle) {
|
||||
(Color::White, Castle::KingSide) => "K",
|
||||
(Color::White, Castle::QueenSide) => "Q",
|
||||
(Color::Black, Castle::KingSide) => "k",
|
||||
(Color::Black, Castle::QueenSide) => "q",
|
||||
(Color::White, Wing::KingSide) => "K",
|
||||
(Color::White, Wing::QueenSide) => "Q",
|
||||
(Color::Black, Wing::KingSide) => "k",
|
||||
(Color::Black, Wing::QueenSide) => "q",
|
||||
}
|
||||
})
|
||||
.concat();
|
||||
|
@ -232,10 +234,10 @@ impl FromFenStr for Board {
|
|||
} else {
|
||||
for ch in castling_rights.chars() {
|
||||
match ch {
|
||||
'K' => board.castling_rights.grant(Color::White, Castle::KingSide),
|
||||
'Q' => board.castling_rights.grant(Color::White, Castle::QueenSide),
|
||||
'k' => board.castling_rights.grant(Color::Black, Castle::KingSide),
|
||||
'q' => board.castling_rights.grant(Color::Black, Castle::QueenSide),
|
||||
'K' => board.castling_rights.grant(Color::White, Wing::KingSide),
|
||||
'Q' => board.castling_rights.grant(Color::White, Wing::QueenSide),
|
||||
'k' => board.castling_rights.grant(Color::Black, Wing::KingSide),
|
||||
'q' => board.castling_rights.grant(Color::Black, Wing::QueenSide),
|
||||
_ => return Err(FromFenStrError::InvalidValue),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,12 +10,7 @@ mod board;
|
|||
mod piece_sets;
|
||||
|
||||
pub use board::Board;
|
||||
pub use castle::Parameters as CastleParameters;
|
||||
pub use piece_sets::{PlacePieceError, PlacePieceStrategy};
|
||||
|
||||
use castle::Castle;
|
||||
use en_passant::EnPassant;
|
||||
use piece_sets::PieceSet;
|
||||
|
||||
// Used by macros.
|
||||
#[allow(unused_imports)]
|
||||
use piece_sets::{PlacePieceError, PlacePieceStrategy};
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Color, Piece, Shape, Square};
|
||||
|
||||
/// A collection of bitboards that organize pieces by color.
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
|
||||
pub(super) struct ByColor(BitBoard, [BitBoard; Color::NUM]);
|
||||
|
||||
/// A collection of bitboards that organize pieces first by color and then by piece type.
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
|
||||
pub(super) struct ByColorAndShape([[BitBoard; Shape::NUM]; Color::NUM]);
|
||||
|
||||
impl ByColor {
|
||||
pub(super) fn new(all_pieces: BitBoard, bitboards_by_color: [BitBoard; Color::NUM]) -> Self {
|
||||
ByColor(all_pieces, bitboards_by_color)
|
||||
}
|
||||
|
||||
pub(crate) fn all(&self) -> BitBoard {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub(crate) fn bitboard(&self, color: Color) -> BitBoard {
|
||||
self.1[color as usize]
|
||||
}
|
||||
|
||||
pub(super) fn set_square(&mut self, square: Square, color: Color) {
|
||||
self.0.set(square);
|
||||
self.1[color as usize].set(square);
|
||||
}
|
||||
|
||||
pub(super) fn clear_square(&mut self, square: Square, color: Color) {
|
||||
self.0.clear(square);
|
||||
self.1[color as usize].clear(square);
|
||||
}
|
||||
}
|
||||
|
||||
impl ByColorAndShape {
|
||||
pub(super) fn new(bitboards: [[BitBoard; Shape::NUM]; Color::NUM]) -> Self {
|
||||
Self(bitboards)
|
||||
}
|
||||
|
||||
pub(super) fn bitboard_for_piece(&self, piece: Piece) -> BitBoard {
|
||||
self.0[piece.color as usize][piece.shape as usize]
|
||||
}
|
||||
|
||||
pub(super) fn bitboard_for_piece_mut(&mut self, piece: Piece) -> &mut BitBoard {
|
||||
&mut self.0[piece.color as usize][piece.shape as usize]
|
||||
}
|
||||
|
||||
pub(super) fn set_square(&mut self, square: Square, piece: Piece) {
|
||||
self.bitboard_for_piece_mut(piece).set(square);
|
||||
}
|
||||
|
||||
pub(super) fn clear_square(&mut self, square: Square, piece: Piece) {
|
||||
self.bitboard_for_piece_mut(piece).clear(square);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
mod wings;
|
||||
|
||||
pub use wings::Wing;
|
||||
|
||||
use crate::Color;
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
|
@ -519,7 +523,7 @@ mod tests {
|
|||
fn bad_algebraic_input() {
|
||||
assert!("a0".parse::<Square>().is_err());
|
||||
assert!("j3".parse::<Square>().is_err());
|
||||
assert!("a11".parse::<Square>().is_err());
|
||||
assert!("a9".parse::<Square>().is_err());
|
||||
assert!("b-1".parse::<Square>().is_err());
|
||||
assert!("a 1".parse::<Square>().is_err());
|
||||
assert!("".parse::<Square>().is_err());
|
||||
|
|
22
core/src/coordinates/wings.rs
Normal file
22
core/src/coordinates/wings.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Wing {
|
||||
KingSide = 0,
|
||||
QueenSide = 1,
|
||||
}
|
||||
|
||||
impl Wing {
|
||||
pub const NUM: usize = 2;
|
||||
pub const ALL: [Wing; Self::NUM] = [Self::KingSide, Self::QueenSide];
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Wing {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Wing::KingSide => write!(f, "kingside"),
|
||||
Wing::QueenSide => write!(f, "queenside"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,6 @@ pub mod shapes;
|
|||
mod macros;
|
||||
|
||||
pub use colors::Color;
|
||||
pub use coordinates::{Direction, File, Rank, Square};
|
||||
pub use coordinates::{Direction, File, Rank, Square, Wing};
|
||||
pub use pieces::{Piece, PlacedPiece};
|
||||
pub use shapes::Shape;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::{defs::Kind, Move, PromotionShape};
|
||||
use chessfriend_board::{castle, en_passant::EnPassant};
|
||||
use chessfriend_core::{Color, File, PlacedPiece, Rank, Square};
|
||||
use chessfriend_board::{en_passant::EnPassant, CastleParameters};
|
||||
use chessfriend_core::{Color, File, PlacedPiece, Rank, Square, Wing};
|
||||
use std::result::Result as StdResult;
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -92,7 +92,7 @@ pub struct Promotion<S> {
|
|||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Castle {
|
||||
color: Color,
|
||||
castle: castle::Castle,
|
||||
wing: Wing,
|
||||
}
|
||||
|
||||
impl Style for Null {}
|
||||
|
@ -119,13 +119,13 @@ impl Style for Capture {
|
|||
|
||||
impl Style for Castle {
|
||||
fn origin_square(&self) -> Option<Square> {
|
||||
let parameters = self.castle.parameters(self.color);
|
||||
Some(parameters.king_origin_square())
|
||||
let parameters = CastleParameters::get(self.color, self.wing);
|
||||
Some(parameters.origin.king)
|
||||
}
|
||||
|
||||
fn target_square(&self) -> Option<Square> {
|
||||
let parameters = self.castle.parameters(self.color);
|
||||
Some(parameters.king_target_square())
|
||||
let parameters = CastleParameters::get(self.color, self.wing);
|
||||
Some(parameters.target.king)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,9 +255,9 @@ impl Builder<Null> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn castling(color: Color, castle: castle::Castle) -> Builder<Castle> {
|
||||
pub fn castling(color: Color, wing: Wing) -> Builder<Castle> {
|
||||
Builder {
|
||||
style: Castle { color, castle },
|
||||
style: Castle { color, wing },
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -357,9 +357,9 @@ impl Builder<Push> {
|
|||
|
||||
impl Builder<Castle> {
|
||||
fn bits(&self) -> u16 {
|
||||
let bits = match self.style.castle {
|
||||
castle::Castle::KingSide => Kind::KingSideCastle,
|
||||
castle::Castle::QueenSide => Kind::QueenSideCastle,
|
||||
let bits = match self.style.wing {
|
||||
Wing::KingSide => Kind::KingSideCastle,
|
||||
Wing::QueenSide => Kind::QueenSideCastle,
|
||||
};
|
||||
|
||||
bits as u16
|
||||
|
@ -403,6 +403,11 @@ impl Builder<EnPassantCapture> {
|
|||
Move(Kind::EnPassantCapture as u16 | self.style.move_bits_unchecked())
|
||||
}
|
||||
|
||||
/// Build an en passant move.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// Returns an error if the target or origin squares are invalid.
|
||||
pub fn build(&self) -> Result {
|
||||
Ok(Move(
|
||||
Kind::EnPassantCapture as u16 | self.style.move_bits()?,
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
use crate::builder::Builder;
|
||||
use crate::defs::Kind;
|
||||
use chessfriend_board::castle::Castle;
|
||||
use chessfriend_core::{Rank, Shape, Square};
|
||||
use chessfriend_core::{Rank, Shape, Square, Wing};
|
||||
use std::fmt;
|
||||
|
||||
/// A single player's move. In game theory parlance, this is a "ply".
|
||||
|
@ -62,10 +61,10 @@ impl Move {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn castle(&self) -> Option<Castle> {
|
||||
pub fn castle(&self) -> Option<Wing> {
|
||||
match self.flags() {
|
||||
0b0010 => Some(Castle::KingSide),
|
||||
0b0011 => Some(Castle::QueenSide),
|
||||
0b0010 => Some(Wing::KingSide),
|
||||
0b0011 => Some(Wing::QueenSide),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +76,7 @@ impl Move {
|
|||
|
||||
#[must_use]
|
||||
pub fn is_en_passant(&self) -> bool {
|
||||
self.0 == Kind::EnPassantCapture as u16
|
||||
self.flags() == Kind::EnPassantCapture as u16
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
@ -130,8 +129,8 @@ impl fmt::Display for Move {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(castle) = self.castle() {
|
||||
return match castle {
|
||||
Castle::KingSide => write!(f, "{KINGSIDE_CASTLE_STR}"),
|
||||
Castle::QueenSide => write!(f, "{QUEENSIDE_CASTLE_STR}"),
|
||||
Wing::KingSide => write!(f, "{KINGSIDE_CASTLE_STR}"),
|
||||
Wing::QueenSide => write!(f, "{QUEENSIDE_CASTLE_STR}"),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use chessfriend_board::castle::Castle;
|
||||
use chessfriend_core::{piece, Color, File, Shape, Square};
|
||||
use chessfriend_core::{piece, Color, File, Shape, Square, Wing};
|
||||
use chessfriend_moves::{testing::*, Builder, PromotionShape};
|
||||
|
||||
macro_rules! assert_flag {
|
||||
|
@ -58,51 +57,50 @@ fn move_flags_capture() -> TestResult {
|
|||
|
||||
#[test]
|
||||
fn move_flags_en_passant_capture() -> TestResult {
|
||||
let mv = unsafe {
|
||||
Builder::new()
|
||||
.from(Square::A4)
|
||||
.capturing_en_passant_on(Square::B3)
|
||||
.build_unchecked()
|
||||
};
|
||||
let ply = Builder::new()
|
||||
.from(Square::A4)
|
||||
.capturing_en_passant_on(Square::B3)
|
||||
.build()?;
|
||||
|
||||
assert_flags!(mv, false, false, true, true, false, false);
|
||||
assert_eq!(mv.origin_square(), Square::A4);
|
||||
assert_eq!(mv.target_square(), Square::B3);
|
||||
assert_eq!(mv.capture_square(), Some(Square::B4));
|
||||
assert!(ply.is_en_passant());
|
||||
assert_eq!(ply.origin_square(), Square::A4);
|
||||
assert_eq!(ply.target_square(), Square::B3);
|
||||
assert_eq!(ply.capture_square(), Some(Square::B4));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_flags_promotion() -> TestResult {
|
||||
let mv = Builder::push(&piece!(White Pawn on H7))
|
||||
let ply = Builder::push(&piece!(White Pawn on H7))
|
||||
.to(Square::H8)
|
||||
.promoting_to(PromotionShape::Queen)
|
||||
.build()?;
|
||||
|
||||
assert_flags!(mv, false, false, false, false, false, true);
|
||||
assert_eq!(mv.promotion(), Some(Shape::Queen));
|
||||
assert!(ply.is_promotion());
|
||||
assert_eq!(ply.promotion(), Some(Shape::Queen));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_flags_capture_promotion() -> TestResult {
|
||||
let mv = Builder::push(&piece!(White Pawn on H7))
|
||||
let ply = Builder::push(&piece!(White Pawn on H7))
|
||||
.to(Square::H8)
|
||||
.capturing_piece(&piece!(Black Knight on G8))
|
||||
.promoting_to(PromotionShape::Queen)
|
||||
.build()?;
|
||||
|
||||
assert_flags!(mv, false, false, false, true, false, true);
|
||||
assert_eq!(mv.promotion(), Some(Shape::Queen));
|
||||
assert!(ply.is_capture());
|
||||
assert!(ply.is_promotion());
|
||||
assert_eq!(ply.promotion(), Some(Shape::Queen));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_flags_castle() -> TestResult {
|
||||
let mv = Builder::castling(Color::White, Castle::KingSide).build()?;
|
||||
let mv = Builder::castling(Color::White, Wing::KingSide).build()?;
|
||||
|
||||
assert_flags!(mv, false, false, false, false, true, false);
|
||||
|
||||
|
|
|
@ -14,4 +14,5 @@ mod macros;
|
|||
#[macro_use]
|
||||
mod testing;
|
||||
|
||||
pub use position::{MakeMoveError, MoveBuilder as MakeMoveBuilder, Position};
|
||||
pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy};
|
||||
pub use position::{CastleEvaluationError, Position, ValidateMove};
|
||||
|
|
|
@ -5,5 +5,5 @@ mod position;
|
|||
|
||||
pub use {
|
||||
make_move::{MakeMoveError, ValidateMove},
|
||||
position::Position,
|
||||
position::{CastleEvaluationError, Position},
|
||||
};
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::{movement::Movement, Position};
|
||||
use chessfriend_board::{PlacePieceError, PlacePieceStrategy};
|
||||
use chessfriend_core::{Color, Piece, Square};
|
||||
use chessfriend_board::{CastleParameters, PlacePieceError, PlacePieceStrategy};
|
||||
use chessfriend_core::{Color, Piece, Square, Wing};
|
||||
use chessfriend_moves::Move;
|
||||
use thiserror::Error;
|
||||
|
||||
use super::CastleEvaluationError;
|
||||
|
||||
type MakeMoveResult = Result<(), MakeMoveError>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
|
@ -38,6 +40,9 @@ pub enum MakeMoveError {
|
|||
|
||||
#[error("{0}")]
|
||||
PlacePieceError(#[from] PlacePieceError),
|
||||
|
||||
#[error("{0}")]
|
||||
CastleError(#[from] CastleEvaluationError),
|
||||
}
|
||||
|
||||
pub enum UnmakeMoveError {}
|
||||
|
@ -60,6 +65,10 @@ impl Position {
|
|||
return self.make_capture_move(ply);
|
||||
}
|
||||
|
||||
if let Some(wing) = ply.castle() {
|
||||
return self.make_castle_move(wing);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -100,6 +109,27 @@ impl Position {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_castle_move(&mut self, wing: Wing) -> MakeMoveResult {
|
||||
self.active_color_can_castle(wing)?;
|
||||
|
||||
let active_color = self.board.active_color;
|
||||
let parameters = self.board.castling_parameters(wing);
|
||||
|
||||
let king = self.board.remove_piece(parameters.origin.king).unwrap();
|
||||
self.board
|
||||
.place_piece(king, parameters.target.king, PlacePieceStrategy::default())?;
|
||||
|
||||
let rook = self.board.remove_piece(parameters.origin.rook).unwrap();
|
||||
self.board
|
||||
.place_piece(rook, parameters.target.rook, PlacePieceStrategy::default())?;
|
||||
|
||||
self.board.castling_rights.revoke(active_color, wing);
|
||||
|
||||
self.advance_clocks(HalfMoveClock::Advance);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Position {
|
||||
|
@ -142,21 +172,14 @@ impl Position {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let active_piece = self.validate_active_piece(ply)?;
|
||||
|
||||
let origin_square = ply.origin_square();
|
||||
let active_piece = self
|
||||
.board
|
||||
.get_piece(origin_square)
|
||||
.ok_or(MakeMoveError::NoPiece(origin_square))?;
|
||||
|
||||
if active_piece.color != self.board.active_color {
|
||||
return Err(MakeMoveError::NonActiveColor {
|
||||
piece: active_piece,
|
||||
square: origin_square,
|
||||
});
|
||||
}
|
||||
|
||||
let target_square = ply.target_square();
|
||||
|
||||
// Pawns can see squares they can't move to. So, calculating valid
|
||||
// squares requires a concept that includes Sight, but adds pawn pushes.
|
||||
// In ChessFriend, that concept is Movement.
|
||||
let movement = active_piece.movement(origin_square, &self.board);
|
||||
if !movement.contains(target_square) {
|
||||
return Err(MakeMoveError::NoMove {
|
||||
|
@ -166,6 +189,8 @@ impl Position {
|
|||
});
|
||||
}
|
||||
|
||||
// TODO: En Passant capture.
|
||||
|
||||
if ply.is_capture() {
|
||||
let target = ply.target_square();
|
||||
if let Some(captured_piece) = self.board.get_piece(target) {
|
||||
|
@ -179,4 +204,22 @@ impl Position {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_active_piece(&self, ply: Move) -> Result<Piece, MakeMoveError> {
|
||||
let origin_square = ply.origin_square();
|
||||
|
||||
let active_piece = self
|
||||
.board
|
||||
.get_piece(origin_square)
|
||||
.ok_or(MakeMoveError::NoPiece(origin_square))?;
|
||||
|
||||
if active_piece.color != self.board.active_color {
|
||||
return Err(MakeMoveError::NonActiveColor {
|
||||
piece: active_piece,
|
||||
square: origin_square,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(active_piece)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,20 +38,168 @@ impl Position {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl Position {
|
||||
/// Return a PlacedPiece representing the rook to use for a castling move.
|
||||
pub(crate) fn rook_for_castle(&self, player: Color, castle: Castle) -> Option<PlacedPiece> {
|
||||
let square = match (player, castle) {
|
||||
(Color::White, Castle::KingSide) => Square::H1,
|
||||
(Color::White, Castle::QueenSide) => Square::A1,
|
||||
(Color::Black, Castle::KingSide) => Square::H8,
|
||||
(Color::Black, Castle::QueenSide) => Square::A8,
|
||||
};
|
||||
|
||||
self.board.piece_on_square(square)
|
||||
/// Place a piece on the board.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// See [`chessfriend_board::Board::place_piece`].
|
||||
pub fn place_piece(
|
||||
&mut self,
|
||||
piece: Piece,
|
||||
square: Square,
|
||||
strategy: PlacePieceStrategy,
|
||||
) -> Result<(), PlacePieceError> {
|
||||
self.board.place_piece(piece, square, strategy)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_piece(&self, square: Square) -> Option<Piece> {
|
||||
self.board.get_piece(square)
|
||||
}
|
||||
|
||||
pub fn remove_piece(&mut self, square: Square) -> Option<Piece> {
|
||||
self.board.remove_piece(square)
|
||||
}
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub fn sight(&self, square: Square) -> BitBoard {
|
||||
if let Some(piece) = self.get_piece(square) {
|
||||
piece.sight(square, &self.board)
|
||||
} else {
|
||||
BitBoard::empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn movement(&self, square: Square) -> BitBoard {
|
||||
if let Some(piece) = self.get_piece(square) {
|
||||
piece.movement(square, &self.board)
|
||||
} else {
|
||||
BitBoard::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub fn active_sight(&self) -> BitBoard {
|
||||
self.friendly_sight(self.board.active_color)
|
||||
}
|
||||
|
||||
/// A [`BitBoard`] of all squares the given color can see.
|
||||
pub fn friendly_sight(&self, color: Color) -> BitBoard {
|
||||
// TODO: Probably want to implement a caching layer here.
|
||||
self.board
|
||||
.friendly_occupancy(color)
|
||||
.occupied_squares(&IterationDirection::default())
|
||||
.map(|square| self.sight(square))
|
||||
.fold(BitBoard::empty(), BitOr::bitor)
|
||||
}
|
||||
|
||||
/// A [`BitBoard`] of all squares visible by colors that oppose the given color.
|
||||
pub fn opposing_sight(&self) -> BitBoard {
|
||||
// TODO: Probably want to implement a caching layer here.
|
||||
let active_color = self.board.active_color;
|
||||
Color::ALL
|
||||
.into_iter()
|
||||
.filter_map(|c| {
|
||||
if c == active_color {
|
||||
None
|
||||
} else {
|
||||
Some(self.friendly_sight(c))
|
||||
}
|
||||
})
|
||||
.fold(BitBoard::empty(), BitOr::bitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
|
||||
pub enum CastleEvaluationError {
|
||||
#[error("{color} does not have the right to castle {wing}")]
|
||||
NoRights { color: Color, wing: Wing },
|
||||
#[error("no king")]
|
||||
NoKing,
|
||||
#[error("no rook")]
|
||||
NoRook,
|
||||
#[error("castling path is not clear")]
|
||||
ObstructingPieces,
|
||||
#[error("opposing pieces check castling path")]
|
||||
CheckingPieces,
|
||||
}
|
||||
|
||||
impl Position {
|
||||
/// Evaluates whether the active color can castle toward the given wing of the board in the
|
||||
/// current position.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// Returns an error indicating why the active color cannot castle.
|
||||
pub fn active_color_can_castle(&self, wing: Wing) -> Result<(), CastleEvaluationError> {
|
||||
// TODO: Cache this result. It's expensive!
|
||||
|
||||
let active_color = self.board.active_color;
|
||||
|
||||
if !self
|
||||
.board
|
||||
.castling_rights
|
||||
.color_has_right(active_color, wing)
|
||||
{
|
||||
return Err(CastleEvaluationError::NoRights {
|
||||
color: active_color,
|
||||
wing,
|
||||
});
|
||||
}
|
||||
|
||||
let parameters = self.board.castling_parameters(wing);
|
||||
|
||||
if self.castling_king(parameters.origin.king).is_none() {
|
||||
return Err(CastleEvaluationError::NoKing);
|
||||
}
|
||||
|
||||
if self.castling_rook(parameters.origin.rook).is_none() {
|
||||
return Err(CastleEvaluationError::NoRook);
|
||||
}
|
||||
|
||||
// All squares must be clear.
|
||||
let has_obstructing_pieces = (self.board.occupancy() & parameters.clear).is_populated();
|
||||
if has_obstructing_pieces {
|
||||
return Err(CastleEvaluationError::ObstructingPieces);
|
||||
}
|
||||
|
||||
// King cannot pass through check.
|
||||
let opposing_sight = self.opposing_sight();
|
||||
let opposing_pieces_can_see_castling_path =
|
||||
(parameters.check & opposing_sight).is_populated();
|
||||
if opposing_pieces_can_see_castling_path {
|
||||
return Err(CastleEvaluationError::CheckingPieces);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn castling_king(&self, square: Square) -> Option<Piece> {
|
||||
self.get_piece(square).and_then(|piece| {
|
||||
if piece.color == self.board.active_color && piece.is_king() {
|
||||
Some(piece)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn castling_rook(&self, square: Square) -> Option<Piece> {
|
||||
self.get_piece(square).and_then(|piece| {
|
||||
if piece.color == self.board.active_color && piece.is_rook() {
|
||||
Some(piece)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl Position {
|
||||
pub fn moves(&self) -> &Moves {
|
||||
self.moves.get_or_init(|| {
|
||||
let player_to_move = self.player_to_move();
|
||||
|
@ -120,11 +268,6 @@ impl Position {
|
|||
self.moves().moves_for_piece(piece)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn sight_of_piece(&self, piece: &PlacedPiece) -> BitBoard {
|
||||
piece.sight(&self.board, self._en_passant_target_square())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn is_king_in_check(&self) -> bool {
|
||||
let danger_squares = self.king_danger(self.player_to_move());
|
||||
|
@ -213,9 +356,9 @@ impl fmt::Display for Position {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{assert_eq_bitboards, position, test_position, Position};
|
||||
use crate::{test_position, Position};
|
||||
use chessfriend_bitboard::bitboard;
|
||||
use chessfriend_core::piece;
|
||||
use chessfriend_core::{piece, Wing};
|
||||
|
||||
#[test]
|
||||
fn piece_on_square() {
|
||||
|
@ -223,22 +366,16 @@ mod tests {
|
|||
Black Bishop on F7,
|
||||
];
|
||||
|
||||
let piece = pos.board.piece_on_square(Square::F7);
|
||||
assert_eq!(piece, Some(piece!(Black Bishop on F7)));
|
||||
let piece = pos.board.get_piece(Square::F7);
|
||||
assert_eq!(piece, Some(piece!(Black Bishop)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn piece_in_starting_position() {
|
||||
let pos = test_position!(starting);
|
||||
|
||||
assert_eq!(
|
||||
pos.board.piece_on_square(Square::H1),
|
||||
Some(piece!(White Rook on H1))
|
||||
);
|
||||
assert_eq!(
|
||||
pos.board.piece_on_square(Square::A8),
|
||||
Some(piece!(Black Rook on A8))
|
||||
);
|
||||
assert_eq!(pos.board.get_piece(Square::H1), Some(piece!(White Rook)));
|
||||
assert_eq!(pos.board.get_piece(Square::A8), Some(piece!(Black Rook)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -274,38 +411,160 @@ mod tests {
|
|||
White Rook on H1
|
||||
);
|
||||
|
||||
assert!(pos.player_can_castle(Color::White, Castle::KingSide));
|
||||
assert!(pos.player_can_castle(Color::White, Castle::QueenSide));
|
||||
let rights = pos.board.castling_rights;
|
||||
assert!(rights.color_has_right(Color::White, Wing::KingSide));
|
||||
assert!(rights.color_has_right(Color::White, Wing::QueenSide));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rook_for_castle() {
|
||||
let pos = position![
|
||||
fn friendly_sight() {
|
||||
let pos = test_position!(
|
||||
White King on E4,
|
||||
);
|
||||
|
||||
let sight = pos.active_sight();
|
||||
assert_eq!(sight, bitboard![E5 F5 F4 F3 E3 D3 D4 D5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opposing_sight() {
|
||||
let pos = test_position!(
|
||||
White King on E4,
|
||||
Black Rook on E7,
|
||||
);
|
||||
|
||||
let sight = pos.opposing_sight();
|
||||
assert_eq!(sight, bitboard![A7 B7 C7 D7 F7 G7 H7 E8 E6 E5 E4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn king_for_castle() {
|
||||
let pos = test_position![
|
||||
White King on E1,
|
||||
White Rook on H1,
|
||||
White Rook on A1,
|
||||
];
|
||||
|
||||
let kingside_parameters = pos.board.castling_parameters(Wing::KingSide);
|
||||
assert_eq!(
|
||||
pos.rook_for_castle(Color::White, Castle::KingSide),
|
||||
Some(piece!(White Rook on H1))
|
||||
pos.castling_king(kingside_parameters.origin.king),
|
||||
Some(piece!(White King))
|
||||
);
|
||||
|
||||
let queenside_parameters = pos.board.castling_parameters(Wing::QueenSide);
|
||||
assert_eq!(
|
||||
pos.rook_for_castle(Color::White, Castle::QueenSide),
|
||||
Some(piece!(White Rook on A1))
|
||||
pos.castling_king(queenside_parameters.origin.king),
|
||||
Some(piece!(White King))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn danger_squares() {
|
||||
let pos = test_position!(Black, [
|
||||
fn rook_for_castle() {
|
||||
let pos = test_position![
|
||||
White King on E1,
|
||||
Black King on E7,
|
||||
White Rook on E4,
|
||||
]);
|
||||
White Rook on H1,
|
||||
];
|
||||
|
||||
let danger_squares = pos.king_danger(Color::Black);
|
||||
let expected = bitboard![D1 F1 D2 E2 F2 E3 A4 B4 C4 D4 F4 G4 H4 E5 E6 E7 E8];
|
||||
assert_eq_bitboards!(danger_squares, expected);
|
||||
let kingside_parameters = pos.board.castling_parameters(Wing::KingSide);
|
||||
assert_eq!(
|
||||
pos.castling_rook(kingside_parameters.origin.rook),
|
||||
Some(piece!(White Rook))
|
||||
);
|
||||
|
||||
let pos = test_position![
|
||||
White King on E1,
|
||||
White Rook on A1,
|
||||
];
|
||||
|
||||
let queenside_parameters = pos.board.castling_parameters(Wing::QueenSide);
|
||||
assert_eq!(
|
||||
pos.castling_rook(queenside_parameters.origin.rook),
|
||||
Some(piece!(White Rook))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_can_castle() {
|
||||
let pos = test_position![
|
||||
White King on E1,
|
||||
White Rook on H1,
|
||||
White Rook on A1,
|
||||
];
|
||||
|
||||
assert_eq!(pos.active_color_can_castle(Wing::KingSide), Ok(()));
|
||||
assert_eq!(pos.active_color_can_castle(Wing::QueenSide), Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_cannot_castle_missing_king() {
|
||||
let pos = test_position![
|
||||
White King on E2,
|
||||
White Rook on H1,
|
||||
White Rook on A1,
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
pos.active_color_can_castle(Wing::KingSide),
|
||||
Err(CastleEvaluationError::NoKing)
|
||||
);
|
||||
assert_eq!(
|
||||
pos.active_color_can_castle(Wing::QueenSide),
|
||||
Err(CastleEvaluationError::NoKing)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_cannot_castle_missing_rook() {
|
||||
let pos = test_position![
|
||||
White King on E1,
|
||||
White Rook on A1,
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
pos.active_color_can_castle(Wing::KingSide),
|
||||
Err(CastleEvaluationError::NoRook)
|
||||
);
|
||||
|
||||
let pos = test_position![
|
||||
White King on E1,
|
||||
White Rook on H1,
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
pos.active_color_can_castle(Wing::QueenSide),
|
||||
Err(CastleEvaluationError::NoRook)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_cannot_castle_obstructing_piece() {
|
||||
let pos = test_position![
|
||||
White King on E1,
|
||||
White Bishop on F1,
|
||||
White Rook on H1,
|
||||
White Rook on A1,
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
pos.active_color_can_castle(Wing::KingSide),
|
||||
Err(CastleEvaluationError::ObstructingPieces)
|
||||
);
|
||||
assert_eq!(pos.active_color_can_castle(Wing::QueenSide), Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_cannot_castle_checking_pieces() {
|
||||
let pos = test_position![
|
||||
White King on E1,
|
||||
White Rook on H1,
|
||||
White Rook on A1,
|
||||
Black Queen on C6,
|
||||
];
|
||||
|
||||
assert_eq!(pos.active_color_can_castle(Wing::KingSide), Ok(()));
|
||||
assert_eq!(
|
||||
pos.active_color_can_castle(Wing::QueenSide),
|
||||
Err(CastleEvaluationError::CheckingPieces)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::MakeMoveError;
|
||||
use crate::position::MakeMoveError;
|
||||
use chessfriend_moves::BuildMoveError;
|
||||
|
||||
#[macro_export]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue