[board] A ton of API refinements

This commit is contained in:
Eryn Wells 2025-05-03 16:02:56 -07:00
parent 99dd2d1be2
commit 867deafd13
7 changed files with 208 additions and 244 deletions

View file

@ -1,19 +1,16 @@
// Eryn Wells <eryn@erynwells.me> // Eryn Wells <eryn@erynwells.me>
use crate::{ use crate::{castle, display::DiagramFormatter, Castle, Clock, PieceSet};
castle, display::DiagramFormatter, piece_sets::PlacePieceError, EnPassant, MoveCounter,
PieceSet,
};
use chessfriend_bitboard::BitBoard; use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
use std::iter::Iterator; use std::iter::Iterator;
#[derive(Clone, Debug, Eq)] #[derive(Clone, Debug, Default, Eq)]
pub struct Board { pub struct Board {
pieces: PieceSet, pub clock: Clock,
en_passant: Option<EnPassant>, pub pieces: PieceSet,
pub move_counter: MoveCounter,
pub castling_rights: castle::Rights, pub castling_rights: castle::Rights,
pub en_passant_target: Option<Square>,
} }
impl Board { impl Board {
@ -52,18 +49,7 @@ impl Board {
#[must_use] #[must_use]
pub fn player_to_move(&self) -> Color { pub fn player_to_move(&self) -> Color {
self.move_counter.active_color self.clock.active_color()
}
/// Returns `true` if the player has the right to castle on the given side
/// of the board.
///
/// A player retains the right to castle on a particular side of the board
/// as long as they have not moved their king, or the rook on that side of
/// the board.
#[must_use]
pub fn player_has_right_to_castle(&self, color: Color, castle: Castle) -> bool {
self.flags.player_has_right_to_castle(color, castle)
} }
/// The rook to use for a castling move. /// The rook to use for a castling move.
@ -83,11 +69,9 @@ impl Board {
/// 2. The king must not be in check /// 2. The king must not be in check
/// 3. In the course of castling on that side, the king must not pass /// 3. In the course of castling on that side, the king must not pass
/// through a square that an enemy piece can see /// through a square that an enemy piece can see
#[must_use]
pub fn player_can_castle(&self, player: Color, castle: Castle) -> bool { pub fn player_can_castle(&self, player: Color, castle: Castle) -> bool {
if !self if !self.castling_rights.is_set(player, castle.into()) {
.castling_rights
.player_has_right_to_castle(player, castle.into())
{
return false; return false;
} }
@ -115,12 +99,13 @@ impl Board {
#[must_use] #[must_use]
pub fn friendly_pieces_bitboard(&self) -> BitBoard { pub fn friendly_pieces_bitboard(&self) -> BitBoard {
self.pieces.all_pieces_of_color(self.player_to_move) self.pieces.all_pieces_of_color(self.clock.active_color())
} }
#[must_use] #[must_use]
pub fn opposing_pieces_bitboard(&self) -> BitBoard { pub fn opposing_pieces_bitboard(&self) -> BitBoard {
self.pieces.all_pieces_of_color(self.player_to_move.other()) self.pieces
.all_pieces_of_color(self.clock.active_color().other())
} }
#[must_use] #[must_use]
@ -131,7 +116,15 @@ impl Board {
) )
} }
/// A [BitBoard] representing the set of squares containing a piece. This 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`]. /// set is the inverse of [`Board::occupied_squares`].
#[must_use] #[must_use]
pub fn empty_squares(&self) -> BitBoard { pub fn empty_squares(&self) -> BitBoard {
@ -145,12 +138,10 @@ impl Board {
.map(|piece| PlacedPiece::new(piece, square)) .map(|piece| PlacedPiece::new(piece, square))
} }
#[must_use]
pub fn iter_all_pieces(&self) -> impl Iterator<Item = PlacedPiece> + '_ { pub fn iter_all_pieces(&self) -> impl Iterator<Item = PlacedPiece> + '_ {
self.pieces.iter() self.pieces.iter()
} }
#[must_use]
pub fn iter_pieces_of_color(&self, color: Color) -> impl Iterator<Item = PlacedPiece> + '_ { pub fn iter_pieces_of_color(&self, color: Color) -> impl Iterator<Item = PlacedPiece> + '_ {
self.pieces self.pieces
.iter() .iter()
@ -158,13 +149,8 @@ impl Board {
} }
#[must_use] #[must_use]
pub fn has_en_passant_square(&self) -> bool { pub fn en_passant_target(&self) -> Option<Square> {
self.en_passant.is_some() self.en_passant_target
}
#[must_use]
pub fn en_passant(&self) -> Option<EnPassant> {
self.en_passant
} }
fn king_bitboard(&self, player: Color) -> BitBoard { fn king_bitboard(&self, player: Color) -> BitBoard {
@ -172,10 +158,7 @@ impl Board {
} }
pub(crate) fn king_square(&self, player: Color) -> Square { pub(crate) fn king_square(&self, player: Color) -> Square {
self.king_bitboard(player) self.king_bitboard(player).try_into().unwrap()
.occupied_squares()
.next()
.unwrap()
} }
#[must_use] #[must_use]
@ -192,16 +175,19 @@ impl Board {
pub fn bitboard_for_piece(&self, piece: Piece) -> BitBoard { pub fn bitboard_for_piece(&self, piece: Piece) -> BitBoard {
self.pieces.bitboard_for_piece(piece) self.pieces.bitboard_for_piece(piece)
} }
}
impl Default for Board { /// A [`BitBoard`] representing the squares where a king of the given color will
fn default() -> Self { /// be in danger of being captured by the opposing player. If the king is on
Self { /// one of these squares, it is in check. The king cannot move to these
castling_rights: castle::Rights::default(), /// squares.
pieces: PieceSet::default(), pub(crate) fn king_danger(&self, color: Color) -> BitBoard {
en_passant: None, let board_without_king = {
move_counter: MoveCounter::default(), let mut cloned_board = self.clone();
} cloned_board.pieces.remove(self.king_square(color));
cloned_board
};
BitBoard::full()
} }
} }
@ -209,8 +195,8 @@ impl PartialEq for Board {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.pieces == other.pieces self.pieces == other.pieces
&& self.castling_rights == other.castling_rights && self.castling_rights == other.castling_rights
&& self.en_passant == other.en_passant && self.en_passant_target == other.en_passant_target
&& self.move_counter == other.move_counter && self.clock == other.clock
} }
} }
@ -243,49 +229,4 @@ mod tests {
Some(piece!(Black Rook on A8)) Some(piece!(Black Rook on A8))
); );
} }
#[test]
fn king_not_on_starting_square_cannot_castle() {
let board = test_board!(White King on E4);
assert!(!board
.castling_rights
.player_has_right_to_castle(Color::White, Castle::KingSide));
assert!(!board
.castling_rights
.player_has_right_to_castle(Color::White, Castle::QueenSide));
}
#[test]
fn king_on_starting_square_can_castle() {
let board = test_board!(
White King on E1,
White Rook on A1,
White Rook on H1
);
assert!(board
.castling_rights
.player_has_right_to_castle(Color::White, Castle::KingSide));
assert!(board
.castling_rights
.player_has_right_to_castle(Color::White, Castle::QueenSide));
}
#[test]
fn rook_for_castle() {
let board = test_board![
White King on E1,
White Rook on H1,
White Rook on A1,
];
assert_eq!(
board.rook_for_castle(Color::White, Castle::KingSide),
Some(piece!(White Rook on H1))
);
assert_eq!(
board.rook_for_castle(Color::White, Castle::QueenSide),
Some(piece!(White Rook on A1))
);
}
} }

View file

@ -12,24 +12,25 @@ impl Rights {
/// A player retains the right to castle on a particular side of the board /// A player retains the right to castle on a particular side of the board
/// 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.
pub fn player_has_right_to_castle(self, color: Color, castle: Castle) -> bool { #[must_use]
(self.0 & (1 << Self::_player_has_right_to_castle_flag_offset(color, castle))) != 0 pub fn is_set(self, color: Color, castle: Castle) -> bool {
(self.0 & (1 << Self::flag_offset(color, castle))) != 0
} }
pub fn set_player_has_right_to_castle_flag(&mut self, color: Color, castle: Castle) { pub fn set(&mut self, color: Color, castle: Castle) {
self.0 |= 1 << Self::_player_has_right_to_castle_flag_offset(color, castle); self.0 |= 1 << Self::flag_offset(color, castle);
} }
pub fn clear_player_has_right_to_castle_flag(&mut self, color: Color, castle: Castle) { pub fn clear(&mut self, color: Color, castle: Castle) {
self.0 &= !(1 << Self::_player_has_right_to_castle_flag_offset(color, castle)); self.0 &= !(1 << Self::flag_offset(color, castle));
} }
pub fn clear_all(&mut self) { pub fn clear_all(&mut self) {
self.0 &= 0b1111_1100; self.0 = 0;
} }
fn _player_has_right_to_castle_flag_offset(color: Color, castle: Castle) -> usize { fn flag_offset(color: Color, castle: Castle) -> usize {
((color as usize) << 1) & castle as usize ((color as usize) << 1) + castle as usize
} }
} }
@ -50,43 +51,31 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn castling_rights() { fn bitfield_offsets() {
assert_eq!( assert_eq!(Rights::flag_offset(Color::White, Castle::KingSide), 0);
Rights::_player_has_right_to_castle_flag_offset(Color::White, Castle::KingSide), assert_eq!(Rights::flag_offset(Color::White, Castle::QueenSide), 1);
0 assert_eq!(Rights::flag_offset(Color::Black, Castle::KingSide), 2);
); assert_eq!(Rights::flag_offset(Color::Black, Castle::QueenSide), 3);
assert_eq!(
Rights::_player_has_right_to_castle_flag_offset(Color::White, Castle::QueenSide),
1
);
assert_eq!(
Rights::_player_has_right_to_castle_flag_offset(Color::Black, Castle::KingSide),
2
);
assert_eq!(
Rights::_player_has_right_to_castle_flag_offset(Color::Black, Castle::QueenSide),
3
);
} }
#[test] #[test]
fn default_rights() { fn default_rights() {
let mut rights = Rights::default(); let mut rights = Rights::default();
assert!(rights.player_has_right_to_castle(Color::White, Castle::KingSide)); assert!(rights.is_set(Color::White, Castle::KingSide));
assert!(rights.player_has_right_to_castle(Color::White, Castle::QueenSide)); assert!(rights.is_set(Color::White, Castle::QueenSide));
assert!(rights.player_has_right_to_castle(Color::Black, Castle::KingSide)); assert!(rights.is_set(Color::Black, Castle::KingSide));
assert!(rights.player_has_right_to_castle(Color::Black, Castle::QueenSide)); assert!(rights.is_set(Color::Black, Castle::QueenSide));
rights.clear_player_has_right_to_castle_flag(Color::White, Castle::QueenSide); rights.clear(Color::White, Castle::QueenSide);
assert!(rights.player_has_right_to_castle(Color::White, Castle::KingSide)); assert!(rights.is_set(Color::White, Castle::KingSide));
assert!(!rights.player_has_right_to_castle(Color::White, Castle::QueenSide)); assert!(!rights.is_set(Color::White, Castle::QueenSide));
assert!(rights.player_has_right_to_castle(Color::Black, Castle::KingSide)); assert!(rights.is_set(Color::Black, Castle::KingSide));
assert!(rights.player_has_right_to_castle(Color::Black, Castle::QueenSide)); assert!(rights.is_set(Color::Black, Castle::QueenSide));
rights.set_player_has_right_to_castle_flag(Color::White, Castle::QueenSide); rights.set(Color::White, Castle::QueenSide);
assert!(rights.player_has_right_to_castle(Color::White, Castle::KingSide)); assert!(rights.is_set(Color::White, Castle::KingSide));
assert!(rights.player_has_right_to_castle(Color::White, Castle::QueenSide)); assert!(rights.is_set(Color::White, Castle::QueenSide));
assert!(rights.player_has_right_to_castle(Color::Black, Castle::KingSide)); assert!(rights.is_set(Color::Black, Castle::KingSide));
assert!(rights.player_has_right_to_castle(Color::Black, Castle::QueenSide)); assert!(rights.is_set(Color::Black, Castle::QueenSide));
} }
} }

View file

@ -1,6 +1,6 @@
// Eryn Wells <eryn@erynwells.me> // Eryn Wells <eryn@erynwells.me>
use crate::{Board, Builder, Castle, EnPassant}; use crate::{piece_sets::PlacePieceStrategy, Board, Castle, EnPassant};
use chessfriend_core::{ use chessfriend_core::{
coordinates::ParseSquareError, piece, Color, File, Piece, PlacedPiece, Rank, Square, coordinates::ParseSquareError, piece, Color, File, Piece, PlacedPiece, Rank, Square,
}; };
@ -103,10 +103,7 @@ impl ToFenStr for Board {
(Color::Black, Castle::QueenSide), (Color::Black, Castle::QueenSide),
] ]
.map(|(color, castle)| { .map(|(color, castle)| {
if !self if !self.castling_rights.is_set(color, castle) {
.castling_rights
.player_has_right_to_castle(color, castle)
{
return ""; return "";
} }
@ -129,14 +126,14 @@ impl ToFenStr for Board {
write!( write!(
fen_string, fen_string,
" {}", " {}",
self.en_passant() self.en_passant_target
.map_or("-".to_string(), |ep| ep.target_square().to_string()) .map_or("-".to_string(), |square| square.to_string())
) )
.map_err(ToFenStrError::FmtError)?; .map_err(ToFenStrError::FmtError)?;
write!(fen_string, " {}", self.move_counter.halfmove_number) write!(fen_string, " {}", self.clock.half_move_number())
.map_err(ToFenStrError::FmtError)?; .map_err(ToFenStrError::FmtError)?;
write!(fen_string, " {}", self.move_counter.fullmove_number) write!(fen_string, " {}", self.clock.full_move_number())
.map_err(ToFenStrError::FmtError)?; .map_err(ToFenStrError::FmtError)?;
Ok(fen_string) Ok(fen_string)
@ -178,7 +175,7 @@ impl FromFenStr for Board {
type Error = FromFenStrError; type Error = FromFenStrError;
fn from_fen_str(string: &str) -> Result<Self, Self::Error> { fn from_fen_str(string: &str) -> Result<Self, Self::Error> {
let mut builder = Builder::default(); let mut board = Board::empty();
let mut fields = string.split(' '); let mut fields = string.split(' ');
@ -202,34 +199,35 @@ impl FromFenStr for Board {
let file = files.next().ok_or(FromFenStrError::MissingPlacement)?; let file = files.next().ok_or(FromFenStrError::MissingPlacement)?;
let piece = Piece::from_fen_str(&ch.to_string())?; let piece = Piece::from_fen_str(&ch.to_string())?;
builder.place_piece(PlacedPiece::new( let _ = board.pieces.place(
piece, piece,
Square::from_file_rank(*file, *rank), Square::from_file_rank(*file, *rank),
)); PlacePieceStrategy::default(),
);
} }
debug_assert_eq!(files.next(), None); debug_assert_eq!(files.next(), None);
} }
let player_to_move = Color::from_fen_str( let active_color = Color::from_fen_str(
fields fields
.next() .next()
.ok_or(FromFenStrError::MissingField(Field::PlayerToMove))?, .ok_or(FromFenStrError::MissingField(Field::PlayerToMove))?,
)?; )?;
builder.to_move(player_to_move); board.clock.active_color = active_color;
let castling_rights = fields let castling_rights = fields
.next() .next()
.ok_or(FromFenStrError::MissingField(Field::CastlingRights))?; .ok_or(FromFenStrError::MissingField(Field::CastlingRights))?;
if castling_rights == "-" { if castling_rights == "-" {
builder.no_castling_rights(); board.castling_rights.clear_all();
} else { } else {
for ch in castling_rights.chars() { for ch in castling_rights.chars() {
match ch { match ch {
'K' => builder.player_can_castle(Color::White, Castle::KingSide), 'K' => board.castling_rights.set(Color::White, Castle::KingSide),
'Q' => builder.player_can_castle(Color::White, Castle::QueenSide), 'Q' => board.castling_rights.set(Color::White, Castle::QueenSide),
'k' => builder.player_can_castle(Color::Black, Castle::KingSide), 'k' => board.castling_rights.set(Color::Black, Castle::KingSide),
'q' => builder.player_can_castle(Color::Black, Castle::QueenSide), 'q' => board.castling_rights.set(Color::Black, Castle::QueenSide),
_ => return Err(FromFenStrError::InvalidValue), _ => return Err(FromFenStrError::InvalidValue),
}; };
} }
@ -241,7 +239,7 @@ impl FromFenStr for Board {
if en_passant_square != "-" { if en_passant_square != "-" {
let square = Square::from_algebraic_str(en_passant_square) let square = Square::from_algebraic_str(en_passant_square)
.map_err(FromFenStrError::ParseSquareError)?; .map_err(FromFenStrError::ParseSquareError)?;
builder.en_passant(Some(EnPassant::from_target_square(square).unwrap())); board.en_passant_target = Some(square);
} }
let half_move_clock = fields let half_move_clock = fields
@ -250,7 +248,7 @@ impl FromFenStr for Board {
let half_move_clock: u16 = half_move_clock let half_move_clock: u16 = half_move_clock
.parse() .parse()
.map_err(FromFenStrError::ParseIntError)?; .map_err(FromFenStrError::ParseIntError)?;
builder.halfmove_number(half_move_clock); board.clock.half_move_number = half_move_clock;
let full_move_counter = fields let full_move_counter = fields
.next() .next()
@ -258,11 +256,11 @@ impl FromFenStr for Board {
let full_move_counter: u16 = full_move_counter let full_move_counter: u16 = full_move_counter
.parse() .parse()
.map_err(FromFenStrError::ParseIntError)?; .map_err(FromFenStrError::ParseIntError)?;
builder.fullmove_number(full_move_counter); board.clock.full_move_number = full_move_counter;
debug_assert_eq!(fields.next(), None); debug_assert_eq!(fields.next(), None);
Ok(builder.build()) Ok(board)
} }
} }
@ -318,16 +316,14 @@ mod tests {
let pos = test_board!(starting); let pos = test_board!(starting);
assert_eq!( assert_eq!(
pos.to_fen_str(), pos.to_fen_str().unwrap(),
Ok(String::from( "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 0"
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
))
); );
} }
#[test] #[test]
fn from_starting_fen() { fn from_starting_fen() {
let board = fen!("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap(); let board = fen!("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 0").unwrap();
let expected = Board::starting(); let expected = Board::starting();
assert_eq!(board, expected, "{board:#?}\n{expected:#?}"); assert_eq!(board, expected, "{board:#?}\n{expected:#?}");
} }

View file

@ -5,14 +5,14 @@ pub mod display;
pub mod en_passant; pub mod en_passant;
pub mod fen; pub mod fen;
pub mod macros; pub mod macros;
pub mod move_counter;
mod board; mod board;
mod move_counter;
mod piece_sets; mod piece_sets;
pub use board::Board; pub use board::Board;
pub use move_counter::Clock;
use castle::Castle; use castle::Castle;
use en_passant::EnPassant; use en_passant::EnPassant;
use move_counter::MoveCounter; use piece_sets::{PieceSet, PlacePieceError, PlacePieceStrategy};
use piece_sets::PieceSet;

View file

@ -16,24 +16,20 @@ macro_rules! board {
}; };
} }
#[cfg(test)]
#[macro_export] #[macro_export]
macro_rules! test_board { macro_rules! test_board {
($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ], $en_passant:ident) => { ($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ], $en_passant:ident) => {
{ {
let board = $crate::Builder::new() let mut board = $crate::Board::empty();
$(.place_piece( $(let _ = board.pieces.place(
chessfriend_core::PlacedPiece::new( chessfriend_core::Piece::new(
chessfriend_core::Piece::new( chessfriend_core::Color::$color,
chessfriend_core::Color::$color, chessfriend_core::Shape::$shape
chessfriend_core::Shape::$shape ),
), chessfriend_core::Square::$square);
chessfriend_core::Square::$square )*
)) board.clock.active_color = chessfriend_core::Color::$to_move;
)* board.en_passant_target = Some(chessfriend_core::Square::$en_passant);
.to_move(chessfriend_core::Color::$to_move)
.en_passant(Some(chessfriend_moves::EnPassant::from_target_square(chessfriend_core::Square::$en_passant)).unwrap())
.build();
println!("{}", board.display()); println!("{}", board.display());
@ -42,37 +38,33 @@ macro_rules! test_board {
}; };
($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ]) => { ($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ]) => {
{ {
let board = $crate::Builder::new() let mut board = $crate::Board::empty();
$(.place_piece( $(let _ = board.pieces.place(
chessfriend_core::PlacedPiece::new( chessfriend_core::Piece::new(
chessfriend_core::Piece::new( chessfriend_core::Color::$color,
chessfriend_core::Color::$color, chessfriend_core::Shape::$shape
chessfriend_core::Shape::$shape ),
), chessfriend_core::Square::$square,
chessfriend_core::Square::$square $crate::PlacePieceStrategy::default());
)) )*
)* board.clock.active_color = chessfriend_core::Color::$to_move;
.to_move(chessfriend_core::Color::$to_move)
.build();
println!("{}", board.display()); println!("{}", board.display());
pos board
} }
}; };
($($color:ident $shape:ident on $square:ident),* $(,)?) => { ($($color:ident $shape:ident on $square:ident),* $(,)?) => {
{ {
let board = $crate::Builder::new() let mut board = $crate::Board::empty();
$(.place_piece( $(let _ = board.pieces.place(
chessfriend_core::PlacedPiece::new( chessfriend_core::Piece::new(
chessfriend_core::Piece::new( chessfriend_core::Color::$color,
chessfriend_core::Color::$color, chessfriend_core::Shape::$shape
chessfriend_core::Shape::$shape ),
), chessfriend_core::Square::$square,
chessfriend_core::Square::$square $crate::PlacePieceStrategy::default());
)) )*
)*
.build();
println!("{}", board.display()); println!("{}", board.display());

View file

@ -1,34 +1,92 @@
use chessfriend_core::Color; use chessfriend_core::Color;
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Default)]
pub struct MoveCounter { pub enum AdvanceHalfMove {
Reset,
#[default]
Advance,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Clock {
/// The player who's turn it is to move. /// The player who's turn it is to move.
pub active_color: Color, pub(crate) active_color: Color,
/// The number of completed turns. A turn finishes when every player has moved. /// The number of completed turns. A turn finishes when every player has moved.
pub fullmove_number: u16, pub(crate) full_move_number: u16,
/// The number of moves by all players since the last pawn advance or capture. /// The number of moves by all players since the last pawn advance or capture.
pub halfmove_number: u16, pub(crate) half_move_number: u16,
} }
impl MoveCounter { impl Clock {
pub fn advance(&mut self, should_reset_halfmove_number: bool) { #[must_use]
self.active_color = self.active_color.next(); pub fn active_color(&self) -> Color {
self.active_color
self.fullmove_number += 1;
self.halfmove_number = if should_reset_halfmove_number {
0
} else {
self.halfmove_number + 1
};
} }
}
impl Default for MoveCounter { #[must_use]
fn default() -> Self { pub fn full_move_number(&self) -> u16 {
Self { self.full_move_number
active_color: Color::default(), }
fullmove_number: 0,
halfmove_number: 0, #[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();
match self.active_color {
Color::Black => self.full_move_number += 1,
Color::White => {}
} }
self.half_move_number = match advance_half_move {
AdvanceHalfMove::Reset => 0,
AdvanceHalfMove::Advance => self.half_move_number + 1,
};
self.active_color = next_color;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_state() {
let clock = Clock::default();
assert_eq!(clock.active_color, Color::White);
assert_eq!(clock.half_move_number, 0);
assert_eq!(clock.full_move_number, 0);
}
#[test]
fn advance() {
let mut clock = Clock::default();
clock.advance(&AdvanceHalfMove::default());
assert_eq!(clock.active_color, Color::Black);
assert_eq!(clock.half_move_number, 1);
assert_eq!(clock.full_move_number, 0);
clock.advance(&AdvanceHalfMove::default());
assert_eq!(clock.active_color, Color::White);
assert_eq!(clock.half_move_number, 2);
assert_eq!(clock.full_move_number, 1);
clock.advance(&AdvanceHalfMove::default());
assert_eq!(clock.active_color, Color::Black);
assert_eq!(clock.half_move_number, 3);
assert_eq!(clock.full_move_number, 1);
// The half move clock resets after a capture or pawn push.
clock.advance(&AdvanceHalfMove::Reset);
assert_eq!(clock.active_color, Color::White);
assert_eq!(clock.half_move_number, 0);
assert_eq!(clock.full_move_number, 2);
} }
} }

View file

@ -29,7 +29,7 @@ impl Default for PlacePieceStrategy {
/// The internal data structure of a [Board] that efficiently manages the /// The internal data structure of a [Board] that efficiently manages the
/// placement of pieces on the board. /// placement of pieces on the board.
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub(crate) struct PieceSet { pub struct PieceSet {
mailbox: Mailbox, mailbox: Mailbox,
by_color: ByColor, by_color: ByColor,
by_color_and_shape: ByColorAndShape, by_color_and_shape: ByColorAndShape,
@ -52,7 +52,7 @@ impl PieceSet {
for c in Color::into_iter() { for c in Color::into_iter() {
for s in Shape::into_iter() { for s in Shape::into_iter() {
let bitboard = pieces[c as usize][s as usize]; let bitboard = pieces[c as usize][s as usize];
for square in bitboard.occupied_squares(IterationDirection::default()) { for square in bitboard.occupied_squares(&IterationDirection::default()) {
mailbox.set(Piece::new(c, s), square); mailbox.set(Piece::new(c, s), square);
} }
} }
@ -65,10 +65,6 @@ impl PieceSet {
} }
} }
pub(crate) fn mailbox(&self) -> &Mailbox {
&self.mailbox
}
/// A [`BitBoard`] representing all the pieces currently on the board. Other /// A [`BitBoard`] representing all the pieces currently on the board. Other
/// engines might refer to this concept as 'occupancy'. /// engines might refer to this concept as 'occupancy'.
pub(crate) fn all_pieces(&self) -> BitBoard { pub(crate) fn all_pieces(&self) -> BitBoard {
@ -95,15 +91,7 @@ impl PieceSet {
self.mailbox.get(square) self.mailbox.get(square)
} }
pub(crate) fn place_piece_on_square( pub(crate) fn place(
&mut self,
piece: Piece,
square: Square,
) -> Result<PlacedPiece, PlacePieceError> {
self.place_piece_on_square_with_strategy(piece, square, PlacePieceStrategy::default())
}
pub(crate) fn place_piece_on_square_with_strategy(
&mut self, &mut self,
piece: Piece, piece: Piece,
square: Square, square: Square,
@ -128,7 +116,7 @@ impl PieceSet {
Ok(PlacedPiece::new(piece, square)) Ok(PlacedPiece::new(piece, square))
} }
pub(crate) fn remove_piece_from_square(&mut self, square: Square) -> Option<PlacedPiece> { pub(crate) fn remove(&mut self, square: Square) -> Option<PlacedPiece> {
if let Some(piece) = self.mailbox.get(square) { if let Some(piece) = self.mailbox.get(square) {
self.by_color_and_shape.clear_square(square, piece.into()); self.by_color_and_shape.clear_square(square, piece.into());
self.by_color.clear_square(square, piece.color()); self.by_color.clear_square(square, piece.color());
@ -146,7 +134,7 @@ impl FromIterator<PlacedPiece> for PieceSet {
let mut pieces: Self = Self::default(); let mut pieces: Self = Self::default();
for piece in iter { for piece in iter {
let _ = pieces.place_piece_on_square(piece.piece(), piece.square()); let _ = pieces.place(piece.piece(), piece.square(), PlacePieceStrategy::default());
} }
pieces pieces