[board] A ton of API refinements
This commit is contained in:
parent
99dd2d1be2
commit
867deafd13
7 changed files with 208 additions and 244 deletions
|
@ -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))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:#?}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue