diff --git a/src/board/chess_board/builder.rs b/src/board/chess_board/builder.rs index 8221d92..d16c881 100644 --- a/src/board/chess_board/builder.rs +++ b/src/board/chess_board/builder.rs @@ -8,9 +8,10 @@ pub struct ChessBoardBuilder { // Same fields as [ChessBoard]. castle_rights: [CastleRights; Color::NUM_VARIANTS], en_passant: Option, - 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 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 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 diff --git a/src/board/chess_board/error.rs b/src/board/chess_board/error.rs index 4265de0..7b570a4 100644 --- a/src/board/chess_board/error.rs +++ b/src/board/chess_board/error.rs @@ -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) } diff --git a/src/board/chess_board/mod.rs b/src/board/chess_board/mod.rs index 879aba6..9ceb673 100644 --- a/src/board/chess_board/mod.rs +++ b/src/board/chess_board/mod.rs @@ -24,7 +24,7 @@ pub struct ChessBoard { /// `Some(target_square)` if a double-step move was made. en_passant: Option, /// 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, - 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::::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 = { diff --git a/src/fen.rs b/src/fen.rs index 3096c95..90f6dd1 100644 --- a/src/fen.rs +++ b/src/fen.rs @@ -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::::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::() + .parse::<_>() .map_err(|_| FenError::InvalidFen)?; builder.with_half_move_clock(half_move_clock); let full_move_counter = full_move_counter - .parse::() + .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.