Compare commits

...

4 commits

Author SHA1 Message Date
Bruno BELANYI a4aa4ae1e4 Make 'half_move_clock' a 'u32'
All checks were successful
ci/woodpecker/push/check Pipeline was successful
It *could* be set to a high value due to e.g: starting the engine in the
middle of a game.

Moving from a `u8` to a `u32` does not change the size of the type, so
let's just do that.

Use that opportunity to fix the comment about the number of
*half-moves* (it's 50 moves *per player*).
2024-04-01 23:14:11 +01:00
Bruno BELANYI 353271f427 Simplify 'FromFen' for 'ChessBoard' 2024-04-01 23:11:43 +01:00
Bruno BELANYI 08f010ed32 Add total plie count validation 2024-04-01 22:59:06 +01:00
Bruno BELANYI f4764f2174 Use turn counts in 'ChessBoardBuilder'
This makes more sense from a user's perspective.
2024-04-01 22:48:55 +01:00
4 changed files with 45 additions and 21 deletions

View file

@ -8,9 +8,10 @@ pub struct ChessBoardBuilder {
// Same fields as [ChessBoard].
castle_rights: [CastleRights; Color::NUM_VARIANTS],
en_passant: Option<Square>,
half_move_clock: u8,
total_plies: u32,
half_move_clock: u32,
side: Color,
// 1-based, a turn is *two* half-moves (i.e: both players have played).
turn_count: u32,
}
impl ChessBoardBuilder {
@ -20,8 +21,8 @@ impl ChessBoardBuilder {
castle_rights: [CastleRights::NoSide; 2],
en_passant: Default::default(),
half_move_clock: Default::default(),
total_plies: Default::default(),
side: Color::White,
turn_count: 1,
}
}
@ -40,13 +41,13 @@ impl ChessBoardBuilder {
self
}
pub fn with_half_move_clock(&mut self, clock: u8) -> &mut Self {
pub fn with_half_move_clock(&mut self, clock: u32) -> &mut Self {
self.half_move_clock = clock;
self
}
pub fn with_total_plies(&mut self, plies: u32) -> &mut Self {
self.total_plies = plies;
pub fn with_turn_count(&mut self, count: u32) -> &mut Self {
self.turn_count = count;
self
}
@ -90,8 +91,8 @@ impl TryFrom<ChessBoardBuilder> for ChessBoard {
castle_rights,
en_passant,
half_move_clock,
total_plies,
side,
turn_count,
} = builder;
for square in Square::iter() {
@ -103,6 +104,8 @@ impl TryFrom<ChessBoardBuilder> for ChessBoard {
combined_occupancy |= square;
}
let total_plies = (turn_count - 1) * 2 + if side == Color::White { 0 } else { 1 };
let board = ChessBoard {
piece_occupancy,
color_occupancy,
@ -146,7 +149,7 @@ mod test {
builder
.with_half_move_clock(board.half_move_clock())
.with_total_plies(board.total_plies())
.with_turn_count(board.total_plies() / 2 + 1)
.with_current_player(board.current_player());
builder

View file

@ -23,6 +23,8 @@ pub enum InvalidError {
ErroneousCombinedOccupancy,
/// Half-move clock is higher than total number of plies.
HalfMoveClockTooHigh,
/// The total plie count does not match the current player.
IncoherentPlieCount,
}
impl std::fmt::Display for InvalidError {
@ -45,6 +47,7 @@ impl std::fmt::Display for InvalidError {
"The pre-computed combined occupancy boards does not match the other boards."
}
Self::HalfMoveClockTooHigh => "Half-move clock is higher than total number of plies.",
Self::IncoherentPlieCount => "The total plie count does not match the current player.",
};
write!(f, "{}", error_msg)
}

View file

@ -24,7 +24,7 @@ pub struct ChessBoard {
/// `Some(target_square)` if a double-step move was made.
en_passant: Option<Square>,
/// The number of half-turns without either a pawn push or capture.
half_move_clock: u8, // Should never go higher than 50.
half_move_clock: u32, // Should *probably* never go higher than 100.
/// The number of half-turns so far.
total_plies: u32, // Should be plenty.
/// The current player turn.
@ -36,7 +36,7 @@ pub struct ChessBoard {
pub struct NonReversibleState {
castle_rights: [CastleRights; Color::NUM_VARIANTS],
en_passant: Option<Square>,
half_move_clock: u8, // Should never go higher than 50.
half_move_clock: u32, // Should *probably* never go higher than 100.
}
impl ChessBoard {
@ -105,7 +105,7 @@ impl ChessBoard {
/// Return the number of half-turns without either a pawn push or a capture.
#[inline(always)]
pub fn half_move_clock(&self) -> u8 {
pub fn half_move_clock(&self) -> u32 {
self.half_move_clock
}
@ -208,8 +208,13 @@ impl ChessBoard {
/// Validate the state of the board. Return Err([InvalidError]) if an issue is found.
pub fn validate(&self) -> Result<(), InvalidError> {
// The current plie count should be odd on white's turn, and vice-versa.
if self.total_plies() % 2 != self.current_player().index() as u32 {
return Err(InvalidError::IncoherentPlieCount);
}
// Make sure the clocks are in agreement.
if u32::from(self.half_move_clock()) > self.total_plies() {
if self.half_move_clock() > self.total_plies() {
return Err(InvalidError::HalfMoveClockTooHigh);
}
@ -428,6 +433,22 @@ mod test {
assert!(default_position.is_valid());
}
#[test]
fn invalid_incoherent_plie_count() {
let position = {
let mut builder = ChessBoardBuilder::new();
builder[Square::E1] = Some((Piece::King, Color::White));
builder[Square::E8] = Some((Piece::King, Color::Black));
let mut board = TryInto::<ChessBoard>::try_into(builder).unwrap();
board.total_plies = 1;
board
};
assert_eq!(
position.validate().err().unwrap(),
InvalidError::IncoherentPlieCount,
);
}
#[test]
fn invalid_half_moves_clock() {
let res = {

View file

@ -139,24 +139,21 @@ impl FromFen for ChessBoard {
builder.with_castle_rights(castle_rights[color.index()], color);
}
let side = Color::from_fen(side_to_move)?;
builder.with_current_player(side);
builder.with_current_player(FromFen::from_fen(side_to_move)?);
if let Some(square) = Option::<Square>::from_fen(en_passant_square)? {
if let Some(square) = FromFen::from_fen(en_passant_square)? {
builder.with_en_passant(square);
};
let half_move_clock = half_move_clock
.parse::<u8>()
.parse::<_>()
.map_err(|_| FenError::InvalidFen)?;
builder.with_half_move_clock(half_move_clock);
let full_move_counter = full_move_counter
.parse::<u32>()
.parse::<_>()
.map_err(|_| FenError::InvalidFen)?;
builder.with_total_plies(
(full_move_counter - 1) * 2 + if side == Color::White { 0 } else { 1 },
);
builder.with_turn_count(full_move_counter);
{
let mut rank: usize = 8;
@ -175,7 +172,7 @@ impl FromFen for ChessBoard {
file += digit.to_digit(10).unwrap() as usize;
continue;
}
_ => Piece::from_fen(&c.to_string())?,
_ => FromFen::from_fen(&c.to_string())?,
};
// Only need to worry about underflow since those are `usize` values.