Compare commits

...

8 commits

Author SHA1 Message Date
Bruno BELANYI cb06fc10c8 Fix broken link in documentation
All checks were successful
ci/woodpecker/push/check Pipeline was successful
2024-04-03 21:26:18 +01:00
Bruno BELANYI b0e9e3cbcc Add explicit 'rustfmt' configuration 2024-04-03 21:26:18 +01:00
Bruno BELANYI d74605ba5c Use 'NUM_VARIANTS' where appropriate 2024-04-03 21:26:18 +01:00
Bruno BELANYI 8e688a0cac Add 'try_from_index' implementations 2024-04-03 21:26:18 +01:00
Bruno BELANYI 753f1590d1 Add 'Panics' section to 'from_index' methods 2024-04-03 21:26:18 +01:00
Bruno BELANYI 7d9c5edb99 Rename 'ValidationError'
This is a better, clearer name.
2024-04-03 21:26:18 +01:00
Bruno BELANYI 6f161d067d Use 'ChessBoardBuilder' in more validation tests 2024-04-03 20:07:50 +01:00
Bruno BELANYI 388c26f4ac Use 'writeln' in magic seed generation 2024-04-03 20:01:00 +01:00
14 changed files with 212 additions and 187 deletions

0
rustfmt.toml Normal file
View file

View file

@ -1,4 +1,4 @@
use super::Square;
use super::{File, Rank, Square};
use crate::utils::static_assert;
mod error;
@ -21,7 +21,7 @@ impl Bitboard {
pub const ALL: Bitboard = Bitboard(u64::MAX);
/// Array of bitboards representing the eight ranks, in order from rank 1 to rank 8.
pub const RANKS: [Self; 8] = [
pub const RANKS: [Self; Rank::NUM_VARIANTS] = [
Bitboard(0b00000001_00000001_00000001_00000001_00000001_00000001_00000001_00000001),
Bitboard(0b00000010_00000010_00000010_00000010_00000010_00000010_00000010_00000010),
Bitboard(0b00000100_00000100_00000100_00000100_00000100_00000100_00000100_00000100),
@ -33,7 +33,7 @@ impl Bitboard {
];
/// Array of bitboards representing the eight files, in order from file A to file H.
pub const FILES: [Self; 8] = [
pub const FILES: [Self; File::NUM_VARIANTS] = [
Bitboard(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_11111111),
Bitboard(0b00000000_00000000_00000000_00000000_00000000_00000000_11111111_00000000),
Bitboard(0b00000000_00000000_00000000_00000000_00000000_11111111_00000000_00000000),

View file

@ -18,11 +18,24 @@ impl CastleRights {
pub const NUM_VARIANTS: usize = 4;
/// Convert from a castle rights index into a [CastleRights] type.
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[inline(always)]
pub fn from_index(index: usize) -> Self {
assert!(index < Self::NUM_VARIANTS);
// SAFETY: we know the value is in-bounds
unsafe { Self::from_index_unchecked(index) }
Self::try_from_index(index).expect("index out of bouds")
}
/// Convert from a castle rights index into a [CastleRights] type. Returns [None] if the index
/// is out of bounds.
pub fn try_from_index(index: usize) -> Option<Self> {
if index < Self::NUM_VARIANTS {
// SAFETY: we know the value is in-bounds
Some(unsafe { Self::from_index_unchecked(index) })
} else {
None
}
}
/// Convert from a castle rights index into a [CastleRights] type, no bounds checking.

View file

@ -1,10 +1,10 @@
use crate::board::{Bitboard, CastleRights, ChessBoard, Color, InvalidError, Piece, Square};
use crate::board::{Bitboard, CastleRights, ChessBoard, Color, Piece, Square, ValidationError};
/// Build a [ChessBoard] one piece at a time.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ChessBoardBuilder {
/// The list of [Piece] on the board. Indexed by [Square::index].
pieces: [Option<(Piece, Color)>; 64],
pieces: [Option<(Piece, Color)>; Square::NUM_VARIANTS],
// Same fields as [ChessBoard].
castle_rights: [CastleRights; Color::NUM_VARIANTS],
en_passant: Option<Square>,
@ -17,8 +17,8 @@ pub struct ChessBoardBuilder {
impl ChessBoardBuilder {
pub fn new() -> Self {
Self {
pieces: [None; 64],
castle_rights: [CastleRights::NoSide; 2],
pieces: [None; Square::NUM_VARIANTS],
castle_rights: [CastleRights::NoSide; Color::NUM_VARIANTS],
en_passant: Default::default(),
half_move_clock: Default::default(),
side: Color::White,
@ -80,7 +80,7 @@ impl std::ops::IndexMut<Square> for ChessBoardBuilder {
}
impl TryFrom<ChessBoardBuilder> for ChessBoard {
type Error = InvalidError;
type Error = ValidationError;
fn try_from(builder: ChessBoardBuilder) -> Result<Self, Self::Error> {
let mut piece_occupancy: [Bitboard; Piece::NUM_VARIANTS] = Default::default();

View file

@ -1,6 +1,6 @@
/// A singular type for all errors that could happen during [ChessBoard::is_valid].
/// A singular type for all errors that could happen during [crate::board::ChessBoard::is_valid].
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum InvalidError {
pub enum ValidationError {
/// Too many pieces.
TooManyPieces,
/// Missing king.
@ -27,7 +27,7 @@ pub enum InvalidError {
IncoherentPlieCount,
}
impl std::fmt::Display for InvalidError {
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let error_msg = match self {
Self::TooManyPieces => "Too many pieces.",
@ -53,4 +53,4 @@ impl std::fmt::Display for InvalidError {
}
}
impl std::error::Error for InvalidError {}
impl std::error::Error for ValidationError {}

View file

@ -206,16 +206,16 @@ impl ChessBoard {
self.validate().is_ok()
}
/// Validate the state of the board. Return Err([InvalidError]) if an issue is found.
pub fn validate(&self) -> Result<(), InvalidError> {
/// Validate the state of the board. Return Err([ValidationError]) if an issue is found.
pub fn validate(&self) -> Result<(), ValidationError> {
// 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);
return Err(ValidationError::IncoherentPlieCount);
}
// Make sure the clocks are in agreement.
if self.half_move_clock() > self.total_plies() {
return Err(InvalidError::HalfMoveClockTooHigh);
return Err(ValidationError::HalfMoveClockTooHigh);
}
// Don't overlap pieces.
@ -224,7 +224,7 @@ impl ChessBoard {
for other in Piece::iter() {
if piece != other {
if !(self.piece_occupancy(piece) & self.piece_occupancy(other)).is_empty() {
return Err(InvalidError::OverlappingPieces);
return Err(ValidationError::OverlappingPieces);
}
}
}
@ -232,7 +232,7 @@ impl ChessBoard {
// Don't overlap colors.
if !(self.color_occupancy(Color::White) & self.color_occupancy(Color::Black)).is_empty() {
return Err(InvalidError::OverlappingColors);
return Err(ValidationError::OverlappingColors);
}
// Calculate the union of all pieces.
@ -241,12 +241,12 @@ impl ChessBoard {
// Ensure that the pre-computed version is accurate.
if combined != self.combined_occupancy() {
return Err(InvalidError::ErroneousCombinedOccupancy);
return Err(ValidationError::ErroneousCombinedOccupancy);
}
// Ensure that all pieces belong to a color, and no color has pieces that don't exist.
if combined != (self.color_occupancy(Color::White) | self.color_occupancy(Color::Black)) {
return Err(InvalidError::ErroneousCombinedOccupancy);
return Err(ValidationError::ErroneousCombinedOccupancy);
}
for color in Color::iter() {
@ -260,18 +260,18 @@ impl ChessBoard {
_ => count <= 10,
};
if !possible {
return Err(InvalidError::TooManyPieces);
return Err(ValidationError::TooManyPieces);
}
}
// Check that we have a king
if self.occupancy(Piece::King, color).count() != 1 {
return Err(InvalidError::MissingKing);
return Err(ValidationError::MissingKing);
}
// Check that don't have too many pieces in total
if self.color_occupancy(color).count() > 16 {
return Err(InvalidError::TooManyPieces);
return Err(ValidationError::TooManyPieces);
}
}
@ -280,7 +280,7 @@ impl ChessBoard {
& (Rank::First.into_bitboard() | Rank::Eighth.into_bitboard()))
.is_empty()
{
return Err(InvalidError::InvalidPawnPosition);
return Err(ValidationError::InvalidPawnPosition);
}
// Verify that rooks and kings that are allowed to castle have not been moved.
@ -296,14 +296,14 @@ impl ChessBoard {
let expected_rooks = castle_rights.unmoved_rooks(color);
// We must check the intersection, in case there are more than 2 rooks on the board.
if (expected_rooks & actual_rooks) != expected_rooks {
return Err(InvalidError::InvalidCastlingRights);
return Err(ValidationError::InvalidCastlingRights);
}
let actual_king = self.occupancy(Piece::King, color);
let expected_king = Square::new(File::E, color.first_rank());
// We have checked that there is exactly one king, no need for intersecting the sets.
if actual_king != expected_king.into_bitboard() {
return Err(InvalidError::InvalidCastlingRights);
return Err(ValidationError::InvalidCastlingRights);
}
}
@ -311,14 +311,14 @@ impl ChessBoard {
if let Some(square) = self.en_passant() {
// Must be empty
if !(self.combined_occupancy() & square).is_empty() {
return Err(InvalidError::InvalidEnPassant);
return Err(ValidationError::InvalidEnPassant);
}
let opponent = !self.current_player();
// Must be on the opponent's third rank
if (square & opponent.third_rank().into_bitboard()).is_empty() {
return Err(InvalidError::InvalidEnPassant);
return Err(ValidationError::InvalidEnPassant);
}
// Must be behind a pawn
@ -328,7 +328,7 @@ impl ChessBoard {
.backward_direction()
.move_board(square.into_bitboard());
if (opponent_pawns & double_pushed_pawn).is_empty() {
return Err(InvalidError::InvalidEnPassant);
return Err(ValidationError::InvalidEnPassant);
}
}
@ -337,12 +337,12 @@ impl ChessBoard {
let black_king = self.occupancy(Piece::King, Color::Black);
// Unwrap is fine, we already checked that there is exactly one king of each color
if !(movegen::king_moves(white_king.try_into().unwrap()) & black_king).is_empty() {
return Err(InvalidError::NeighbouringKings);
return Err(ValidationError::NeighbouringKings);
}
// Check that the opponent is not currently in check.
if !self.compute_checkers(!self.current_player()).is_empty() {
return Err(InvalidError::OpponentInCheck);
return Err(ValidationError::OpponentInCheck);
}
Ok(())
@ -411,7 +411,7 @@ impl Default for ChessBoard {
| Rank::Second.into_bitboard()
| Rank::Seventh.into_bitboard()
| Rank::Eighth.into_bitboard(),
castle_rights: [CastleRights::BothSides; 2],
castle_rights: [CastleRights::BothSides; Color::NUM_VARIANTS],
en_passant: None,
half_move_clock: 0,
total_plies: 0,
@ -445,7 +445,7 @@ mod test {
};
assert_eq!(
position.validate().err().unwrap(),
InvalidError::IncoherentPlieCount,
ValidationError::IncoherentPlieCount,
);
}
@ -458,130 +458,70 @@ mod test {
builder.with_half_move_clock(10);
TryInto::<ChessBoard>::try_into(builder)
};
assert_eq!(res.err().unwrap(), InvalidError::HalfMoveClockTooHigh);
assert_eq!(res.err().unwrap(), ValidationError::HalfMoveClockTooHigh);
}
#[test]
fn invalid_overlapping_pieces() {
let position = ChessBoard {
piece_occupancy: [
// King
Square::E1 | Square::E8,
// Queen
Square::E1 | Square::E8,
// Rook
Bitboard::EMPTY,
// Bishop
Bitboard::EMPTY,
// Knight
Bitboard::EMPTY,
// Pawn
Bitboard::EMPTY,
],
color_occupancy: [Square::E1.into_bitboard(), Square::E8.into_bitboard()],
combined_occupancy: Square::E1 | Square::E8,
castle_rights: [CastleRights::NoSide; 2],
en_passant: None,
half_move_clock: 0,
total_plies: 0,
side: Color::White,
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: ChessBoard = builder.try_into().unwrap();
*board.piece_occupancy_mut(Piece::Queen) |= Square::E1.into_bitboard();
board
};
assert_eq!(
position.validate().err().unwrap(),
InvalidError::OverlappingPieces,
ValidationError::OverlappingPieces,
);
}
#[test]
fn invalid_overlapping_colors() {
let position = ChessBoard {
piece_occupancy: [
// King
Square::E1 | Square::E8,
// Queen
Bitboard::EMPTY,
// Rook
Bitboard::EMPTY,
// Bishop
Bitboard::EMPTY,
// Knight
Bitboard::EMPTY,
// Pawn
Bitboard::EMPTY,
],
color_occupancy: [Square::E1 | Square::E8, Square::E1 | Square::E8],
combined_occupancy: Square::E1 | Square::E8,
castle_rights: [CastleRights::NoSide; 2],
en_passant: None,
half_move_clock: 0,
total_plies: 0,
side: Color::White,
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: ChessBoard = builder.try_into().unwrap();
*board.color_occupancy_mut(Color::White) |= Square::E8.into_bitboard();
board
};
assert_eq!(
position.validate().err().unwrap(),
InvalidError::OverlappingColors,
ValidationError::OverlappingColors,
);
}
#[test]
fn invalid_combined_does_not_equal_pieces() {
let position = ChessBoard {
piece_occupancy: [
// King
Square::E1 | Square::E8,
// Queen
Bitboard::EMPTY,
// Rook
Bitboard::EMPTY,
// Bishop
Bitboard::EMPTY,
// Knight
Bitboard::EMPTY,
// Pawn
Bitboard::EMPTY,
],
color_occupancy: [Square::E1.into_bitboard(), Square::E8.into_bitboard()],
combined_occupancy: Square::E1.into_bitboard(),
castle_rights: [CastleRights::NoSide; 2],
en_passant: None,
half_move_clock: 0,
total_plies: 0,
side: Color::White,
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: ChessBoard = builder.try_into().unwrap();
*board.piece_occupancy_mut(Piece::Pawn) |= Square::E2.into_bitboard();
board
};
assert_eq!(
position.validate().err().unwrap(),
InvalidError::ErroneousCombinedOccupancy,
ValidationError::ErroneousCombinedOccupancy,
);
}
#[test]
fn invalid_combined_does_not_equal_colors() {
let position = ChessBoard {
piece_occupancy: [
// King
Square::E1 | Square::E8,
// Queen
Bitboard::EMPTY,
// Rook
Bitboard::EMPTY,
// Bishop
Bitboard::EMPTY,
// Knight
Bitboard::EMPTY,
// Pawn
Bitboard::EMPTY,
],
color_occupancy: [Square::E1 | Square::H1, Square::E8 | Square::H8],
combined_occupancy: Square::E1 | Square::E8,
castle_rights: [CastleRights::NoSide; 2],
en_passant: None,
half_move_clock: 0,
total_plies: 0,
side: Color::White,
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: ChessBoard = builder.try_into().unwrap();
*board.color_occupancy_mut(Color::Black) |= Square::E2.into_bitboard();
board
};
assert_eq!(
position.validate().err().unwrap(),
InvalidError::ErroneousCombinedOccupancy,
ValidationError::ErroneousCombinedOccupancy,
);
}
@ -595,7 +535,7 @@ mod test {
builder[Square::E8] = Some((Piece::King, Color::Black));
TryInto::<ChessBoard>::try_into(builder)
};
assert_eq!(res.err().unwrap(), InvalidError::TooManyPieces);
assert_eq!(res.err().unwrap(), ValidationError::TooManyPieces);
}
#[test]
@ -607,7 +547,7 @@ mod test {
builder.with_castle_rights(CastleRights::BothSides, Color::White);
TryInto::<ChessBoard>::try_into(builder)
};
assert_eq!(res.err().unwrap(), InvalidError::InvalidCastlingRights);
assert_eq!(res.err().unwrap(), ValidationError::InvalidCastlingRights);
}
#[test]
@ -623,7 +563,7 @@ mod test {
builder.with_castle_rights(CastleRights::BothSides, Color::White);
TryInto::<ChessBoard>::try_into(builder)
};
assert_eq!(res.err().unwrap(), InvalidError::InvalidCastlingRights);
assert_eq!(res.err().unwrap(), ValidationError::InvalidCastlingRights);
}
#[test]
@ -647,7 +587,7 @@ mod test {
builder.with_en_passant(Square::A6);
TryInto::<ChessBoard>::try_into(builder)
};
assert_eq!(res.err().unwrap(), InvalidError::InvalidEnPassant);
assert_eq!(res.err().unwrap(), ValidationError::InvalidEnPassant);
}
#[test]
@ -660,7 +600,7 @@ mod test {
builder.with_en_passant(Square::A6);
TryInto::<ChessBoard>::try_into(builder)
};
assert_eq!(res.err().unwrap(), InvalidError::InvalidEnPassant);
assert_eq!(res.err().unwrap(), ValidationError::InvalidEnPassant);
}
#[test]
@ -673,7 +613,7 @@ mod test {
builder.with_en_passant(Square::A5);
TryInto::<ChessBoard>::try_into(builder)
};
assert_eq!(res.err().unwrap(), InvalidError::InvalidEnPassant);
assert_eq!(res.err().unwrap(), ValidationError::InvalidEnPassant);
}
#[test]
@ -684,7 +624,7 @@ mod test {
builder[Square::E2] = Some((Piece::King, Color::Black));
TryInto::<ChessBoard>::try_into(builder)
};
assert_eq!(res.err().unwrap(), InvalidError::NeighbouringKings);
assert_eq!(res.err().unwrap(), ValidationError::NeighbouringKings);
}
#[test]
@ -696,7 +636,7 @@ mod test {
builder[Square::E8] = Some((Piece::King, Color::Black));
TryInto::<ChessBoard>::try_into(builder)
};
assert_eq!(res.err().unwrap(), InvalidError::OpponentInCheck);
assert_eq!(res.err().unwrap(), ValidationError::OpponentInCheck);
}
#[test]
@ -708,7 +648,7 @@ mod test {
builder[Square::H8] = Some((Piece::King, Color::Black));
TryInto::<ChessBoard>::try_into(builder)
};
assert_eq!(res.err().unwrap(), InvalidError::InvalidPawnPosition);
assert_eq!(res.err().unwrap(), ValidationError::InvalidPawnPosition);
}
#[test]
@ -725,7 +665,7 @@ mod test {
}
TryInto::<ChessBoard>::try_into(builder)
};
assert_eq!(res.err().unwrap(), InvalidError::TooManyPieces);
assert_eq!(res.err().unwrap(), ValidationError::TooManyPieces);
}
#[test]
@ -759,7 +699,7 @@ mod test {
| Square::F3
| Square::G1
| Square::H2,
castle_rights: [CastleRights::NoSide; 2],
castle_rights: [CastleRights::NoSide; Color::NUM_VARIANTS],
en_passant: None,
half_move_clock: 0,
total_plies: 0,

View file

@ -19,11 +19,24 @@ impl Color {
}
/// Convert from a color index into a [Color] type.
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[inline(always)]
pub fn from_index(index: usize) -> Self {
assert!(index < Self::NUM_VARIANTS);
// SAFETY: we know the value is in-bounds
unsafe { Self::from_index_unchecked(index) }
Self::try_from_index(index).expect("index out of bouds")
}
/// Convert from a color index into a [Color] type. Returns [None] if the index is out of
/// bounds.
pub fn try_from_index(index: usize) -> Option<Self> {
if index < Self::NUM_VARIANTS {
// SAFETY: we know the value is in-bounds
Some(unsafe { Self::from_index_unchecked(index) })
} else {
None
}
}
/// Convert from a color index into a [Color] type, no bounds checking.

View file

@ -35,11 +35,23 @@ impl File {
}
/// Convert from a file index into a [File] type.
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[inline(always)]
pub fn from_index(index: usize) -> Self {
assert!(index < Self::NUM_VARIANTS);
// SAFETY: we know the value is in-bounds
unsafe { Self::from_index_unchecked(index) }
Self::try_from_index(index).expect("index out of bouds")
}
/// Convert from a file index into a [File] type. Returns [None] if the index is out of bounds.
pub fn try_from_index(index: usize) -> Option<Self> {
if index < Self::NUM_VARIANTS {
// SAFETY: we know the value is in-bounds
Some(unsafe { Self::from_index_unchecked(index) })
} else {
None
}
}
/// Convert from a file index into a [File] type, no bounds checking.

View file

@ -28,11 +28,24 @@ impl Piece {
}
/// Convert from a piece index into a [Piece] type.
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[inline(always)]
pub fn from_index(index: usize) -> Self {
assert!(index < Self::NUM_VARIANTS);
// SAFETY: we know the value is in-bounds
unsafe { Self::from_index_unchecked(index) }
Self::try_from_index(index).expect("index out of bouds")
}
/// Convert from a piece index into a [Piece] type. Returns [None] if the index is out of
/// bounds.
pub fn try_from_index(index: usize) -> Option<Self> {
if index < Self::NUM_VARIANTS {
// SAFETY: we know the value is in-bounds
Some(unsafe { Self::from_index_unchecked(index) })
} else {
None
}
}
/// Convert from a piece index into a [Piece] type, no bounds checking.

View file

@ -35,11 +35,23 @@ impl Rank {
}
/// Convert from a rank index into a [Rank] type.
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[inline(always)]
pub fn from_index(index: usize) -> Self {
assert!(index < Self::NUM_VARIANTS);
// SAFETY: we know the value is in-bounds
unsafe { Self::from_index_unchecked(index) }
Self::try_from_index(index).expect("index out of bouds")
}
/// Convert from a rank index into a [Rank] type. Returns [None] if the index is out of bounds.
pub fn try_from_index(index: usize) -> Option<Self> {
if index < Self::NUM_VARIANTS {
// SAFETY: we know the value is in-bounds
Some(unsafe { Self::from_index_unchecked(index) })
} else {
None
}
}
/// Convert from a rank index into a [Rank] type, no bounds checking.

View file

@ -39,6 +39,10 @@ impl Square {
];
/// Construct a [Square] from a [File] and [Rank].
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[inline(always)]
pub fn new(file: File, rank: Rank) -> Self {
// SAFETY: we know the value is in-bounds
@ -53,9 +57,18 @@ impl Square {
/// Convert from a square index into a [Square] type.
#[inline(always)]
pub fn from_index(index: usize) -> Self {
assert!(index < Self::NUM_VARIANTS);
// SAFETY: we know the value is in-bounds
unsafe { Self::from_index_unchecked(index) }
Self::try_from_index(index).expect("index out of bouds")
}
/// Convert from a square index into a [Square] type. Returns [None] if the index is out of
/// bounds.
pub fn try_from_index(index: usize) -> Option<Self> {
if index < Self::NUM_VARIANTS {
// SAFETY: we know the value is in-bounds
Some(unsafe { Self::from_index_unchecked(index) })
} else {
None
}
}
/// Convert from a square index into a [Square] type, no bounds checking.

View file

@ -1,5 +1,5 @@
use crate::board::{
CastleRights, ChessBoard, ChessBoardBuilder, Color, File, InvalidError, Piece, Rank, Square,
CastleRights, ChessBoard, ChessBoardBuilder, Color, File, Piece, Rank, Square, ValidationError,
};
/// A trait to mark items that can be converted from a FEN input.
@ -15,7 +15,7 @@ pub enum FenError {
/// Invalid FEN input.
InvalidFen,
/// Invalid chess position.
InvalidPosition(InvalidError),
InvalidPosition(ValidationError),
}
impl std::fmt::Display for FenError {
@ -29,15 +29,15 @@ impl std::fmt::Display for FenError {
impl std::error::Error for FenError {}
/// Allow converting a [InvalidError] into [FenError], for use with the '?' operator.
impl From<InvalidError> for FenError {
fn from(err: InvalidError) -> Self {
/// Allow converting a [ValidationError] into [FenError], for use with the '?' operator.
impl From<ValidationError> for FenError {
fn from(err: ValidationError) -> Self {
Self::InvalidPosition(err)
}
}
/// Convert the castling rights segment of a FEN string to an array of [CastleRights].
impl FromFen for [CastleRights; 2] {
impl FromFen for [CastleRights; Color::NUM_VARIANTS] {
type Err = FenError;
fn from_fen(s: &str) -> Result<Self, Self::Err> {
@ -45,7 +45,7 @@ impl FromFen for [CastleRights; 2] {
return Err(FenError::InvalidFen);
}
let mut res = [CastleRights::NoSide; 2];
let mut res = [CastleRights::NoSide; Color::NUM_VARIANTS];
if s == "-" {
return Ok(res);
@ -134,7 +134,7 @@ impl FromFen for ChessBoard {
let mut builder = ChessBoardBuilder::new();
let castle_rights = <[CastleRights; 2]>::from_fen(castling_rights)?;
let castle_rights = <[CastleRights; Color::NUM_VARIANTS]>::from_fen(castling_rights)?;
for color in Color::iter() {
builder.with_castle_rights(castle_rights[color.index()], color);
}

View file

@ -13,12 +13,12 @@ use crate::{
// A pre-rolled RNG for magic bitboard generation, using pre-determined values.
struct PreRolledRng {
numbers: [u64; 64],
numbers: [u64; Square::NUM_VARIANTS],
current_index: usize,
}
impl PreRolledRng {
pub fn new(numbers: [u64; 64]) -> Self {
pub fn new(numbers: [u64; Square::NUM_VARIANTS]) -> Self {
Self {
numbers,
current_index: 0,
@ -39,7 +39,8 @@ impl RandGen for PreRolledRng {
/// Compute the set of possible non-attack moves for a pawn on a [Square], given its [Color] and
/// set of blockers.
pub fn pawn_quiet_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard {
static PAWN_MOVES: OnceLock<[[Bitboard; 64]; 2]> = OnceLock::new();
static PAWN_MOVES: OnceLock<[[Bitboard; Square::NUM_VARIANTS]; Color::NUM_VARIANTS]> =
OnceLock::new();
// If there is a piece in front of the pawn, it can't advance
if !(color.backward_direction().move_board(blockers) & square).is_empty() {
@ -47,7 +48,7 @@ pub fn pawn_quiet_moves(color: Color, square: Square, blockers: Bitboard) -> Bit
}
PAWN_MOVES.get_or_init(|| {
let mut res = [[Bitboard::EMPTY; 64]; 2];
let mut res = [[Bitboard::EMPTY; Square::NUM_VARIANTS]; Color::NUM_VARIANTS];
for color in Color::iter() {
for square in Square::iter() {
res[color.index()][square.index()] =
@ -60,10 +61,11 @@ pub fn pawn_quiet_moves(color: Color, square: Square, blockers: Bitboard) -> Bit
/// Compute the set of possible attacks for a pawn on a [Square], given its [Color].
pub fn pawn_attacks(color: Color, square: Square) -> Bitboard {
static PAWN_ATTACKS: OnceLock<[[Bitboard; 64]; 2]> = OnceLock::new();
static PAWN_ATTACKS: OnceLock<[[Bitboard; Square::NUM_VARIANTS]; Color::NUM_VARIANTS]> =
OnceLock::new();
PAWN_ATTACKS.get_or_init(|| {
let mut res = [[Bitboard::EMPTY; 64]; 2];
let mut res = [[Bitboard::EMPTY; Square::NUM_VARIANTS]; Color::NUM_VARIANTS];
for color in Color::iter() {
for square in Square::iter() {
res[color.index()][square.index()] = naive::pawn_captures(color, square);
@ -81,9 +83,9 @@ pub fn pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard
/// Compute the set of possible moves for a knight on a [Square].
pub fn knight_moves(square: Square) -> Bitboard {
static KNIGHT_MOVES: OnceLock<[Bitboard; 64]> = OnceLock::new();
static KNIGHT_MOVES: OnceLock<[Bitboard; Square::NUM_VARIANTS]> = OnceLock::new();
KNIGHT_MOVES.get_or_init(|| {
let mut res = [Bitboard::EMPTY; 64];
let mut res = [Bitboard::EMPTY; Square::NUM_VARIANTS];
for square in Square::iter() {
res[square.index()] = naive::knight_moves(square)
}
@ -122,9 +124,9 @@ pub fn queen_moves(square: Square, blockers: Bitboard) -> Bitboard {
/// Compute the set of possible moves for a king on a [Square].
pub fn king_moves(square: Square) -> Bitboard {
static KING_MOVES: OnceLock<[Bitboard; 64]> = OnceLock::new();
static KING_MOVES: OnceLock<[Bitboard; Square::NUM_VARIANTS]> = OnceLock::new();
KING_MOVES.get_or_init(|| {
let mut res = [Bitboard::EMPTY; 64];
let mut res = [Bitboard::EMPTY; Square::NUM_VARIANTS];
for square in Square::iter() {
res[square.index()] = naive::king_moves(square)
}

View file

@ -59,7 +59,7 @@ impl MagicMoves {
// region:sourcegen
/// A set of magic numbers for bishop move generation.
pub(crate) const BISHOP_SEED: [u64; 64] = [
pub(crate) const BISHOP_SEED: [u64; Square::NUM_VARIANTS] = [
4908958787341189172,
1157496606860279808,
289395876198088778,
@ -127,7 +127,7 @@ pub(crate) const BISHOP_SEED: [u64; 64] = [
];
/// A set of magic numbers for rook move generation.
pub(crate) const ROOK_SEED: [u64; 64] = [
pub(crate) const ROOK_SEED: [u64; Square::NUM_VARIANTS] = [
2341871943948451840,
18015635528220736,
72066665545773824,
@ -197,6 +197,8 @@ pub(crate) const ROOK_SEED: [u64; 64] = [
#[cfg(test)]
mod test {
use std::fmt::Write as _;
use super::*;
// A simple XOR-shift RNG implementation.
@ -238,20 +240,25 @@ mod test {
Some((prefix, mid, suffix))
}
fn array_string(piece_type: &str, values: &[Magic]) -> String {
let mut res = format!(
"/// A set of magic numbers for {} move generation.\n",
fn array_string(piece_type: &str, values: &[Magic]) -> Result<String, std::fmt::Error> {
let mut res = String::new();
writeln!(
&mut res,
"/// A set of magic numbers for {} move generation.",
piece_type
);
res.push_str(&format!(
"pub(crate) const {}_SEED: [u64; 64] = [\n",
)?;
writeln!(
&mut res,
"pub(crate) const {}_SEED: [u64; Square::NUM_VARIANTS] = [",
piece_type.to_uppercase()
));
)?;
for magic in values {
res.push_str(&format!(" {},\n", magic.magic));
writeln!(&mut res, " {},", magic.magic)?;
}
res.push_str("];\n");
res
writeln!(&mut res, "];")?;
Ok(res)
}
#[test]
@ -264,8 +271,8 @@ mod test {
let original_text = std::fs::read_to_string(file!()).unwrap();
let bishop_array = array_string("bishop", &bishop_magics[..]);
let rook_array = array_string("rook", &rook_magics[..]);
let bishop_array = array_string("bishop", &bishop_magics[..]).unwrap();
let rook_array = array_string("rook", &rook_magics[..]).unwrap();
let new_text = {
let start_marker = "// region:sourcegen\n";