[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,
|
PieceSet,
|
||||||
};
|
};
|
||||||
use chessfriend_bitboard::BitBoard;
|
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)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
|
@ -82,10 +82,12 @@ impl Board {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
|
/// A [`BitBoard`] of squares occupied by pieces of all colors.
|
||||||
pub fn occupancy(&self) -> BitBoard {
|
pub fn occupancy(&self) -> BitBoard {
|
||||||
self.pieces.occpuancy()
|
self.pieces.occpuancy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`BitBoard`] of squares that are vacant.
|
||||||
pub fn vacancy(&self) -> BitBoard {
|
pub fn vacancy(&self) -> BitBoard {
|
||||||
!self.occupancy()
|
!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 {
|
impl Board {
|
||||||
pub fn display(&self) -> DiagramFormatter<'_> {
|
pub fn display(&self) -> DiagramFormatter<'_> {
|
||||||
DiagramFormatter::new(self)
|
DiagramFormatter::new(self)
|
||||||
|
|
|
@ -3,22 +3,5 @@
|
||||||
mod parameters;
|
mod parameters;
|
||||||
mod rights;
|
mod rights;
|
||||||
|
|
||||||
|
pub use parameters::Parameters;
|
||||||
pub use rights::Rights;
|
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_bitboard::BitBoard;
|
||||||
use chessfriend_core::{Color, Square};
|
use chessfriend_core::{Color, Square, Wing};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Parameters {
|
pub struct Parameters {
|
||||||
/// Origin squares of the king and rook.
|
/// Origin squares of the king and rook.
|
||||||
origin: Squares,
|
pub origin: Squares,
|
||||||
|
|
||||||
/// Target or destination squares for the king and rook.
|
/// 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
|
/// The set of squares that must be clear of any pieces in order to perform
|
||||||
/// this castle.
|
/// this castle.
|
||||||
clear: BitBoard,
|
pub clear: BitBoard,
|
||||||
|
|
||||||
/// The set of squares that must not be attacked (i.e. visible to opposing
|
/// The set of squares that must not be attacked (i.e. visible to opposing
|
||||||
/// pieces) in order to perform this castle.
|
/// pieces) in order to perform this castle.
|
||||||
check: BitBoard,
|
pub check: BitBoard,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(super) struct Squares {
|
pub struct Squares {
|
||||||
pub king: Square,
|
pub king: Square,
|
||||||
pub rook: Square,
|
pub rook: Square,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parameters {
|
impl Parameters {
|
||||||
/// Parameters for each castling move, organized by color and board-side.
|
/// 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 {
|
Parameters {
|
||||||
origin: Squares {
|
origin: Squares {
|
||||||
|
@ -80,31 +81,8 @@ impl Parameters {
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn king_origin_square(&self) -> Square {
|
#[must_use]
|
||||||
self.origin.king
|
pub fn get(color: Color, wing: Wing) -> &'static Parameters {
|
||||||
}
|
&Self::BY_COLOR[color as usize][wing as usize]
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use super::Castle;
|
use chessfriend_core::{Color, Wing};
|
||||||
use chessfriend_core::Color;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
#[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
|
/// as long as they have not moved their king, or the rook on that side of
|
||||||
/// the board.
|
/// the board.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn color_has_right(self, color: Color, castle: Castle) -> bool {
|
pub fn color_has_right(self, color: Color, wing: Wing) -> bool {
|
||||||
(self.0 & (1 << Self::flag_offset(color, castle))) != 0
|
(self.0 & (1 << Self::flag_offset(color, wing))) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grant(&mut self, color: Color, castle: Castle) {
|
pub fn grant(&mut self, color: Color, wing: Wing) {
|
||||||
self.0 |= 1 << Self::flag_offset(color, castle);
|
self.0 |= 1 << Self::flag_offset(color, wing);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn revoke(&mut self, color: Color, castle: Castle) {
|
pub fn revoke(&mut self, color: Color, wing: Wing) {
|
||||||
self.0 &= !(1 << Self::flag_offset(color, castle));
|
self.0 &= !(1 << Self::flag_offset(color, wing));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Revoke castling rights for all colors and all sides of the board.
|
/// Revoke castling rights for all colors and all sides of the board.
|
||||||
|
@ -32,8 +31,8 @@ impl Rights {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rights {
|
impl Rights {
|
||||||
fn flag_offset(color: Color, castle: Castle) -> usize {
|
fn flag_offset(color: Color, wing: Wing) -> usize {
|
||||||
((color as usize) << 1) + castle as usize
|
((color as usize) << 1) + wing as usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,30 +54,30 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bitfield_offsets() {
|
fn bitfield_offsets() {
|
||||||
assert_eq!(Rights::flag_offset(Color::White, Castle::KingSide), 0);
|
assert_eq!(Rights::flag_offset(Color::White, Wing::KingSide), 0);
|
||||||
assert_eq!(Rights::flag_offset(Color::White, Castle::QueenSide), 1);
|
assert_eq!(Rights::flag_offset(Color::White, Wing::QueenSide), 1);
|
||||||
assert_eq!(Rights::flag_offset(Color::Black, Castle::KingSide), 2);
|
assert_eq!(Rights::flag_offset(Color::Black, Wing::KingSide), 2);
|
||||||
assert_eq!(Rights::flag_offset(Color::Black, Castle::QueenSide), 3);
|
assert_eq!(Rights::flag_offset(Color::Black, Wing::QueenSide), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_rights() {
|
fn default_rights() {
|
||||||
let mut rights = Rights::default();
|
let mut rights = Rights::default();
|
||||||
assert!(rights.color_has_right(Color::White, Castle::KingSide));
|
assert!(rights.color_has_right(Color::White, Wing::KingSide));
|
||||||
assert!(rights.color_has_right(Color::White, Castle::QueenSide));
|
assert!(rights.color_has_right(Color::White, Wing::QueenSide));
|
||||||
assert!(rights.color_has_right(Color::Black, Castle::KingSide));
|
assert!(rights.color_has_right(Color::Black, Wing::KingSide));
|
||||||
assert!(rights.color_has_right(Color::Black, Castle::QueenSide));
|
assert!(rights.color_has_right(Color::Black, Wing::QueenSide));
|
||||||
|
|
||||||
rights.revoke(Color::White, Castle::QueenSide);
|
rights.revoke(Color::White, Wing::QueenSide);
|
||||||
assert!(rights.color_has_right(Color::White, Castle::KingSide));
|
assert!(rights.color_has_right(Color::White, Wing::KingSide));
|
||||||
assert!(!rights.color_has_right(Color::White, Castle::QueenSide));
|
assert!(!rights.color_has_right(Color::White, Wing::QueenSide));
|
||||||
assert!(rights.color_has_right(Color::Black, Castle::KingSide));
|
assert!(rights.color_has_right(Color::Black, Wing::KingSide));
|
||||||
assert!(rights.color_has_right(Color::Black, Castle::QueenSide));
|
assert!(rights.color_has_right(Color::Black, Wing::QueenSide));
|
||||||
|
|
||||||
rights.grant(Color::White, Castle::QueenSide);
|
rights.grant(Color::White, Wing::QueenSide);
|
||||||
assert!(rights.color_has_right(Color::White, Castle::KingSide));
|
assert!(rights.color_has_right(Color::White, Wing::KingSide));
|
||||||
assert!(rights.color_has_right(Color::White, Castle::QueenSide));
|
assert!(rights.color_has_right(Color::White, Wing::QueenSide));
|
||||||
assert!(rights.color_has_right(Color::Black, Castle::KingSide));
|
assert!(rights.color_has_right(Color::Black, Wing::KingSide));
|
||||||
assert!(rights.color_has_right(Color::Black, Castle::QueenSide));
|
assert!(rights.color_has_right(Color::Black, Wing::QueenSide));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::{piece_sets::PlacePieceStrategy, Board, Castle};
|
use crate::{piece_sets::PlacePieceStrategy, Board};
|
||||||
use chessfriend_core::{coordinates::ParseSquareError, piece, Color, File, Piece, Rank, Square};
|
use chessfriend_core::{
|
||||||
|
coordinates::ParseSquareError, piece, Color, File, Piece, Rank, Square, Wing,
|
||||||
|
};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -115,10 +117,10 @@ impl ToFenStr for Board {
|
||||||
.map_err(ToFenStrError::FmtError)?;
|
.map_err(ToFenStrError::FmtError)?;
|
||||||
|
|
||||||
let castling = [
|
let castling = [
|
||||||
(Color::White, Castle::KingSide),
|
(Color::White, Wing::KingSide),
|
||||||
(Color::White, Castle::QueenSide),
|
(Color::White, Wing::QueenSide),
|
||||||
(Color::Black, Castle::KingSide),
|
(Color::Black, Wing::KingSide),
|
||||||
(Color::Black, Castle::QueenSide),
|
(Color::Black, Wing::QueenSide),
|
||||||
]
|
]
|
||||||
.map(|(color, castle)| {
|
.map(|(color, castle)| {
|
||||||
if !self.castling_rights.color_has_right(color, castle) {
|
if !self.castling_rights.color_has_right(color, castle) {
|
||||||
|
@ -126,10 +128,10 @@ impl ToFenStr for Board {
|
||||||
}
|
}
|
||||||
|
|
||||||
match (color, castle) {
|
match (color, castle) {
|
||||||
(Color::White, Castle::KingSide) => "K",
|
(Color::White, Wing::KingSide) => "K",
|
||||||
(Color::White, Castle::QueenSide) => "Q",
|
(Color::White, Wing::QueenSide) => "Q",
|
||||||
(Color::Black, Castle::KingSide) => "k",
|
(Color::Black, Wing::KingSide) => "k",
|
||||||
(Color::Black, Castle::QueenSide) => "q",
|
(Color::Black, Wing::QueenSide) => "q",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.concat();
|
.concat();
|
||||||
|
@ -232,10 +234,10 @@ impl FromFenStr for Board {
|
||||||
} else {
|
} else {
|
||||||
for ch in castling_rights.chars() {
|
for ch in castling_rights.chars() {
|
||||||
match ch {
|
match ch {
|
||||||
'K' => board.castling_rights.grant(Color::White, Castle::KingSide),
|
'K' => board.castling_rights.grant(Color::White, Wing::KingSide),
|
||||||
'Q' => board.castling_rights.grant(Color::White, Castle::QueenSide),
|
'Q' => board.castling_rights.grant(Color::White, Wing::QueenSide),
|
||||||
'k' => board.castling_rights.grant(Color::Black, Castle::KingSide),
|
'k' => board.castling_rights.grant(Color::Black, Wing::KingSide),
|
||||||
'q' => board.castling_rights.grant(Color::Black, Castle::QueenSide),
|
'q' => board.castling_rights.grant(Color::Black, Wing::QueenSide),
|
||||||
_ => return Err(FromFenStrError::InvalidValue),
|
_ => return Err(FromFenStrError::InvalidValue),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,7 @@ mod board;
|
||||||
mod piece_sets;
|
mod piece_sets;
|
||||||
|
|
||||||
pub use board::Board;
|
pub use board::Board;
|
||||||
|
pub use castle::Parameters as CastleParameters;
|
||||||
pub use piece_sets::{PlacePieceError, PlacePieceStrategy};
|
pub use piece_sets::{PlacePieceError, PlacePieceStrategy};
|
||||||
|
|
||||||
use castle::Castle;
|
|
||||||
use en_passant::EnPassant;
|
|
||||||
use piece_sets::PieceSet;
|
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>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
mod wings;
|
||||||
|
|
||||||
|
pub use wings::Wing;
|
||||||
|
|
||||||
use crate::Color;
|
use crate::Color;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -519,7 +523,7 @@ mod tests {
|
||||||
fn bad_algebraic_input() {
|
fn bad_algebraic_input() {
|
||||||
assert!("a0".parse::<Square>().is_err());
|
assert!("a0".parse::<Square>().is_err());
|
||||||
assert!("j3".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!("b-1".parse::<Square>().is_err());
|
||||||
assert!("a 1".parse::<Square>().is_err());
|
assert!("a 1".parse::<Square>().is_err());
|
||||||
assert!("".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;
|
mod macros;
|
||||||
|
|
||||||
pub use colors::Color;
|
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 pieces::{Piece, PlacedPiece};
|
||||||
pub use shapes::Shape;
|
pub use shapes::Shape;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::{defs::Kind, Move, PromotionShape};
|
use crate::{defs::Kind, Move, PromotionShape};
|
||||||
use chessfriend_board::{castle, en_passant::EnPassant};
|
use chessfriend_board::{en_passant::EnPassant, CastleParameters};
|
||||||
use chessfriend_core::{Color, File, PlacedPiece, Rank, Square};
|
use chessfriend_core::{Color, File, PlacedPiece, Rank, Square, Wing};
|
||||||
use std::result::Result as StdResult;
|
use std::result::Result as StdResult;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ pub struct Promotion<S> {
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct Castle {
|
pub struct Castle {
|
||||||
color: Color,
|
color: Color,
|
||||||
castle: castle::Castle,
|
wing: Wing,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Style for Null {}
|
impl Style for Null {}
|
||||||
|
@ -119,13 +119,13 @@ impl Style for Capture {
|
||||||
|
|
||||||
impl Style for Castle {
|
impl Style for Castle {
|
||||||
fn origin_square(&self) -> Option<Square> {
|
fn origin_square(&self) -> Option<Square> {
|
||||||
let parameters = self.castle.parameters(self.color);
|
let parameters = CastleParameters::get(self.color, self.wing);
|
||||||
Some(parameters.king_origin_square())
|
Some(parameters.origin.king)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn target_square(&self) -> Option<Square> {
|
fn target_square(&self) -> Option<Square> {
|
||||||
let parameters = self.castle.parameters(self.color);
|
let parameters = CastleParameters::get(self.color, self.wing);
|
||||||
Some(parameters.king_target_square())
|
Some(parameters.target.king)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,9 +255,9 @@ impl Builder<Null> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn castling(color: Color, castle: castle::Castle) -> Builder<Castle> {
|
pub fn castling(color: Color, wing: Wing) -> Builder<Castle> {
|
||||||
Builder {
|
Builder {
|
||||||
style: Castle { color, castle },
|
style: Castle { color, wing },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,9 +357,9 @@ impl Builder<Push> {
|
||||||
|
|
||||||
impl Builder<Castle> {
|
impl Builder<Castle> {
|
||||||
fn bits(&self) -> u16 {
|
fn bits(&self) -> u16 {
|
||||||
let bits = match self.style.castle {
|
let bits = match self.style.wing {
|
||||||
castle::Castle::KingSide => Kind::KingSideCastle,
|
Wing::KingSide => Kind::KingSideCastle,
|
||||||
castle::Castle::QueenSide => Kind::QueenSideCastle,
|
Wing::QueenSide => Kind::QueenSideCastle,
|
||||||
};
|
};
|
||||||
|
|
||||||
bits as u16
|
bits as u16
|
||||||
|
@ -403,6 +403,11 @@ impl Builder<EnPassantCapture> {
|
||||||
Move(Kind::EnPassantCapture as u16 | self.style.move_bits_unchecked())
|
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 {
|
pub fn build(&self) -> Result {
|
||||||
Ok(Move(
|
Ok(Move(
|
||||||
Kind::EnPassantCapture as u16 | self.style.move_bits()?,
|
Kind::EnPassantCapture as u16 | self.style.move_bits()?,
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
use crate::builder::Builder;
|
use crate::builder::Builder;
|
||||||
use crate::defs::Kind;
|
use crate::defs::Kind;
|
||||||
use chessfriend_board::castle::Castle;
|
use chessfriend_core::{Rank, Shape, Square, Wing};
|
||||||
use chessfriend_core::{Rank, Shape, Square};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// A single player's move. In game theory parlance, this is a "ply".
|
/// A single player's move. In game theory parlance, this is a "ply".
|
||||||
|
@ -62,10 +61,10 @@ impl Move {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn castle(&self) -> Option<Castle> {
|
pub fn castle(&self) -> Option<Wing> {
|
||||||
match self.flags() {
|
match self.flags() {
|
||||||
0b0010 => Some(Castle::KingSide),
|
0b0010 => Some(Wing::KingSide),
|
||||||
0b0011 => Some(Castle::QueenSide),
|
0b0011 => Some(Wing::QueenSide),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +76,7 @@ impl Move {
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_en_passant(&self) -> bool {
|
pub fn is_en_passant(&self) -> bool {
|
||||||
self.0 == Kind::EnPassantCapture as u16
|
self.flags() == Kind::EnPassantCapture as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -130,8 +129,8 @@ impl fmt::Display for Move {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if let Some(castle) = self.castle() {
|
if let Some(castle) = self.castle() {
|
||||||
return match castle {
|
return match castle {
|
||||||
Castle::KingSide => write!(f, "{KINGSIDE_CASTLE_STR}"),
|
Wing::KingSide => write!(f, "{KINGSIDE_CASTLE_STR}"),
|
||||||
Castle::QueenSide => write!(f, "{QUEENSIDE_CASTLE_STR}"),
|
Wing::QueenSide => write!(f, "{QUEENSIDE_CASTLE_STR}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use chessfriend_board::castle::Castle;
|
use chessfriend_core::{piece, Color, File, Shape, Square, Wing};
|
||||||
use chessfriend_core::{piece, Color, File, Shape, Square};
|
|
||||||
use chessfriend_moves::{testing::*, Builder, PromotionShape};
|
use chessfriend_moves::{testing::*, Builder, PromotionShape};
|
||||||
|
|
||||||
macro_rules! assert_flag {
|
macro_rules! assert_flag {
|
||||||
|
@ -58,51 +57,50 @@ fn move_flags_capture() -> TestResult {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_flags_en_passant_capture() -> TestResult {
|
fn move_flags_en_passant_capture() -> TestResult {
|
||||||
let mv = unsafe {
|
let ply = Builder::new()
|
||||||
Builder::new()
|
.from(Square::A4)
|
||||||
.from(Square::A4)
|
.capturing_en_passant_on(Square::B3)
|
||||||
.capturing_en_passant_on(Square::B3)
|
.build()?;
|
||||||
.build_unchecked()
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_flags!(mv, false, false, true, true, false, false);
|
assert!(ply.is_en_passant());
|
||||||
assert_eq!(mv.origin_square(), Square::A4);
|
assert_eq!(ply.origin_square(), Square::A4);
|
||||||
assert_eq!(mv.target_square(), Square::B3);
|
assert_eq!(ply.target_square(), Square::B3);
|
||||||
assert_eq!(mv.capture_square(), Some(Square::B4));
|
assert_eq!(ply.capture_square(), Some(Square::B4));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_flags_promotion() -> TestResult {
|
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)
|
.to(Square::H8)
|
||||||
.promoting_to(PromotionShape::Queen)
|
.promoting_to(PromotionShape::Queen)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
assert_flags!(mv, false, false, false, false, false, true);
|
assert!(ply.is_promotion());
|
||||||
assert_eq!(mv.promotion(), Some(Shape::Queen));
|
assert_eq!(ply.promotion(), Some(Shape::Queen));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_flags_capture_promotion() -> TestResult {
|
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)
|
.to(Square::H8)
|
||||||
.capturing_piece(&piece!(Black Knight on G8))
|
.capturing_piece(&piece!(Black Knight on G8))
|
||||||
.promoting_to(PromotionShape::Queen)
|
.promoting_to(PromotionShape::Queen)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
assert_flags!(mv, false, false, false, true, false, true);
|
assert!(ply.is_capture());
|
||||||
assert_eq!(mv.promotion(), Some(Shape::Queen));
|
assert!(ply.is_promotion());
|
||||||
|
assert_eq!(ply.promotion(), Some(Shape::Queen));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_flags_castle() -> TestResult {
|
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);
|
assert_flags!(mv, false, false, false, false, true, false);
|
||||||
|
|
||||||
|
|
|
@ -14,4 +14,5 @@ mod macros;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod testing;
|
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 {
|
pub use {
|
||||||
make_move::{MakeMoveError, ValidateMove},
|
make_move::{MakeMoveError, ValidateMove},
|
||||||
position::Position,
|
position::{CastleEvaluationError, Position},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::{movement::Movement, Position};
|
use crate::{movement::Movement, Position};
|
||||||
use chessfriend_board::{PlacePieceError, PlacePieceStrategy};
|
use chessfriend_board::{CastleParameters, PlacePieceError, PlacePieceStrategy};
|
||||||
use chessfriend_core::{Color, Piece, Square};
|
use chessfriend_core::{Color, Piece, Square, Wing};
|
||||||
use chessfriend_moves::Move;
|
use chessfriend_moves::Move;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use super::CastleEvaluationError;
|
||||||
|
|
||||||
type MakeMoveResult = Result<(), MakeMoveError>;
|
type MakeMoveResult = Result<(), MakeMoveError>;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
@ -38,6 +40,9 @@ pub enum MakeMoveError {
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
PlacePieceError(#[from] PlacePieceError),
|
PlacePieceError(#[from] PlacePieceError),
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
CastleError(#[from] CastleEvaluationError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum UnmakeMoveError {}
|
pub enum UnmakeMoveError {}
|
||||||
|
@ -60,6 +65,10 @@ impl Position {
|
||||||
return self.make_capture_move(ply);
|
return self.make_capture_move(ply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(wing) = ply.castle() {
|
||||||
|
return self.make_castle_move(wing);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +109,27 @@ impl Position {
|
||||||
|
|
||||||
Ok(())
|
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 {
|
impl Position {
|
||||||
|
@ -142,21 +172,14 @@ impl Position {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let active_piece = self.validate_active_piece(ply)?;
|
||||||
|
|
||||||
let origin_square = ply.origin_square();
|
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();
|
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);
|
let movement = active_piece.movement(origin_square, &self.board);
|
||||||
if !movement.contains(target_square) {
|
if !movement.contains(target_square) {
|
||||||
return Err(MakeMoveError::NoMove {
|
return Err(MakeMoveError::NoMove {
|
||||||
|
@ -166,6 +189,8 @@ impl Position {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: En Passant capture.
|
||||||
|
|
||||||
if ply.is_capture() {
|
if ply.is_capture() {
|
||||||
let target = ply.target_square();
|
let target = ply.target_square();
|
||||||
if let Some(captured_piece) = self.board.get_piece(target) {
|
if let Some(captured_piece) = self.board.get_piece(target) {
|
||||||
|
@ -179,4 +204,22 @@ impl Position {
|
||||||
|
|
||||||
Ok(())
|
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 {
|
impl Position {
|
||||||
/// Return a PlacedPiece representing the rook to use for a castling move.
|
/// Place a piece on the board.
|
||||||
pub(crate) fn rook_for_castle(&self, player: Color, castle: Castle) -> Option<PlacedPiece> {
|
///
|
||||||
let square = match (player, castle) {
|
/// ## Errors
|
||||||
(Color::White, Castle::KingSide) => Square::H1,
|
///
|
||||||
(Color::White, Castle::QueenSide) => Square::A1,
|
/// See [`chessfriend_board::Board::place_piece`].
|
||||||
(Color::Black, Castle::KingSide) => Square::H8,
|
pub fn place_piece(
|
||||||
(Color::Black, Castle::QueenSide) => Square::A8,
|
&mut self,
|
||||||
};
|
piece: Piece,
|
||||||
|
square: Square,
|
||||||
self.board.piece_on_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 {
|
pub fn moves(&self) -> &Moves {
|
||||||
self.moves.get_or_init(|| {
|
self.moves.get_or_init(|| {
|
||||||
let player_to_move = self.player_to_move();
|
let player_to_move = self.player_to_move();
|
||||||
|
@ -120,11 +268,6 @@ impl Position {
|
||||||
self.moves().moves_for_piece(piece)
|
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)]
|
#[cfg(test)]
|
||||||
pub(crate) fn is_king_in_check(&self) -> bool {
|
pub(crate) fn is_king_in_check(&self) -> bool {
|
||||||
let danger_squares = self.king_danger(self.player_to_move());
|
let danger_squares = self.king_danger(self.player_to_move());
|
||||||
|
@ -213,9 +356,9 @@ impl fmt::Display for Position {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{assert_eq_bitboards, position, test_position, Position};
|
use crate::{test_position, Position};
|
||||||
use chessfriend_bitboard::bitboard;
|
use chessfriend_bitboard::bitboard;
|
||||||
use chessfriend_core::piece;
|
use chessfriend_core::{piece, Wing};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn piece_on_square() {
|
fn piece_on_square() {
|
||||||
|
@ -223,22 +366,16 @@ mod tests {
|
||||||
Black Bishop on F7,
|
Black Bishop on F7,
|
||||||
];
|
];
|
||||||
|
|
||||||
let piece = pos.board.piece_on_square(Square::F7);
|
let piece = pos.board.get_piece(Square::F7);
|
||||||
assert_eq!(piece, Some(piece!(Black Bishop on F7)));
|
assert_eq!(piece, Some(piece!(Black Bishop)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn piece_in_starting_position() {
|
fn piece_in_starting_position() {
|
||||||
let pos = test_position!(starting);
|
let pos = test_position!(starting);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(pos.board.get_piece(Square::H1), Some(piece!(White Rook)));
|
||||||
pos.board.piece_on_square(Square::H1),
|
assert_eq!(pos.board.get_piece(Square::A8), Some(piece!(Black Rook)));
|
||||||
Some(piece!(White Rook on H1))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
pos.board.piece_on_square(Square::A8),
|
|
||||||
Some(piece!(Black Rook on A8))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -274,38 +411,160 @@ mod tests {
|
||||||
White Rook on H1
|
White Rook on H1
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(pos.player_can_castle(Color::White, Castle::KingSide));
|
let rights = pos.board.castling_rights;
|
||||||
assert!(pos.player_can_castle(Color::White, Castle::QueenSide));
|
assert!(rights.color_has_right(Color::White, Wing::KingSide));
|
||||||
|
assert!(rights.color_has_right(Color::White, Wing::QueenSide));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rook_for_castle() {
|
fn friendly_sight() {
|
||||||
let pos = position![
|
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 King on E1,
|
||||||
White Rook on H1,
|
White Rook on H1,
|
||||||
White Rook on A1,
|
White Rook on A1,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let kingside_parameters = pos.board.castling_parameters(Wing::KingSide);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pos.rook_for_castle(Color::White, Castle::KingSide),
|
pos.castling_king(kingside_parameters.origin.king),
|
||||||
Some(piece!(White Rook on H1))
|
Some(piece!(White King))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let queenside_parameters = pos.board.castling_parameters(Wing::QueenSide);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pos.rook_for_castle(Color::White, Castle::QueenSide),
|
pos.castling_king(queenside_parameters.origin.king),
|
||||||
Some(piece!(White Rook on A1))
|
Some(piece!(White King))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn danger_squares() {
|
fn rook_for_castle() {
|
||||||
let pos = test_position!(Black, [
|
let pos = test_position![
|
||||||
White King on E1,
|
White King on E1,
|
||||||
Black King on E7,
|
White Rook on H1,
|
||||||
White Rook on E4,
|
];
|
||||||
]);
|
|
||||||
|
|
||||||
let danger_squares = pos.king_danger(Color::Black);
|
let kingside_parameters = pos.board.castling_parameters(Wing::KingSide);
|
||||||
let expected = bitboard![D1 F1 D2 E2 F2 E3 A4 B4 C4 D4 F4 G4 H4 E5 E6 E7 E8];
|
assert_eq!(
|
||||||
assert_eq_bitboards!(danger_squares, expected);
|
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>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::MakeMoveError;
|
use crate::position::MakeMoveError;
|
||||||
use chessfriend_moves::BuildMoveError;
|
use chessfriend_moves::BuildMoveError;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue