WIP
This commit is contained in:
parent
d5cdf273c8
commit
091cc99cb3
42 changed files with 805 additions and 1662 deletions
|
@ -1,16 +1,22 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::{castle, display::DiagramFormatter, Castle, Clock, PieceSet};
|
||||
use crate::{
|
||||
castle,
|
||||
display::DiagramFormatter,
|
||||
piece_sets::{PlacePieceError, PlacePieceStrategy},
|
||||
PieceSet,
|
||||
};
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
||||
use std::iter::Iterator;
|
||||
use chessfriend_core::{Color, Piece, Shape, Square};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq)]
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Board {
|
||||
pub clock: Clock,
|
||||
pub active_color: Color,
|
||||
pub pieces: PieceSet,
|
||||
pub castling_rights: castle::Rights,
|
||||
pub en_passant_target: Option<Square>,
|
||||
pub half_move_clock: u32,
|
||||
pub full_move_number: u32,
|
||||
}
|
||||
|
||||
impl Board {
|
||||
|
@ -46,12 +52,44 @@ impl Board {
|
|||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
#[must_use]
|
||||
pub fn player_to_move(&self) -> Color {
|
||||
self.clock.active_color()
|
||||
pub fn get_piece(&self, square: Square) -> Option<Piece> {
|
||||
self.pieces.get(square)
|
||||
}
|
||||
|
||||
/// Place a piece on the board.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// When is called with [`PlacePieceStrategy::PreserveExisting`], and a piece already exists on
|
||||
/// `square`, this method returns a [`PlacePieceError::ExistingPiece`] error.
|
||||
///
|
||||
pub fn place_piece(
|
||||
&mut self,
|
||||
piece: Piece,
|
||||
square: Square,
|
||||
strategy: PlacePieceStrategy,
|
||||
) -> Result<(), PlacePieceError> {
|
||||
self.pieces.place(piece, square, strategy)
|
||||
}
|
||||
|
||||
pub fn remove_piece(&mut self, square: Square) -> Option<Piece> {
|
||||
self.pieces.remove(square)
|
||||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
#[must_use]
|
||||
pub fn display(&self) -> DiagramFormatter<'_> {
|
||||
DiagramFormatter::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl Board {
|
||||
/// The rook to use for a castling move.
|
||||
#[must_use]
|
||||
pub fn rook_for_castle(&self, player: Color, castle: Castle) -> Option<PlacedPiece> {
|
||||
|
@ -71,73 +109,26 @@ impl Board {
|
|||
/// through a square that an enemy piece can see
|
||||
#[must_use]
|
||||
pub fn player_can_castle(&self, player: Color, castle: Castle) -> bool {
|
||||
if !self.castling_rights.is_set(player, castle.into()) {
|
||||
if !self.castling_rights.is_set(player, castle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let castling_parameters = castle.parameters(player);
|
||||
|
||||
let all_pieces = self.all_pieces_bitboard();
|
||||
let all_pieces = self.pieces.all_pieces();
|
||||
if !(all_pieces & castling_parameters.clear_squares()).is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let danger_squares = self.king_danger(player);
|
||||
if !(danger_squares & castling_parameters.check_squares()).is_empty() {
|
||||
return false;
|
||||
}
|
||||
// TODO: Reimplement king_danger here or in Position.
|
||||
// let danger_squares = self.king_danger(player);
|
||||
// if !(danger_squares & castling_parameters.check_squares()).is_empty() {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// A [`BitBoard`] representing the set of squares containing a piece. This
|
||||
/// set is the inverse of [`Board::empty_squares`].
|
||||
#[must_use]
|
||||
pub fn occupied_squares(&self) -> BitBoard {
|
||||
self.pieces.all_pieces()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn friendly_pieces_bitboard(&self) -> BitBoard {
|
||||
self.pieces.all_pieces_of_color(self.clock.active_color())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn opposing_pieces_bitboard(&self) -> BitBoard {
|
||||
self.pieces
|
||||
.all_pieces_of_color(self.clock.active_color().other())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn all_pieces(&self) -> (BitBoard, BitBoard) {
|
||||
(
|
||||
self.friendly_pieces_bitboard(),
|
||||
self.opposing_pieces_bitboard(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn all_pieces_bitboard(&self) -> BitBoard {
|
||||
self.pieces.all_pieces()
|
||||
}
|
||||
|
||||
pub fn all_pieces_of_color_bitboard(&self, color: Color) -> BitBoard {
|
||||
self.pieces.all_pieces_of_color(color)
|
||||
}
|
||||
|
||||
/// A [`BitBoard`] representing the set of squares containing a piece. This
|
||||
/// set is the inverse of [`Board::occupied_squares`].
|
||||
#[must_use]
|
||||
pub fn empty_squares(&self) -> BitBoard {
|
||||
!self.occupied_squares()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn piece_on_square(&self, square: Square) -> Option<PlacedPiece> {
|
||||
self.pieces
|
||||
.get(square)
|
||||
.map(|piece| PlacedPiece::new(piece, square))
|
||||
}
|
||||
|
||||
pub fn iter_all_pieces(&self) -> impl Iterator<Item = PlacedPiece> + '_ {
|
||||
self.pieces.iter()
|
||||
}
|
||||
|
@ -147,58 +138,8 @@ impl Board {
|
|||
.iter()
|
||||
.filter(move |piece| piece.color() == color)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn en_passant_target(&self) -> Option<Square> {
|
||||
self.en_passant_target
|
||||
}
|
||||
|
||||
fn king_bitboard(&self, player: Color) -> BitBoard {
|
||||
self.pieces.bitboard_for_piece(Piece::king(player))
|
||||
}
|
||||
|
||||
pub(crate) fn king_square(&self, player: Color) -> Square {
|
||||
self.king_bitboard(player).try_into().unwrap()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn display(&self) -> DiagramFormatter<'_> {
|
||||
DiagramFormatter::new(self)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn bitboard_for_color(&self, color: Color) -> BitBoard {
|
||||
self.pieces.bitboard_for_color(color)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn bitboard_for_piece(&self, piece: Piece) -> BitBoard {
|
||||
self.pieces.bitboard_for_piece(piece)
|
||||
}
|
||||
|
||||
/// A [`BitBoard`] representing the squares where a king of the given color will
|
||||
/// be in danger of being captured by the opposing player. If the king is on
|
||||
/// one of these squares, it is in check. The king cannot move to these
|
||||
/// squares.
|
||||
pub(crate) fn king_danger(&self, color: Color) -> BitBoard {
|
||||
let board_without_king = {
|
||||
let mut cloned_board = self.clone();
|
||||
cloned_board.pieces.remove(self.king_square(color));
|
||||
cloned_board
|
||||
};
|
||||
|
||||
BitBoard::full()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Board {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.pieces == other.pieces
|
||||
&& self.castling_rights == other.castling_rights
|
||||
&& self.en_passant_target == other.en_passant_target
|
||||
&& self.clock == other.clock
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
@ -207,26 +148,11 @@ mod tests {
|
|||
use chessfriend_core::piece;
|
||||
|
||||
#[test]
|
||||
fn piece_on_square() {
|
||||
let pos = test_board![
|
||||
fn get_piece_on_square() {
|
||||
let board = test_board![
|
||||
Black Bishop on F7,
|
||||
];
|
||||
|
||||
let piece = pos.piece_on_square(Square::F7);
|
||||
assert_eq!(piece, Some(piece!(Black Bishop on F7)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn piece_in_starting_position() {
|
||||
let board = test_board!(starting);
|
||||
|
||||
assert_eq!(
|
||||
board.piece_on_square(Square::H1),
|
||||
Some(piece!(White Rook on H1))
|
||||
);
|
||||
assert_eq!(
|
||||
board.piece_on_square(Square::A8),
|
||||
Some(piece!(Black Rook on A8))
|
||||
);
|
||||
assert_eq!(board.get_piece(Square::F7), Some(piece!(Black Bishop)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,22 +13,25 @@ impl Rights {
|
|||
/// as long as they have not moved their king, or the rook on that side of
|
||||
/// the board.
|
||||
#[must_use]
|
||||
pub fn is_set(self, color: Color, castle: Castle) -> bool {
|
||||
pub fn color_has_right(self, color: Color, castle: Castle) -> bool {
|
||||
(self.0 & (1 << Self::flag_offset(color, castle))) != 0
|
||||
}
|
||||
|
||||
pub fn set(&mut self, color: Color, castle: Castle) {
|
||||
pub fn grant(&mut self, color: Color, castle: Castle) {
|
||||
self.0 |= 1 << Self::flag_offset(color, castle);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, color: Color, castle: Castle) {
|
||||
pub fn revoke(&mut self, color: Color, castle: Castle) {
|
||||
self.0 &= !(1 << Self::flag_offset(color, castle));
|
||||
}
|
||||
|
||||
pub fn clear_all(&mut self) {
|
||||
/// Revoke castling rights for all colors and all sides of the board.
|
||||
pub fn revoke_all(&mut self) {
|
||||
self.0 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Rights {
|
||||
fn flag_offset(color: Color, castle: Castle) -> usize {
|
||||
((color as usize) << 1) + castle as usize
|
||||
}
|
||||
|
@ -61,21 +64,21 @@ mod tests {
|
|||
#[test]
|
||||
fn default_rights() {
|
||||
let mut rights = Rights::default();
|
||||
assert!(rights.is_set(Color::White, Castle::KingSide));
|
||||
assert!(rights.is_set(Color::White, Castle::QueenSide));
|
||||
assert!(rights.is_set(Color::Black, Castle::KingSide));
|
||||
assert!(rights.is_set(Color::Black, 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.clear(Color::White, Castle::QueenSide);
|
||||
assert!(rights.is_set(Color::White, Castle::KingSide));
|
||||
assert!(!rights.is_set(Color::White, Castle::QueenSide));
|
||||
assert!(rights.is_set(Color::Black, Castle::KingSide));
|
||||
assert!(rights.is_set(Color::Black, Castle::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.set(Color::White, Castle::QueenSide);
|
||||
assert!(rights.is_set(Color::White, Castle::KingSide));
|
||||
assert!(rights.is_set(Color::White, Castle::QueenSide));
|
||||
assert!(rights.is_set(Color::Black, Castle::KingSide));
|
||||
assert!(rights.is_set(Color::Black, Castle::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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::Board;
|
|||
use chessfriend_core::{File, Rank, Square};
|
||||
use std::fmt;
|
||||
|
||||
#[must_use]
|
||||
pub struct DiagramFormatter<'a>(&'a Board);
|
||||
|
||||
impl<'a> DiagramFormatter<'a> {
|
||||
|
@ -21,8 +22,8 @@ impl<'a> fmt::Display for DiagramFormatter<'a> {
|
|||
|
||||
for file in File::ALL {
|
||||
let square = Square::from_file_rank(file, rank);
|
||||
match self.0.piece_on_square(square) {
|
||||
Some(placed_piece) => write!(f, "{} ", placed_piece.piece())?,
|
||||
match self.0.get_piece(square) {
|
||||
Some(piece) => write!(f, "{piece} ")?,
|
||||
None => write!(f, "· ")?,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::{piece_sets::PlacePieceStrategy, Board, Castle, EnPassant};
|
||||
use crate::{piece_sets::PlacePieceStrategy, Board, Castle};
|
||||
use chessfriend_core::{
|
||||
coordinates::ParseSquareError, piece, Color, File, Piece, PlacedPiece, Rank, Square,
|
||||
};
|
||||
|
@ -69,7 +69,7 @@ impl ToFenStr for Board {
|
|||
for rank in Rank::ALL.into_iter().rev() {
|
||||
for file in File::ALL {
|
||||
let square = Square::from_file_rank(file, rank);
|
||||
match self.piece_on_square(square) {
|
||||
match self.get_piece(square) {
|
||||
Some(piece) => {
|
||||
if empty_squares > 0 {
|
||||
write!(fen_string, "{empty_squares}")
|
||||
|
@ -93,7 +93,7 @@ impl ToFenStr for Board {
|
|||
}
|
||||
}
|
||||
|
||||
write!(fen_string, " {}", self.player_to_move().to_fen_str()?)
|
||||
write!(fen_string, " {}", self.active_color.to_fen_str()?)
|
||||
.map_err(ToFenStrError::FmtError)?;
|
||||
|
||||
let castling = [
|
||||
|
@ -103,7 +103,7 @@ impl ToFenStr for Board {
|
|||
(Color::Black, Castle::QueenSide),
|
||||
]
|
||||
.map(|(color, castle)| {
|
||||
if !self.castling_rights.is_set(color, castle) {
|
||||
if !self.castling_rights.color_has_right(color, castle) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -131,10 +131,8 @@ impl ToFenStr for Board {
|
|||
)
|
||||
.map_err(ToFenStrError::FmtError)?;
|
||||
|
||||
write!(fen_string, " {}", self.clock.half_move_number())
|
||||
.map_err(ToFenStrError::FmtError)?;
|
||||
write!(fen_string, " {}", self.clock.full_move_number())
|
||||
.map_err(ToFenStrError::FmtError)?;
|
||||
write!(fen_string, " {}", self.half_move_clock).map_err(ToFenStrError::FmtError)?;
|
||||
write!(fen_string, " {}", self.full_move_number).map_err(ToFenStrError::FmtError)?;
|
||||
|
||||
Ok(fen_string)
|
||||
}
|
||||
|
@ -156,7 +154,7 @@ impl ToFenStr for Piece {
|
|||
|
||||
fn to_fen_str(&self) -> Result<String, Self::Error> {
|
||||
let ascii: char = self.to_ascii();
|
||||
Ok(String::from(match self.color() {
|
||||
Ok(String::from(match self.color {
|
||||
Color::White => ascii.to_ascii_uppercase(),
|
||||
Color::Black => ascii.to_ascii_lowercase(),
|
||||
}))
|
||||
|
@ -214,20 +212,20 @@ impl FromFenStr for Board {
|
|||
.next()
|
||||
.ok_or(FromFenStrError::MissingField(Field::PlayerToMove))?,
|
||||
)?;
|
||||
board.clock.active_color = active_color;
|
||||
board.active_color = active_color;
|
||||
|
||||
let castling_rights = fields
|
||||
.next()
|
||||
.ok_or(FromFenStrError::MissingField(Field::CastlingRights))?;
|
||||
if castling_rights == "-" {
|
||||
board.castling_rights.clear_all();
|
||||
board.castling_rights.revoke_all();
|
||||
} else {
|
||||
for ch in castling_rights.chars() {
|
||||
match ch {
|
||||
'K' => board.castling_rights.set(Color::White, Castle::KingSide),
|
||||
'Q' => board.castling_rights.set(Color::White, Castle::QueenSide),
|
||||
'k' => board.castling_rights.set(Color::Black, Castle::KingSide),
|
||||
'q' => board.castling_rights.set(Color::Black, Castle::QueenSide),
|
||||
'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),
|
||||
_ => return Err(FromFenStrError::InvalidValue),
|
||||
};
|
||||
}
|
||||
|
@ -245,18 +243,18 @@ impl FromFenStr for Board {
|
|||
let half_move_clock = fields
|
||||
.next()
|
||||
.ok_or(FromFenStrError::MissingField(Field::HalfMoveClock))?;
|
||||
let half_move_clock: u16 = half_move_clock
|
||||
let half_move_clock: u32 = half_move_clock
|
||||
.parse()
|
||||
.map_err(FromFenStrError::ParseIntError)?;
|
||||
board.clock.half_move_number = half_move_clock;
|
||||
board.half_move_clock = half_move_clock;
|
||||
|
||||
let full_move_counter = fields
|
||||
.next()
|
||||
.ok_or(FromFenStrError::MissingField(Field::FullMoveCounter))?;
|
||||
let full_move_counter: u16 = full_move_counter
|
||||
let full_move_counter: u32 = full_move_counter
|
||||
.parse()
|
||||
.map_err(FromFenStrError::ParseIntError)?;
|
||||
board.clock.full_move_number = full_move_counter;
|
||||
board.full_move_number = full_move_counter;
|
||||
|
||||
debug_assert_eq!(fields.next(), None);
|
||||
|
||||
|
|
|
@ -7,12 +7,14 @@ pub mod fen;
|
|||
pub mod macros;
|
||||
|
||||
mod board;
|
||||
mod move_counter;
|
||||
mod piece_sets;
|
||||
|
||||
pub use board::Board;
|
||||
pub use move_counter::Clock;
|
||||
|
||||
use castle::Castle;
|
||||
use en_passant::EnPassant;
|
||||
use piece_sets::{PieceSet, PlacePieceError, PlacePieceStrategy};
|
||||
use piece_sets::PieceSet;
|
||||
|
||||
// Used by macros.
|
||||
#[allow(unused_imports)]
|
||||
use piece_sets::{PlacePieceError, PlacePieceStrategy};
|
||||
|
|
|
@ -1,34 +1,19 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! board {
|
||||
[$($color:ident $shape:ident on $square:ident),* $(,)?] => {
|
||||
$crate::Builder::new()
|
||||
$(.place_piece(
|
||||
chessfriend_core::PlacedPiece::new(
|
||||
chessfriend_core::Piece::new(
|
||||
chessfriend_core::Color::$color,
|
||||
chessfriend_core::Shape::$shape),
|
||||
chessfriend_core::Square::$square
|
||||
)
|
||||
))*
|
||||
.build()
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_board {
|
||||
($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ], $en_passant:ident) => {
|
||||
{
|
||||
let mut board = $crate::Board::empty();
|
||||
$(let _ = board.pieces.place(
|
||||
$(let _ = board.place_piece(
|
||||
chessfriend_core::Piece::new(
|
||||
chessfriend_core::Color::$color,
|
||||
chessfriend_core::Shape::$shape
|
||||
),
|
||||
chessfriend_core::Square::$square);
|
||||
chessfriend_core::Square::$square,
|
||||
$crate::PlacePieceStrategy::default());
|
||||
)*
|
||||
board.clock.active_color = chessfriend_core::Color::$to_move;
|
||||
board.active_color = chessfriend_core::Color::$to_move;
|
||||
board.en_passant_target = Some(chessfriend_core::Square::$en_passant);
|
||||
|
||||
println!("{}", board.display());
|
||||
|
@ -39,7 +24,7 @@ macro_rules! test_board {
|
|||
($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ]) => {
|
||||
{
|
||||
let mut board = $crate::Board::empty();
|
||||
$(let _ = board.pieces.place(
|
||||
$(let _ = board.place_piece(
|
||||
chessfriend_core::Piece::new(
|
||||
chessfriend_core::Color::$color,
|
||||
chessfriend_core::Shape::$shape
|
||||
|
@ -47,7 +32,7 @@ macro_rules! test_board {
|
|||
chessfriend_core::Square::$square,
|
||||
$crate::PlacePieceStrategy::default());
|
||||
)*
|
||||
board.clock.active_color = chessfriend_core::Color::$to_move;
|
||||
board.active_color = chessfriend_core::Color::$to_move;
|
||||
|
||||
println!("{}", board.display());
|
||||
|
||||
|
@ -57,7 +42,7 @@ macro_rules! test_board {
|
|||
($($color:ident $shape:ident on $square:ident),* $(,)?) => {
|
||||
{
|
||||
let mut board = $crate::Board::empty();
|
||||
$(let _ = board.pieces.place(
|
||||
$(let _ = board.place_piece(
|
||||
chessfriend_core::Piece::new(
|
||||
chessfriend_core::Color::$color,
|
||||
chessfriend_core::Shape::$shape
|
||||
|
|
|
@ -9,32 +9,14 @@ pub enum AdvanceHalfMove {
|
|||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Clock {
|
||||
/// The player who's turn it is to move.
|
||||
pub(crate) active_color: Color,
|
||||
|
||||
/// The number of completed turns. A turn finishes when every player has moved.
|
||||
pub(crate) full_move_number: u16,
|
||||
pub full_move_number: u16,
|
||||
|
||||
/// The number of moves by all players since the last pawn advance or capture.
|
||||
pub(crate) half_move_number: u16,
|
||||
pub half_move_number: u16,
|
||||
}
|
||||
|
||||
impl Clock {
|
||||
#[must_use]
|
||||
pub fn active_color(&self) -> Color {
|
||||
self.active_color
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn full_move_number(&self) -> u16 {
|
||||
self.full_move_number
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn half_move_number(&self) -> u16 {
|
||||
self.half_move_number
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, advance_half_move: &AdvanceHalfMove) {
|
||||
let next_color = self.active_color.next();
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
mod bitboards;
|
||||
mod mailbox;
|
||||
|
||||
pub(crate) use mailbox::Mailbox;
|
||||
|
||||
use bitboards::{ByColor, ByColorAndShape};
|
||||
use self::mailbox::Mailbox;
|
||||
use chessfriend_bitboard::{BitBoard, IterationDirection};
|
||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
||||
use std::ops::BitOr;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub enum PlacePieceStrategy {
|
||||
#[default]
|
||||
Replace,
|
||||
PreserveExisting,
|
||||
}
|
||||
|
@ -20,73 +19,54 @@ pub enum PlacePieceError {
|
|||
ExisitingPiece(PlacedPiece),
|
||||
}
|
||||
|
||||
impl Default for PlacePieceStrategy {
|
||||
fn default() -> Self {
|
||||
Self::Replace
|
||||
}
|
||||
}
|
||||
|
||||
/// The internal data structure of a [Board] that efficiently manages the
|
||||
/// placement of pieces on the board.
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
|
||||
pub struct PieceSet {
|
||||
mailbox: Mailbox,
|
||||
by_color: ByColor,
|
||||
by_color_and_shape: ByColorAndShape,
|
||||
color_occupancy: [BitBoard; Color::NUM],
|
||||
shape_occupancy: [BitBoard; Shape::NUM],
|
||||
}
|
||||
|
||||
impl PieceSet {
|
||||
pub(crate) fn new(pieces: [[BitBoard; Shape::NUM]; Color::NUM]) -> Self {
|
||||
use std::ops::BitOr;
|
||||
|
||||
let white_pieces = pieces[Color::White as usize]
|
||||
.iter()
|
||||
.fold(BitBoard::empty(), BitOr::bitor);
|
||||
let black_pieces = pieces[Color::Black as usize]
|
||||
.iter()
|
||||
.fold(BitBoard::empty(), BitOr::bitor);
|
||||
|
||||
let all_pieces = white_pieces | black_pieces;
|
||||
|
||||
let mut mailbox = Mailbox::default();
|
||||
for c in Color::into_iter() {
|
||||
for s in Shape::into_iter() {
|
||||
let bitboard = pieces[c as usize][s as usize];
|
||||
let mut color_occupancy: [BitBoard; Color::NUM] = Default::default();
|
||||
let mut shape_occupancy: [BitBoard; Shape::NUM] = Default::default();
|
||||
|
||||
for (color_index, color) in Color::iter().enumerate() {
|
||||
for (shape_index, shape) in Shape::into_iter().enumerate() {
|
||||
let bitboard = pieces[color_index][shape_index];
|
||||
|
||||
color_occupancy[color_index] |= bitboard;
|
||||
shape_occupancy[shape_index] |= bitboard;
|
||||
|
||||
for square in bitboard.occupied_squares(&IterationDirection::default()) {
|
||||
mailbox.set(Piece::new(c, s), square);
|
||||
let piece = Piece::new(*color, shape);
|
||||
mailbox.set(piece, square);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
by_color: ByColor::new(all_pieces, [white_pieces, black_pieces]),
|
||||
by_color_and_shape: ByColorAndShape::new(pieces),
|
||||
mailbox,
|
||||
color_occupancy,
|
||||
shape_occupancy,
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`BitBoard`] representing all the pieces currently on the board. Other
|
||||
/// engines might refer to this concept as 'occupancy'.
|
||||
pub(crate) fn all_pieces(&self) -> BitBoard {
|
||||
self.by_color.all()
|
||||
}
|
||||
|
||||
pub(crate) fn all_pieces_of_color(&self, color: Color) -> BitBoard {
|
||||
self.by_color.bitboard(color)
|
||||
pub fn all_pieces(&self) -> BitBoard {
|
||||
self.color_occupancy
|
||||
.iter()
|
||||
.fold(BitBoard::empty(), BitOr::bitor)
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = PlacedPiece> {
|
||||
self.mailbox.iter()
|
||||
}
|
||||
|
||||
pub(super) fn bitboard_for_color(&self, color: Color) -> BitBoard {
|
||||
self.by_color.bitboard(color)
|
||||
}
|
||||
|
||||
pub(crate) fn bitboard_for_piece(&self, piece: Piece) -> BitBoard {
|
||||
self.by_color_and_shape.bitboard_for_piece(piece)
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, square: Square) -> Option<Piece> {
|
||||
self.mailbox.get(square)
|
||||
}
|
||||
|
@ -96,9 +76,7 @@ impl PieceSet {
|
|||
piece: Piece,
|
||||
square: Square,
|
||||
strategy: PlacePieceStrategy,
|
||||
) -> Result<PlacedPiece, PlacePieceError> {
|
||||
let color = piece.color();
|
||||
|
||||
) -> Result<(), PlacePieceError> {
|
||||
if strategy == PlacePieceStrategy::PreserveExisting {
|
||||
if let Some(existing_piece) = self.mailbox.get(square) {
|
||||
return Err(PlacePieceError::ExisitingPiece(PlacedPiece::new(
|
||||
|
@ -108,35 +86,78 @@ impl PieceSet {
|
|||
}
|
||||
}
|
||||
|
||||
let piece: Piece = piece.into();
|
||||
self.by_color_and_shape.set_square(square, piece);
|
||||
self.by_color.set_square(square, color);
|
||||
let color = piece.color;
|
||||
let shape = piece.shape;
|
||||
|
||||
self.color_occupancy[color as usize].set(square);
|
||||
self.shape_occupancy[shape as usize].set(square);
|
||||
self.mailbox.set(piece, square);
|
||||
|
||||
Ok(PlacedPiece::new(piece, square))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&mut self, square: Square) -> Option<PlacedPiece> {
|
||||
pub(crate) fn remove(&mut self, square: Square) -> Option<Piece> {
|
||||
if let Some(piece) = self.mailbox.get(square) {
|
||||
self.by_color_and_shape.clear_square(square, piece.into());
|
||||
self.by_color.clear_square(square, piece.color());
|
||||
self.color_occupancy[piece.color as usize].clear(square);
|
||||
self.shape_occupancy[piece.shape as usize].clear(square);
|
||||
self.mailbox.remove(square);
|
||||
|
||||
Some(PlacedPiece::new(piece, square))
|
||||
Some(piece)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PieceSet {
|
||||
pub fn color_bitboard(&self, color: Color) -> BitBoard {
|
||||
self.color_occupancy[color as usize]
|
||||
}
|
||||
|
||||
pub fn piece_bitboard(&self, piece: Piece) -> BitBoard {
|
||||
let color_occupancy = self.color_occupancy[piece.color as usize];
|
||||
let shape_occupancy = self.shape_occupancy[piece.shape as usize];
|
||||
color_occupancy & shape_occupancy
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<PlacedPiece> for PieceSet {
|
||||
fn from_iter<T: IntoIterator<Item = PlacedPiece>>(iter: T) -> Self {
|
||||
let mut pieces: Self = Self::default();
|
||||
|
||||
for piece in iter {
|
||||
let _ = pieces.place(piece.piece(), piece.square(), PlacePieceStrategy::default());
|
||||
let _ = pieces.place(piece.piece, piece.square, PlacePieceStrategy::default());
|
||||
}
|
||||
|
||||
pieces
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chessfriend_bitboard::bitboard;
|
||||
|
||||
#[test]
|
||||
fn place_piece() -> Result<(), PlacePieceError> {
|
||||
let mut pieces = PieceSet::default();
|
||||
|
||||
pieces.place(
|
||||
Piece::king(Color::White),
|
||||
Square::F5,
|
||||
PlacePieceStrategy::default(),
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
pieces.mailbox.get(Square::F5),
|
||||
Some(Piece::king(Color::White))
|
||||
);
|
||||
assert_eq!(pieces.color_bitboard(Color::White), bitboard![F5]);
|
||||
assert_eq!(
|
||||
pieces.piece_bitboard(Piece::king(Color::White)),
|
||||
bitboard![F5]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,11 +39,11 @@ impl ByColorAndShape {
|
|||
}
|
||||
|
||||
pub(super) fn bitboard_for_piece(&self, piece: Piece) -> BitBoard {
|
||||
self.0[piece.color() as usize][piece.shape() as usize]
|
||||
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]
|
||||
&mut self.0[piece.color as usize][piece.shape as usize]
|
||||
}
|
||||
|
||||
pub(super) fn set_square(&mut self, square: Square, piece: Piece) {
|
||||
|
|
|
@ -7,23 +7,23 @@ use std::iter::FromIterator;
|
|||
pub(crate) struct Mailbox([Option<Piece>; Square::NUM]);
|
||||
|
||||
impl Mailbox {
|
||||
pub(crate) fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, square: Square) -> Option<Piece> {
|
||||
pub fn get(&self, square: Square) -> Option<Piece> {
|
||||
self.0[square as usize]
|
||||
}
|
||||
|
||||
pub(crate) fn set(&mut self, piece: Piece, square: Square) {
|
||||
pub fn set(&mut self, piece: Piece, square: Square) {
|
||||
self.0[square as usize] = Some(piece);
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&mut self, square: Square) {
|
||||
pub fn remove(&mut self, square: Square) {
|
||||
self.0[square as usize] = None;
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = PlacedPiece> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = PlacedPiece> {
|
||||
self.0
|
||||
.into_iter()
|
||||
.flatten() // Remove the Nones
|
||||
|
@ -50,7 +50,7 @@ impl FromIterator<PlacedPiece> for Mailbox {
|
|||
fn from_iter<T: IntoIterator<Item = PlacedPiece>>(iter: T) -> Self {
|
||||
iter.into_iter()
|
||||
.fold(Self::new(), |mut mailbox, placed_piece| {
|
||||
mailbox.set(placed_piece.piece(), placed_piece.square());
|
||||
mailbox.set(placed_piece.piece(), placed_piece.square);
|
||||
mailbox
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue