diff options
| author | Mica White <botahamec@outlook.com> | 2025-12-08 19:56:48 -0500 |
|---|---|---|
| committer | Mica White <botahamec@outlook.com> | 2025-12-08 19:56:48 -0500 |
| commit | fdb2804883deb31e3aeb15bbe588dcc9b7b76bd0 (patch) | |
| tree | a4c51cd88664cab7b6673dcd3b425fb7a0202a38 /model/src | |
| parent | 93461aa9399d981db7d8611b3eb166636de4d971 (diff) | |
Diffstat (limited to 'model/src')
| -rwxr-xr-x[-rw-r--r--] | model/src/board.rs | 1346 | ||||
| -rwxr-xr-x[-rw-r--r--] | model/src/board/tests.rs | 1114 | ||||
| -rwxr-xr-x[-rw-r--r--] | model/src/color.rs | 0 | ||||
| -rwxr-xr-x[-rw-r--r--] | model/src/coordinates.rs | 304 | ||||
| -rwxr-xr-x[-rw-r--r--] | model/src/lib.rs | 26 | ||||
| -rwxr-xr-x[-rw-r--r--] | model/src/moves.rs | 590 | ||||
| -rwxr-xr-x[-rw-r--r--] | model/src/piece.rs | 42 | ||||
| -rwxr-xr-x[-rw-r--r--] | model/src/possible_moves.rs | 2156 |
8 files changed, 2789 insertions, 2789 deletions
diff --git a/model/src/board.rs b/model/src/board.rs index b722fd6..c6d6551 100644..100755 --- a/model/src/board.rs +++ b/model/src/board.rs @@ -1,673 +1,673 @@ -use crate::possible_moves::PossibleMoves; -use crate::{Piece, PieceColor, SquareCoordinate}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; -use std::hash::{Hash, Hasher}; - -#[cfg(test)] -mod tests; - -/// A checker board, -/// organized in the following structure: -/// ```txt -/// 11 05 31 25 -/// 10 04 30 24 -/// 03 29 23 17 -/// 02 28 22 16 -/// 27 21 15 09 -/// 26 20 14 08 -/// 19 13 07 01 -/// 18 12 06 00 -/// ``` -#[derive(Copy, Clone, Debug, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct CheckersBitBoard { - /// If the space contains a piece, it's a 1 - pub pieces: u32, - /// If the piece is black, 1, otherwise 0 - pub color: u32, - /// 1 if the piece is a king - pub kings: u32, - /// The player who has the next turn - pub turn: PieceColor, - /// The player with the previous turn - pub previous_turn: PieceColor, - /// Where the most recent move was to - pub previous_move_to: u8, -} - -impl Default for CheckersBitBoard { - /// Returns the starting position - fn default() -> Self { - Self::starting_position() - } -} - -impl PartialEq for CheckersBitBoard { - fn eq(&self, other: &Self) -> bool { - self.pieces == other.pieces - && self.pieces & self.color == other.pieces & other.color - && self.pieces & self.kings == other.pieces & other.kings - && self.turn == other.turn - } -} - -impl Hash for CheckersBitBoard { - /// Hashes with only the pieces part, to ensure correctness and efficiency - fn hash<H: Hasher>(&self, hasher: &mut H) { - self.hash_code().hash(hasher) - } -} - -impl CheckersBitBoard { - /// Creates a new Checkers BitBoard - /// - /// # Arguments - /// - /// * `pieces` - Each bit is 1 if the corresponding space contains a piece - /// * `color` - For each space with a piece, the value is 1 if it's dark, and 0 otherwise. - /// Bits for spaces without colors are undefined - /// * `kings` - For each space with a piece, the value is 1 if it's a king, and 0 otherwise. - /// Bits for spaces without colors are undefined - /// - /// # Example - /// - /// ``` - /// // This is the starting position - /// use model::{CheckersBitBoard, PieceColor}; - /// let board = CheckersBitBoard::new(0b11011111101111100111100111100111, - /// 0b00111100001100001100001111001111, - /// 0, - /// PieceColor::Dark); - /// ``` - #[must_use] - pub const fn new(pieces: u32, color: u32, kings: u32, turn: PieceColor) -> Self { - Self { - pieces, - color, - kings, - turn, - previous_turn: turn.flip(), - // this field is only used if previous_turn == turn - previous_move_to: 0, - } - } - - /// Creates a board at the starting position - #[must_use] - pub const fn starting_position() -> Self { - const STARTING_BITBOARD: CheckersBitBoard = CheckersBitBoard::new( - 0b11100111100111100111110111111011, - 0b00001100001111001111001111000011, - 0, - PieceColor::Dark, - ); - STARTING_BITBOARD - } - - #[must_use] - pub const fn hash_code(self) -> u64 { - (((self.color & self.pieces) as u64) << 32) | (((!self.color & self.pieces) as u64) << 32) - } - - /// Gets the bits that represent where pieces are on the board - #[must_use] - pub const fn pieces_bits(self) -> u32 { - self.pieces - } - - /// Gets the bits that represents the color of each piece on the board - /// - /// # Safety - /// - /// This is inherently unsafe, because this also returns the bits of empty squares - #[must_use] - pub const fn color_bits(self) -> u32 { - self.color - } - - /// Gets the bits that represents the status of each piece on the board - /// - /// # Safety - /// - /// This is inherently unsafe, because this also returns the bits of empty squares - #[must_use] - pub const fn king_bits(self) -> u32 { - self.kings - } - - /// The player whose turn it is - #[must_use] - pub const fn turn(self) -> PieceColor { - self.turn - } - - /// Gets the piece at a given row column coordinate - /// - /// # Arguments - /// - /// * `row` - The row. The a file is row 0 - /// * `col` - The column. The first rank is column 0 - #[must_use] - // TODO test - pub fn get_at_row_col(self, row: usize, col: usize) -> Option<Piece> { - if row > 32 || col > 32 { - None - } else { - let value = SquareCoordinate::new(row as u8, col as u8).to_ampere_value(); - if let Some(value) = value { - if self.piece_at(value) { - Some(Piece::new( - unsafe { self.king_at_unchecked(value) }, - unsafe { self.color_at_unchecked(value) }, - )) - } else { - None - } - } else { - None - } - } - } - - /// Checks if there's a piece at the given space value - /// - /// # Arguments - /// - /// * `value` - The value of the space to check - /// - /// # Example - /// - /// ``` - /// use model::CheckersBitBoard; - /// let board = CheckersBitBoard::default(); - /// match board.piece_at(0) { - /// true => println!("There's a piece in the bottom right"), - /// false => println!("The bottom right is empty") - /// } - /// ``` - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - #[must_use] - pub const fn piece_at(self, value: usize) -> bool { - ((self.pieces >> value) & 1) == 1 - } - - /// Checks the color at the piece in the given location, - /// without checking if there's a piece there - /// - /// # Arguments - /// - /// * `value` - The value of the space to check - /// - /// # Example - /// - /// ``` - /// use model::CheckersBitBoard; - /// use model::PieceColor; - /// let board = CheckersBitBoard::default(); - /// if board.piece_at(0) { - /// match unsafe {board.color_at_unchecked(0)} { - /// PieceColor::Dark => println!("The piece in the bottom right is dark colored"), - /// PieceColor::Light => println!("The piece in the bottom right is light colored") - /// } - /// } - /// ``` - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - /// - /// # Safety - /// - /// Checking the color at a square that is empty results in undefined behavior - #[must_use] - pub const unsafe fn color_at_unchecked(self, value: usize) -> PieceColor { - if ((self.color >> value) & 1) != 0 { - PieceColor::Dark - } else { - PieceColor::Light - } - } - - /// Checks the color at the piece in the given location. - /// Returns `None` if there isn't a piece there - /// - /// # Arguments - /// - /// * `value` - The value of the space to check - /// - /// # Example - /// - /// ``` - /// use model::CheckersBitBoard; - /// use model::PieceColor; - /// let board = CheckersBitBoard::default(); - /// if let Some(color) = board.color_at(0) { - /// match color { - /// PieceColor::Dark => println!("The piece in the bottom right is dark colored"), - /// PieceColor::Light => println!("The piece in the bottom left is light colored") - /// } - /// } - /// ``` - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - #[must_use] - pub const fn color_at(self, value: usize) -> Option<PieceColor> { - if self.piece_at(value) { - // safety: if this block runs, then it's already confirmed a piece exists here - Some(unsafe { self.color_at_unchecked(value) }) - } else { - None - } - } - - /// Checks if the given location has a king, without checking if there's a piece there - /// - /// # Arguments - /// - /// * `value` - The value of the space to check - /// - /// # Example - /// - /// ``` - /// use model::CheckersBitBoard; - /// let board = CheckersBitBoard::default(); - /// if board.piece_at(0) { - /// match unsafe {board.king_at_unchecked(0)} { - /// true => println!("The piece in the bottom right is a king"), - /// false => println!("The piece in the bottom right is a peasant") - /// } - /// } - /// ``` - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - /// - /// # Safety - /// - /// Checking a square that is empty results in undefined behavior - #[must_use] - pub const unsafe fn king_at_unchecked(self, value: usize) -> bool { - ((self.kings >> value) & 1) == 1 - } - - /// Checks if the piece in the given location is a king. - /// Returns `None` if there isn't a piece there - /// - /// # Arguments - /// - /// * `value` - The value of the space to check - /// - /// # Example - /// - /// ``` - /// use model::CheckersBitBoard; - /// let board = CheckersBitBoard::default(); - /// if let Some(status) = board.king_at(0) { - /// match status { - /// true => println!("The piece in the bottom right is a king"), - /// false => println!("The piece in the bottom right is a peasant") - /// } - /// } - /// ``` - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - #[must_use] - pub const fn king_at(self, value: usize) -> Option<bool> { - if self.piece_at(value) { - // safety: if this block runs, then it's already confirmed a piece exists here - Some(unsafe { self.king_at_unchecked(value) }) - } else { - None - } - } - - /// Change whose turn it is, without modifying the board - #[must_use] - // TODO test - pub const fn flip_turn(self) -> Self { - CheckersBitBoard::new(self.pieces, self.color, self.kings, self.turn.flip()) - } - - /// Change whose turn it was previously to the current player - pub const fn set_previous_turn(self, dest: usize) -> Self { - CheckersBitBoard { - pieces: self.pieces, - color: self.color, - kings: self.kings, - turn: self.turn, - previous_turn: self.turn, - previous_move_to: dest as u8, - } - } - - /// Moves a piece from `start` to `dest`. The original location will be empty. - /// This does not mutate the original board. - /// If a piece already exists at `dest`, it will be overwritten. - /// - /// # Arguments - /// - /// * `start` - The original location of the piece - /// * `dest` - The new location - /// - /// # Panics - /// - /// Panics if `start` or `dest` is greater than or equal to 32 - /// - /// # Safety - /// - /// Results in undefined behavior if `start` does not contain a piece - // TODO rip out so we don't need to check for both black and white promotion - #[must_use] - pub const unsafe fn move_piece_to_unchecked(self, start: usize, dest: usize) -> Self { - // Clears the bit at the starting value - // Sets the bit at the destination value - let pieces = (self.pieces & !(1 << start)) | (1 << dest); - - // Clears the bit at the destination value - // Sets the value at the destination to the value of the start - let color = (self.color & !(1 << dest)) | (((self.color >> start) & 1) << dest); - - // The squares where certain pieces should be promoted - const DARK_PROMOTION_MASK: u32 = 0b10000010000000000000100000100000; - const LIGHT_PROMOTION_MASK: u32 = 0b1000001000001000001; - - // Clears the bit at the destination value - // Sets the value at the destination to the value of the start - // Promotes if the end of the board was reached - let kings = (self.kings & !(1 << dest)) - | (((self.kings >> start) & 1) << dest) - | (color & DARK_PROMOTION_MASK) - | (!color & LIGHT_PROMOTION_MASK); - - let turn = self.turn.flip(); - - CheckersBitBoard::new(pieces, color, kings, turn) - } - - /// Moves a piece from `value` to `(value + amount) % 32`. The original location will be empty. - /// This does not mutate the original board - /// - /// # Arguments - /// - /// * `value` - The original location of the piece - /// * `amount` - The amount to shift the location by - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32, - /// or `value + amount` is greater than `usize::MAX` - /// - /// # Safety - /// - /// This results in undefined behavior if `value` does not contain a piece - #[must_use] - const unsafe fn move_piece_forward_unchecked(self, value: usize, amount: usize) -> Self { - self.move_piece_to_unchecked(value, (value + amount) & 31) - } - - /// Moves a piece from `value` to `(value - amount) % 32`. The original location will be empty. - /// This does not mutate the original board. - /// If a piece already exists there, then it will be overwritten - /// - /// # Arguments - /// - /// * `value` - The original location of the piece - /// * `amount` - The amount to shift the location by - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - /// - /// # Safety - /// - /// This results in undefined behavior if `value` does not contain a piece - #[must_use] - const unsafe fn move_piece_backward_unchecked(self, value: usize, amount: usize) -> Self { - self.move_piece_to_unchecked(value, value.wrapping_sub(amount) & 31) - } - - /// Tries to move the piece forward and to the left, without checking if it's a legal move. - /// If a piece already exists there, then it will be overwritten - /// - /// # Arguments - /// - /// * `value` - The original location of the piece - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - /// - /// # Safety - /// - /// Moving from the left side of the board results in undefined behavior. - /// Moving from the top of the board results in undefined behavior. - /// A `value` which doesn't contain a piece results in undefined behavior. - #[must_use] - pub const unsafe fn move_piece_forward_left_unchecked(self, value: usize) -> Self { - self.move_piece_forward_unchecked(value, 7) - } - - /// Tries to move the piece forward and to the right, without checking if it's a legal move. - /// If a piece already exists there, then it will be overwritten - /// - /// # Arguments - /// - /// * `value` - The original location of the piece - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - /// - /// # Safety - /// - /// Moving from the right side of the board results in undefined behavior. - /// Moving from the top of the board results in undefined behavior. - /// A `value` which doesn't contain a piece results in undefined behavior. - #[must_use] - pub const unsafe fn move_piece_forward_right_unchecked(self, value: usize) -> Self { - self.move_piece_forward_unchecked(value, 1) - } - - /// Tries to move the piece backward and to the left, without checking if it's a legal move. - /// If a piece already exists there, then it will be overwritten - /// - /// # Arguments - /// - /// * `value` - The original location of the piece - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - /// - /// # Safety - /// - /// Moving from the left side of the board results in undefined behavior. - /// Moving from the bottom of the board results in undefined behavior. - /// A `value` which doesn't contain a piece results in undefined behavior. - #[must_use] - pub const unsafe fn move_piece_backward_left_unchecked(self, value: usize) -> Self { - self.move_piece_backward_unchecked(value, 1) - } - - /// Tries to move the piece backward and to the right, without checking if it's a legal move. - /// If a piece already exists there, then it will be overwritten - /// - /// # Arguments - /// - /// * `value` - The original location of the piece - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - /// - /// # Safety - /// - /// Moving from the right side of the board results in undefined behavior. - /// Moving from the bottom of the board results in undefined behavior. - /// A `value` which doesn't contain a piece results in undefined behavior. - #[must_use] - pub const unsafe fn move_piece_backward_right_unchecked(self, value: usize) -> Self { - self.move_piece_backward_unchecked(value, 7) - } - - /// Clears a space on the board. If the space is empty, then this function does nothing. - /// - /// # Arguments - /// - /// * `value` - The value of the space to clear - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - #[must_use] - pub const fn clear_piece(self, value: usize) -> Self { - let pieces = self.pieces & !(1 << value); - CheckersBitBoard::new(pieces, self.color, self.kings, self.turn) - } - - /// Tries to jump the piece forward and to the left, without checking if it's a legal move. - /// If a piece already exists there, then it will be overwritten. - /// The space the piece jumps over is cleared - /// - /// # Arguments - /// - /// * `value` - The original location of the piece - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - /// - /// # Safety - /// - /// Moving from the left side of the board results in undefined behavior. - /// Moving from the top of the board results in undefined behavior - #[must_use] - // TODO test the edge cases of the below if statement - pub const unsafe fn jump_piece_forward_left_unchecked(self, value: usize) -> Self { - let is_king = self.king_at_unchecked(value); - let board = self - .move_piece_forward_unchecked(value, 14) - .clear_piece((value + 7) & 31); - - const KING_MASK: u32 = 0b00100000100000100000000000001000; - if (is_king || (((1 << value) & KING_MASK) == 0)) - && PossibleMoves::has_jumps_at(board.flip_turn(), (value + 14) & 31) - { - board.flip_turn().set_previous_turn((value + 14) & 31) - } else { - board - } - } - - /// Tries to move the piece forward and to the right, without checking if it's a legal move. - /// If a piece already exists there, then it will be overwritten - /// The space the piece jumps over is cleared - /// - /// # Arguments - /// - /// * `value` - The original location of the piece - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - /// - /// # Safety - /// - /// Moving from the right side of the board results in undefined behavior. - /// Moving from the top of the board results in undefined behavior - #[must_use] - pub const unsafe fn jump_piece_forward_right_unchecked(self, value: usize) -> Self { - let is_king = self.king_at_unchecked(value); - let board = self - .move_piece_forward_unchecked(value, 2) - .clear_piece((value + 1) & 31); - - const KING_MASK: u32 = 0b00100000100000100000000000001000; - if (is_king || (((1 << value) & KING_MASK) == 0)) - && PossibleMoves::has_jumps_at(board.flip_turn(), (value + 2) & 31) - { - board.flip_turn().set_previous_turn((value + 2) & 31) - } else { - board - } - } - - /// Tries to move the piece backward and to the left, without checking if it's a legal move. - /// If a piece already exists there, then it will be overwritten - /// The space the piece jumps over is cleared - /// - /// # Arguments - /// - /// * `value` - The original location of the piece - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - /// - /// # Safety - /// - /// Moving from the left side of the board results in undefined behavior. - /// Moving from the bottom of the board results in undefined behavior - #[must_use] - pub const unsafe fn jump_piece_backward_left_unchecked(self, value: usize) -> Self { - let is_king = self.king_at_unchecked(value); - let board = self - .move_piece_backward_unchecked(value, 2) - .clear_piece(value.wrapping_sub(1) & 31); - - const KING_MASK: u32 = 0b00000100000100000100000100000000; - if (is_king || (((1 << value) & KING_MASK) == 0)) - && PossibleMoves::has_jumps_at(board.flip_turn(), value.wrapping_sub(2) & 31) - { - board - .flip_turn() - .set_previous_turn((value.wrapping_sub(2)) & 31) - } else { - board - } - } - - /// Tries to move the piece backward and to the right, without checking if it's a legal move. - /// If a piece already exists there, then it will be overwritten - /// The space the piece jumps over is cleared - /// - /// # Arguments - /// - /// * `value` - The original location of the piece - /// - /// # Panics - /// - /// Panics if `value` is greater than or equal to 32 - /// - /// # Safety - /// - /// Moving from the right side of the board results in undefined behavior. - /// Moving from the bottom of the board results in undefined behavior - #[must_use] - pub const unsafe fn jump_piece_backward_right_unchecked(self, value: usize) -> Self { - let is_king = self.king_at_unchecked(value); - let board = self - .move_piece_backward_unchecked(value, 14) - .clear_piece(value.wrapping_sub(7) & 31); - - const KING_MASK: u32 = 0b00000100000100000100000100000000; - if (is_king || (((1 << value) & KING_MASK) == 0)) - && PossibleMoves::has_jumps_at(board.flip_turn(), value.wrapping_sub(14) & 31) - { - board - .flip_turn() - .set_previous_turn((value.wrapping_sub(14)) & 31) - } else { - board - } - } -} +use crate::possible_moves::PossibleMoves;
+use crate::{Piece, PieceColor, SquareCoordinate};
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+use std::hash::{Hash, Hasher};
+
+#[cfg(test)]
+mod tests;
+
+/// A checker board,
+/// organized in the following structure:
+/// ```txt
+/// 11 05 31 25
+/// 10 04 30 24
+/// 03 29 23 17
+/// 02 28 22 16
+/// 27 21 15 09
+/// 26 20 14 08
+/// 19 13 07 01
+/// 18 12 06 00
+/// ```
+#[derive(Copy, Clone, Debug, Eq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+pub struct CheckersBitBoard {
+ /// If the space contains a piece, it's a 1
+ pub pieces: u32,
+ /// If the piece is black, 1, otherwise 0
+ pub color: u32,
+ /// 1 if the piece is a king
+ pub kings: u32,
+ /// The player who has the next turn
+ pub turn: PieceColor,
+ /// The player with the previous turn
+ pub previous_turn: PieceColor,
+ /// Where the most recent move was to
+ pub previous_move_to: u8,
+}
+
+impl Default for CheckersBitBoard {
+ /// Returns the starting position
+ fn default() -> Self {
+ Self::starting_position()
+ }
+}
+
+impl PartialEq for CheckersBitBoard {
+ fn eq(&self, other: &Self) -> bool {
+ self.pieces == other.pieces
+ && self.pieces & self.color == other.pieces & other.color
+ && self.pieces & self.kings == other.pieces & other.kings
+ && self.turn == other.turn
+ }
+}
+
+impl Hash for CheckersBitBoard {
+ /// Hashes with only the pieces part, to ensure correctness and efficiency
+ fn hash<H: Hasher>(&self, hasher: &mut H) {
+ self.hash_code().hash(hasher)
+ }
+}
+
+impl CheckersBitBoard {
+ /// Creates a new Checkers BitBoard
+ ///
+ /// # Arguments
+ ///
+ /// * `pieces` - Each bit is 1 if the corresponding space contains a piece
+ /// * `color` - For each space with a piece, the value is 1 if it's dark, and 0 otherwise.
+ /// Bits for spaces without colors are undefined
+ /// * `kings` - For each space with a piece, the value is 1 if it's a king, and 0 otherwise.
+ /// Bits for spaces without colors are undefined
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// // This is the starting position
+ /// use model::{CheckersBitBoard, PieceColor};
+ /// let board = CheckersBitBoard::new(0b11011111101111100111100111100111,
+ /// 0b00111100001100001100001111001111,
+ /// 0,
+ /// PieceColor::Dark);
+ /// ```
+ #[must_use]
+ pub const fn new(pieces: u32, color: u32, kings: u32, turn: PieceColor) -> Self {
+ Self {
+ pieces,
+ color,
+ kings,
+ turn,
+ previous_turn: turn.flip(),
+ // this field is only used if previous_turn == turn
+ previous_move_to: 0,
+ }
+ }
+
+ /// Creates a board at the starting position
+ #[must_use]
+ pub const fn starting_position() -> Self {
+ const STARTING_BITBOARD: CheckersBitBoard = CheckersBitBoard::new(
+ 0b11100111100111100111110111111011,
+ 0b00001100001111001111001111000011,
+ 0,
+ PieceColor::Dark,
+ );
+ STARTING_BITBOARD
+ }
+
+ #[must_use]
+ pub const fn hash_code(self) -> u64 {
+ (((self.color & self.pieces) as u64) << 32) | (((!self.color & self.pieces) as u64) << 32)
+ }
+
+ /// Gets the bits that represent where pieces are on the board
+ #[must_use]
+ pub const fn pieces_bits(self) -> u32 {
+ self.pieces
+ }
+
+ /// Gets the bits that represents the color of each piece on the board
+ ///
+ /// # Safety
+ ///
+ /// This is inherently unsafe, because this also returns the bits of empty squares
+ #[must_use]
+ pub const fn color_bits(self) -> u32 {
+ self.color
+ }
+
+ /// Gets the bits that represents the status of each piece on the board
+ ///
+ /// # Safety
+ ///
+ /// This is inherently unsafe, because this also returns the bits of empty squares
+ #[must_use]
+ pub const fn king_bits(self) -> u32 {
+ self.kings
+ }
+
+ /// The player whose turn it is
+ #[must_use]
+ pub const fn turn(self) -> PieceColor {
+ self.turn
+ }
+
+ /// Gets the piece at a given row column coordinate
+ ///
+ /// # Arguments
+ ///
+ /// * `row` - The row. The a file is row 0
+ /// * `col` - The column. The first rank is column 0
+ #[must_use]
+ // TODO test
+ pub fn get_at_row_col(self, row: usize, col: usize) -> Option<Piece> {
+ if row > 32 || col > 32 {
+ None
+ } else {
+ let value = SquareCoordinate::new(row as u8, col as u8).to_ampere_value();
+ if let Some(value) = value {
+ if self.piece_at(value) {
+ Some(Piece::new(
+ unsafe { self.king_at_unchecked(value) },
+ unsafe { self.color_at_unchecked(value) },
+ ))
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Checks if there's a piece at the given space value
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The value of the space to check
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use model::CheckersBitBoard;
+ /// let board = CheckersBitBoard::default();
+ /// match board.piece_at(0) {
+ /// true => println!("There's a piece in the bottom right"),
+ /// false => println!("The bottom right is empty")
+ /// }
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ #[must_use]
+ pub const fn piece_at(self, value: usize) -> bool {
+ ((self.pieces >> value) & 1) == 1
+ }
+
+ /// Checks the color at the piece in the given location,
+ /// without checking if there's a piece there
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The value of the space to check
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use model::CheckersBitBoard;
+ /// use model::PieceColor;
+ /// let board = CheckersBitBoard::default();
+ /// if board.piece_at(0) {
+ /// match unsafe {board.color_at_unchecked(0)} {
+ /// PieceColor::Dark => println!("The piece in the bottom right is dark colored"),
+ /// PieceColor::Light => println!("The piece in the bottom right is light colored")
+ /// }
+ /// }
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Checking the color at a square that is empty results in undefined behavior
+ #[must_use]
+ pub const unsafe fn color_at_unchecked(self, value: usize) -> PieceColor {
+ if ((self.color >> value) & 1) != 0 {
+ PieceColor::Dark
+ } else {
+ PieceColor::Light
+ }
+ }
+
+ /// Checks the color at the piece in the given location.
+ /// Returns `None` if there isn't a piece there
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The value of the space to check
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use model::CheckersBitBoard;
+ /// use model::PieceColor;
+ /// let board = CheckersBitBoard::default();
+ /// if let Some(color) = board.color_at(0) {
+ /// match color {
+ /// PieceColor::Dark => println!("The piece in the bottom right is dark colored"),
+ /// PieceColor::Light => println!("The piece in the bottom left is light colored")
+ /// }
+ /// }
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ #[must_use]
+ pub const fn color_at(self, value: usize) -> Option<PieceColor> {
+ if self.piece_at(value) {
+ // safety: if this block runs, then it's already confirmed a piece exists here
+ Some(unsafe { self.color_at_unchecked(value) })
+ } else {
+ None
+ }
+ }
+
+ /// Checks if the given location has a king, without checking if there's a piece there
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The value of the space to check
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use model::CheckersBitBoard;
+ /// let board = CheckersBitBoard::default();
+ /// if board.piece_at(0) {
+ /// match unsafe {board.king_at_unchecked(0)} {
+ /// true => println!("The piece in the bottom right is a king"),
+ /// false => println!("The piece in the bottom right is a peasant")
+ /// }
+ /// }
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Checking a square that is empty results in undefined behavior
+ #[must_use]
+ pub const unsafe fn king_at_unchecked(self, value: usize) -> bool {
+ ((self.kings >> value) & 1) == 1
+ }
+
+ /// Checks if the piece in the given location is a king.
+ /// Returns `None` if there isn't a piece there
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The value of the space to check
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use model::CheckersBitBoard;
+ /// let board = CheckersBitBoard::default();
+ /// if let Some(status) = board.king_at(0) {
+ /// match status {
+ /// true => println!("The piece in the bottom right is a king"),
+ /// false => println!("The piece in the bottom right is a peasant")
+ /// }
+ /// }
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ #[must_use]
+ pub const fn king_at(self, value: usize) -> Option<bool> {
+ if self.piece_at(value) {
+ // safety: if this block runs, then it's already confirmed a piece exists here
+ Some(unsafe { self.king_at_unchecked(value) })
+ } else {
+ None
+ }
+ }
+
+ /// Change whose turn it is, without modifying the board
+ #[must_use]
+ // TODO test
+ pub const fn flip_turn(self) -> Self {
+ CheckersBitBoard::new(self.pieces, self.color, self.kings, self.turn.flip())
+ }
+
+ /// Change whose turn it was previously to the current player
+ pub const fn set_previous_turn(self, dest: usize) -> Self {
+ CheckersBitBoard {
+ pieces: self.pieces,
+ color: self.color,
+ kings: self.kings,
+ turn: self.turn,
+ previous_turn: self.turn,
+ previous_move_to: dest as u8,
+ }
+ }
+
+ /// Moves a piece from `start` to `dest`. The original location will be empty.
+ /// This does not mutate the original board.
+ /// If a piece already exists at `dest`, it will be overwritten.
+ ///
+ /// # Arguments
+ ///
+ /// * `start` - The original location of the piece
+ /// * `dest` - The new location
+ ///
+ /// # Panics
+ ///
+ /// Panics if `start` or `dest` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Results in undefined behavior if `start` does not contain a piece
+ // TODO rip out so we don't need to check for both black and white promotion
+ #[must_use]
+ pub const unsafe fn move_piece_to_unchecked(self, start: usize, dest: usize) -> Self {
+ // Clears the bit at the starting value
+ // Sets the bit at the destination value
+ let pieces = (self.pieces & !(1 << start)) | (1 << dest);
+
+ // Clears the bit at the destination value
+ // Sets the value at the destination to the value of the start
+ let color = (self.color & !(1 << dest)) | (((self.color >> start) & 1) << dest);
+
+ // The squares where certain pieces should be promoted
+ const DARK_PROMOTION_MASK: u32 = 0b10000010000000000000100000100000;
+ const LIGHT_PROMOTION_MASK: u32 = 0b1000001000001000001;
+
+ // Clears the bit at the destination value
+ // Sets the value at the destination to the value of the start
+ // Promotes if the end of the board was reached
+ let kings = (self.kings & !(1 << dest))
+ | (((self.kings >> start) & 1) << dest)
+ | (color & DARK_PROMOTION_MASK)
+ | (!color & LIGHT_PROMOTION_MASK);
+
+ let turn = self.turn.flip();
+
+ CheckersBitBoard::new(pieces, color, kings, turn)
+ }
+
+ /// Moves a piece from `value` to `(value + amount) % 32`. The original location will be empty.
+ /// This does not mutate the original board
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The original location of the piece
+ /// * `amount` - The amount to shift the location by
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32,
+ /// or `value + amount` is greater than `usize::MAX`
+ ///
+ /// # Safety
+ ///
+ /// This results in undefined behavior if `value` does not contain a piece
+ #[must_use]
+ const unsafe fn move_piece_forward_unchecked(self, value: usize, amount: usize) -> Self {
+ self.move_piece_to_unchecked(value, (value + amount) & 31)
+ }
+
+ /// Moves a piece from `value` to `(value - amount) % 32`. The original location will be empty.
+ /// This does not mutate the original board.
+ /// If a piece already exists there, then it will be overwritten
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The original location of the piece
+ /// * `amount` - The amount to shift the location by
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// This results in undefined behavior if `value` does not contain a piece
+ #[must_use]
+ const unsafe fn move_piece_backward_unchecked(self, value: usize, amount: usize) -> Self {
+ self.move_piece_to_unchecked(value, value.wrapping_sub(amount) & 31)
+ }
+
+ /// Tries to move the piece forward and to the left, without checking if it's a legal move.
+ /// If a piece already exists there, then it will be overwritten
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The original location of the piece
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Moving from the left side of the board results in undefined behavior.
+ /// Moving from the top of the board results in undefined behavior.
+ /// A `value` which doesn't contain a piece results in undefined behavior.
+ #[must_use]
+ pub const unsafe fn move_piece_forward_left_unchecked(self, value: usize) -> Self {
+ self.move_piece_forward_unchecked(value, 7)
+ }
+
+ /// Tries to move the piece forward and to the right, without checking if it's a legal move.
+ /// If a piece already exists there, then it will be overwritten
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The original location of the piece
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Moving from the right side of the board results in undefined behavior.
+ /// Moving from the top of the board results in undefined behavior.
+ /// A `value` which doesn't contain a piece results in undefined behavior.
+ #[must_use]
+ pub const unsafe fn move_piece_forward_right_unchecked(self, value: usize) -> Self {
+ self.move_piece_forward_unchecked(value, 1)
+ }
+
+ /// Tries to move the piece backward and to the left, without checking if it's a legal move.
+ /// If a piece already exists there, then it will be overwritten
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The original location of the piece
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Moving from the left side of the board results in undefined behavior.
+ /// Moving from the bottom of the board results in undefined behavior.
+ /// A `value` which doesn't contain a piece results in undefined behavior.
+ #[must_use]
+ pub const unsafe fn move_piece_backward_left_unchecked(self, value: usize) -> Self {
+ self.move_piece_backward_unchecked(value, 1)
+ }
+
+ /// Tries to move the piece backward and to the right, without checking if it's a legal move.
+ /// If a piece already exists there, then it will be overwritten
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The original location of the piece
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Moving from the right side of the board results in undefined behavior.
+ /// Moving from the bottom of the board results in undefined behavior.
+ /// A `value` which doesn't contain a piece results in undefined behavior.
+ #[must_use]
+ pub const unsafe fn move_piece_backward_right_unchecked(self, value: usize) -> Self {
+ self.move_piece_backward_unchecked(value, 7)
+ }
+
+ /// Clears a space on the board. If the space is empty, then this function does nothing.
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The value of the space to clear
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ #[must_use]
+ pub const fn clear_piece(self, value: usize) -> Self {
+ let pieces = self.pieces & !(1 << value);
+ CheckersBitBoard::new(pieces, self.color, self.kings, self.turn)
+ }
+
+ /// Tries to jump the piece forward and to the left, without checking if it's a legal move.
+ /// If a piece already exists there, then it will be overwritten.
+ /// The space the piece jumps over is cleared
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The original location of the piece
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Moving from the left side of the board results in undefined behavior.
+ /// Moving from the top of the board results in undefined behavior
+ #[must_use]
+ // TODO test the edge cases of the below if statement
+ pub const unsafe fn jump_piece_forward_left_unchecked(self, value: usize) -> Self {
+ let is_king = self.king_at_unchecked(value);
+ let board = self
+ .move_piece_forward_unchecked(value, 14)
+ .clear_piece((value + 7) & 31);
+
+ const KING_MASK: u32 = 0b00100000100000100000000000001000;
+ if (is_king || (((1 << value) & KING_MASK) == 0))
+ && PossibleMoves::has_jumps_at(board.flip_turn(), (value + 14) & 31)
+ {
+ board.flip_turn().set_previous_turn((value + 14) & 31)
+ } else {
+ board
+ }
+ }
+
+ /// Tries to move the piece forward and to the right, without checking if it's a legal move.
+ /// If a piece already exists there, then it will be overwritten
+ /// The space the piece jumps over is cleared
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The original location of the piece
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Moving from the right side of the board results in undefined behavior.
+ /// Moving from the top of the board results in undefined behavior
+ #[must_use]
+ pub const unsafe fn jump_piece_forward_right_unchecked(self, value: usize) -> Self {
+ let is_king = self.king_at_unchecked(value);
+ let board = self
+ .move_piece_forward_unchecked(value, 2)
+ .clear_piece((value + 1) & 31);
+
+ const KING_MASK: u32 = 0b00100000100000100000000000001000;
+ if (is_king || (((1 << value) & KING_MASK) == 0))
+ && PossibleMoves::has_jumps_at(board.flip_turn(), (value + 2) & 31)
+ {
+ board.flip_turn().set_previous_turn((value + 2) & 31)
+ } else {
+ board
+ }
+ }
+
+ /// Tries to move the piece backward and to the left, without checking if it's a legal move.
+ /// If a piece already exists there, then it will be overwritten
+ /// The space the piece jumps over is cleared
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The original location of the piece
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Moving from the left side of the board results in undefined behavior.
+ /// Moving from the bottom of the board results in undefined behavior
+ #[must_use]
+ pub const unsafe fn jump_piece_backward_left_unchecked(self, value: usize) -> Self {
+ let is_king = self.king_at_unchecked(value);
+ let board = self
+ .move_piece_backward_unchecked(value, 2)
+ .clear_piece(value.wrapping_sub(1) & 31);
+
+ const KING_MASK: u32 = 0b00000100000100000100000100000000;
+ if (is_king || (((1 << value) & KING_MASK) == 0))
+ && PossibleMoves::has_jumps_at(board.flip_turn(), value.wrapping_sub(2) & 31)
+ {
+ board
+ .flip_turn()
+ .set_previous_turn((value.wrapping_sub(2)) & 31)
+ } else {
+ board
+ }
+ }
+
+ /// Tries to move the piece backward and to the right, without checking if it's a legal move.
+ /// If a piece already exists there, then it will be overwritten
+ /// The space the piece jumps over is cleared
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The original location of the piece
+ ///
+ /// # Panics
+ ///
+ /// Panics if `value` is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Moving from the right side of the board results in undefined behavior.
+ /// Moving from the bottom of the board results in undefined behavior
+ #[must_use]
+ pub const unsafe fn jump_piece_backward_right_unchecked(self, value: usize) -> Self {
+ let is_king = self.king_at_unchecked(value);
+ let board = self
+ .move_piece_backward_unchecked(value, 14)
+ .clear_piece(value.wrapping_sub(7) & 31);
+
+ const KING_MASK: u32 = 0b00000100000100000100000100000000;
+ if (is_king || (((1 << value) & KING_MASK) == 0))
+ && PossibleMoves::has_jumps_at(board.flip_turn(), value.wrapping_sub(14) & 31)
+ {
+ board
+ .flip_turn()
+ .set_previous_turn((value.wrapping_sub(14)) & 31)
+ } else {
+ board
+ }
+ }
+}
diff --git a/model/src/board/tests.rs b/model/src/board/tests.rs index 8c119dc..9c356ae 100644..100755 --- a/model/src/board/tests.rs +++ b/model/src/board/tests.rs @@ -1,557 +1,557 @@ -use std::collections::hash_map::DefaultHasher; - -use proptest::prelude::*; - -use super::*; - -proptest! { - #[test] - fn test_bitboard_new(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX) { - let board = CheckersBitBoard::new(p, c, k, PieceColor::Dark); - assert_eq!(p, board.pieces); - assert_eq!(c, board.color); - assert_eq!(k, board.kings); - } - - #[test] - fn test_bits_fns(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX) { - let board = CheckersBitBoard::new(p, c, k, PieceColor::Dark); - assert_eq!(p, board.pieces_bits()); - assert_eq!(c, board.color_bits()); - assert_eq!(k, board.king_bits()); - } - - #[test] - fn test_bitboard_hash(pieces in 0u32..=u32::MAX, color in 0u32..=u32::MAX, kings in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX) { - let board1 = CheckersBitBoard::new(pieces, color, kings, PieceColor::Dark); - let board2 = CheckersBitBoard::new(pieces, color, kings, PieceColor::Dark); - let mut hasher1 = DefaultHasher::new(); - let mut hasher2 = DefaultHasher::new(); - board1.hash(&mut hasher1); - board2.hash(&mut hasher2); - assert_eq!(hasher1.finish(), hasher2.finish()); - } - - #[test] - fn test_bitboard_eq_identical(pieces in 0u32..=u32::MAX, color in 0u32..u32::MAX, kings in 0u32..=u32::MAX) { - let board1 = CheckersBitBoard::new(pieces, color, kings, PieceColor::Dark); - let board2 = CheckersBitBoard::new(pieces, color, kings, PieceColor::Dark); - assert_eq!(board1, board2); - } - - #[test] - fn test_bitboard_eq_empty(c1 in 0u32..u32::MAX, k1 in 0u32..=u32::MAX, c2 in 0u32..u32::MAX, k2 in 0u32..=u32::MAX) { - let board1 = CheckersBitBoard::new(0, c1, k1, PieceColor::Dark); - let board2 = CheckersBitBoard::new(0, c2, k2, PieceColor::Dark); - assert_eq!(board1, board2); - } - - #[test] - fn test_piece_at(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, v in 0usize..32) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - - // just test for no crash - let _ = board.piece_at(v); - } - - #[test] - fn test_color_at_unchecked(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, v in 0usize..32) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - - // just test for no crash - unsafe {let _ = board.color_at_unchecked(v);} - } - - #[test] - fn test_king_at_unchecked(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, v in 0usize..32) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - unsafe {let _ = board.king_at_unchecked(v);} - } - - #[test] - fn test_color_at(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, v in 0usize..32) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - - // just testing for no crash - let _ = board.color_at(v); - } - - #[test] - fn test_king_at(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, v in 0usize..32) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - - // just testing for no crash - let _ = board.king_at(v); - } - - #[test] - fn test_move_piece_to(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, s in 0usize..32, e in 0usize..32) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - let _ = unsafe {board.move_piece_to_unchecked(s, e)}; - } - - #[test] - fn test_move_forward(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX, v in 0usize..32, a in 0usize..usize::MAX) { - if a <= usize::MAX - v { // so there's no overflow - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - let _ = unsafe {board.move_piece_forward_unchecked(v, a)}; - } - } - - #[test] - fn test_move_backward(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX, v in 0usize..32, a in 0usize..usize::MAX) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - let _ = unsafe {board.move_piece_backward_unchecked(v, a)}; - } - - #[test] - fn test_move_forward_left(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - if board.piece_at(0) { - let board2 = unsafe {board.move_piece_forward_left_unchecked(0)}; - assert_eq!(board2.color_at(7), board.color_at(0)); - assert_eq!(board2.king_at(7), board.king_at(0)); - } - } - - #[test] - fn test_move_forward_right(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - if board.piece_at(18) { - let board2 = unsafe {board.move_piece_forward_right_unchecked(18)}; - assert_eq!(board2.color_at(19), board.color_at(18)); - assert_eq!(board2.king_at(19), board.king_at(18)); - } - } - - #[test] - fn test_move_backward_left(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - if board.piece_at(25) { - let board2 = unsafe {board.move_piece_backward_left_unchecked(25)}; - assert_eq!(board2.color_at(24), board.color_at(25)); - assert_eq!(board2.king_at(24), board.king_at(25)); - } - } - - #[test] - fn test_move_backward_right(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - if board.piece_at(11) { - let board2 = unsafe {board.move_piece_backward_right_unchecked(11)}; - assert_eq!(board2.color_at(4), board.color_at(11)); - assert_eq!(board2.king_at(4), board.king_at(11)); - } - } - - #[test] - fn test_clear_piece(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX, v in 0usize..32) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - let board = board.clear_piece(v); - assert!(!board.piece_at(v)); - } - - #[test] - fn test_jump_forward_left(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - unsafe { - if board.piece_at(0) && board.piece_at(7) && !board.piece_at(14) && board.color_at_unchecked(0) != board.color_at_unchecked(7) { - let board2 = board.jump_piece_forward_left_unchecked(0); - assert!(!board2.piece_at(0)); - assert!(!board2.piece_at(7)); - assert!(board2.piece_at(14)); - assert_eq!(board2.color_at_unchecked(14), board.color_at_unchecked(0)); - assert_eq!(board2.king_at_unchecked(14), board.king_at_unchecked(0)); - } - } - } - - #[test] - fn test_jump_forward_right(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - unsafe { - if board.piece_at(18) && board.piece_at(19) && !board.piece_at(20) && board.color_at_unchecked(18) != board.color_at_unchecked(19) { - let board2 = board.jump_piece_forward_right_unchecked(18); - assert!(!board2.piece_at(18)); - assert!(!board2.piece_at(19)); - assert!(board2.piece_at(20)); - assert_eq!(board2.color_at_unchecked(20), board.color_at_unchecked(18)); - assert_eq!(board2.king_at_unchecked(20), board.king_at_unchecked(18)); - } - } - } - - #[test] - fn test_jump_backward_left(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - unsafe { - if board.piece_at(25) && board.piece_at(24) && !board.piece_at(23) && board.color_at_unchecked(25) != board.color_at_unchecked(24) { - let board2 = board.jump_piece_backward_left_unchecked(25); - assert!(!board2.piece_at(25)); - assert!(!board2.piece_at(24)); - assert!(board2.piece_at(23)); - assert_eq!(board2.color_at_unchecked(23), board.color_at_unchecked(25)); - assert_eq!(board2.king_at_unchecked(23), board.king_at_unchecked(25)); - } - } - } - - #[test] - fn test_jump_backward_right(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) { - let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark); - unsafe { - if board.piece_at(11) && board.piece_at(4) && !board.piece_at(29) && board.color_at_unchecked(11) != board.color_at_unchecked(4) { - let board2 = board.jump_piece_backward_right_unchecked(11); - assert!(!board2.piece_at(11)); - assert!(!board2.piece_at(4)); - assert!(board2.piece_at(29)); - assert_eq!(board2.color_at_unchecked(29), board.color_at_unchecked(11)); - assert_eq!(board2.king_at_unchecked(29), board.king_at_unchecked(11)); - } - } - } -} - -#[test] -fn test_piece_at_empty_board() { - let board = CheckersBitBoard::new(0, 0, 0, PieceColor::Dark); - - // There should be no piece in any space - for i in 0..32 { - assert!(!board.piece_at(i)) - } -} - -#[test] -fn test_piece_at_space_zero() { - let board = CheckersBitBoard::new(1, 0, 0, PieceColor::Dark); - assert!(board.piece_at(0)); // There should be a piece in space 0 - - // There should be no piece in any other square - for i in 1..32 { - assert!(!board.piece_at(i)) - } -} - -#[test] -fn test_color_at_unchecked_all_light() { - let board = CheckersBitBoard::new(0, 0, 0, PieceColor::Dark); - - // All squares should be light - for i in 0..32 { - assert_eq!(unsafe { board.color_at_unchecked(i) }, PieceColor::Light) - } -} - -#[test] -fn test_color_at_unchecked_all_dark() { - let board = CheckersBitBoard::new(0, u32::MAX, 0, PieceColor::Dark); - - // All squares should be dark - for i in 0..32 { - assert_eq!(unsafe { board.color_at_unchecked(i) }, PieceColor::Dark) - } -} - -#[test] -fn test_king_at_unchecked_all_kings() { - let board = CheckersBitBoard::new(0, 0, u32::MAX, PieceColor::Dark); - - // All squares should be kings - for i in 0..32 { - assert!(unsafe { board.king_at_unchecked(i) }) - } -} - -#[test] -fn test_king_at_unchecked_one_king() { - let board = CheckersBitBoard::new(0, 0, 1, PieceColor::Dark); - - assert!(unsafe { board.king_at_unchecked(0) }); - - // All other squares should be peasants - for i in 1..32 { - assert!(!unsafe { board.king_at_unchecked(i) }) - } -} - -#[test] -fn test_default_bitboard() { - let board = CheckersBitBoard::default(); - let exemptions = [2, 28, 22, 16, 27, 21, 15, 9]; - let black = [18, 12, 6, 0, 19, 13, 7, 1, 26, 20, 14, 8]; - - for i in 0..32 { - if !exemptions.contains(&i) { - assert!(board.piece_at(i)); - assert!(!unsafe { board.king_at_unchecked(i) }); - - if black.contains(&i) { - assert_eq!(unsafe { board.color_at_unchecked(i) }, PieceColor::Dark) - } else { - assert_eq!(unsafe { board.color_at_unchecked(i) }, PieceColor::Light) - } - } else { - assert!(!board.piece_at(i)) - } - } -} - -#[test] -fn test_bitboard_eq_default() { - let board1 = CheckersBitBoard::new( - 0b11100111100111100111110111111011, - 0b11110011110000110000110000111100, - 0, - PieceColor::Dark, - ); - let board2 = CheckersBitBoard::new( - 0b11100111100111100111110111111011, - 0b11110011110000110000110000111100, - 0, - PieceColor::Dark, - ); - assert_eq!(board1, board2); -} - -#[test] -fn test_bitboard_neq_color() { - let board1 = CheckersBitBoard::new( - 0b11100111100111100111110111111011, - 0b11110011110000110000110000111100, - 0, - PieceColor::Dark, - ); - let board2 = CheckersBitBoard::new( - 0b11100111100111100111110111111011, - 465413646, - 0, - PieceColor::Dark, - ); - assert_ne!(board1, board2); -} - -#[test] -fn test_bitboard_neq_kings() { - let board1 = CheckersBitBoard::new( - 0b11100111100111100111110111111011, - 0b11110011110000110000110000111100, - 0, - PieceColor::Dark, - ); - let board2 = CheckersBitBoard::new( - 0b11100111100111100111110111111011, - 0b11110011110000110000110000111100, - 465413646, - PieceColor::Dark, - ); - assert_ne!(board1, board2); -} - -#[test] -fn test_color_at_empty() { - let board = CheckersBitBoard::new(0, 0, 0, PieceColor::Dark); - - for i in 0..32 { - assert_eq!(board.color_at(i), None) - } -} - -#[test] -fn test_color_at_specified_empty_colors() { - let board = CheckersBitBoard::new(0, 0b01, 0, PieceColor::Dark); - - for i in 0..32 { - assert_eq!(board.color_at(i), None) - } -} - -#[test] -fn test_color_at_some_colors() { - let board = CheckersBitBoard::new(3, 0b01, 0, PieceColor::Dark); - - assert_eq!(board.color_at(0), Some(PieceColor::Dark)); - assert_eq!(board.color_at(1), Some(PieceColor::Light)); - - for i in 2..32 { - assert_eq!(board.color_at(i), None) - } -} - -#[test] -fn test_king_at_empty() { - let board = CheckersBitBoard::new(0, 0, 0, PieceColor::Dark); - - for i in 0..32 { - assert_eq!(board.king_at(i), None) - } -} - -#[test] -fn test_king_at_specified_empty_colors() { - let board = CheckersBitBoard::new(0, 0, 0b01, PieceColor::Dark); - - for i in 0..32 { - assert_eq!(board.king_at(i), None) - } -} - -#[test] -fn test_king_at_some_colors() { - let board = CheckersBitBoard::new(3, 0, 0b01, PieceColor::Dark); - - assert_eq!(board.king_at(0), Some(true)); - assert_eq!(board.king_at(1), Some(false)); - - for i in 2..32 { - assert_eq!(board.king_at(i), None) - } -} - -#[test] -fn test_move_piece_to_default_board() { - let board = CheckersBitBoard::default(); - let board = unsafe { board.move_piece_to_unchecked(0, 5) }; - assert!(!board.piece_at(0)); - assert!(board.piece_at(5)); - assert_eq!(board.color_at(5).unwrap(), PieceColor::Dark); - assert!(board.king_at(5).unwrap()); - assert_eq!(board.turn, PieceColor::Light); -} - -#[test] -fn test_move_piece_forward_standard() { - let board = CheckersBitBoard::default(); - let board = unsafe { board.move_piece_forward_unchecked(14, 2) }; // go to 16 - assert!(!board.piece_at(14)); - assert!(board.piece_at(16)); - assert_eq!(board.color_at(16).unwrap(), PieceColor::Dark); - assert!(!board.king_at(16).unwrap()); - assert_eq!(board.turn, PieceColor::Light); -} - -#[test] -fn test_move_piece_forward_wrap() { - let board = CheckersBitBoard::default(); - let board = unsafe { board.move_piece_forward_unchecked(26, 8) }; // go to 9 - assert!(!board.piece_at(26)); - assert!(board.piece_at(2)); - assert_eq!(board.color_at(2).unwrap(), PieceColor::Dark); - assert!(!board.king_at(2).unwrap()); - assert_eq!(board.turn, PieceColor::Light); -} - -#[test] -fn test_move_piece_forward_left_to_king() { - let board = CheckersBitBoard::new(0b10000, 0b10000, 0, PieceColor::Dark); - let board = unsafe { board.move_piece_forward_left_unchecked(4) }; - assert!(board.piece_at(11)); - assert!(board.king_at(11).unwrap()); -} - -#[test] -fn test_move_piece_backward_left_to_king() { - let board = CheckersBitBoard::new(0b10, 0, 0, PieceColor::Dark); - let board = unsafe { board.move_piece_backward_left_unchecked(1) }; - assert!(board.piece_at(0)); - assert!(board.king_at(0).unwrap()); -} - -#[test] -fn test_move_piece_backward_standard() { - let board = CheckersBitBoard::default().flip_turn(); - let board = unsafe { board.move_piece_backward_unchecked(29, 14) }; // go to 15 - assert!(!board.piece_at(29)); - assert!(board.piece_at(15)); - assert_eq!(board.color_at(15).unwrap(), PieceColor::Light); - assert!(!board.king_at(15).unwrap()); - assert_eq!(board.turn, PieceColor::Dark); - assert_eq!(board.previous_turn, PieceColor::Light); -} - -#[test] -fn test_move_piece_backward_wrap() { - let board = CheckersBitBoard::default(); - let board = unsafe { board.move_piece_backward_unchecked(0, 4) }; // go to 28 - assert!(!board.piece_at(0)); - assert!(board.piece_at(28)); - assert_eq!(board.color_at(28).unwrap(), PieceColor::Dark); - assert!(!board.king_at(28).unwrap()); - assert_eq!(board.turn, PieceColor::Light); - assert_eq!(board.previous_turn, PieceColor::Dark); -} - -#[test] -// the specific tests have special values, and are different from the property tests -fn test_jump_forward_left_specific() { - let board = CheckersBitBoard::new(0b10000001, 1, 0, PieceColor::Dark); - - let board2 = unsafe { board.jump_piece_forward_left_unchecked(0) }; - assert!(!board2.piece_at(0)); - assert!(!board2.piece_at(7)); - assert!(board2.piece_at(14)); - assert_eq!(board2.color_at(14).unwrap(), board.color_at(0).unwrap()); - assert_eq!(board2.king_at(14).unwrap(), board.king_at(0).unwrap()); - assert_eq!(board2.turn, PieceColor::Light); -} - -#[test] -fn test_jump_forward_right_specific() { - let board = CheckersBitBoard::new( - 0b11000000000000000000, - 0b10000000000000000000, - 0, - PieceColor::Dark, - ); - - let board2 = unsafe { board.jump_piece_forward_right_unchecked(18) }; - assert!(!board2.piece_at(18)); - assert!(!board2.piece_at(19)); - assert!(board2.piece_at(20)); - assert_eq!(board2.color_at(20).unwrap(), board.color_at(18).unwrap()); - assert_eq!(board2.king_at(20).unwrap(), board.king_at(18).unwrap()); - assert_eq!(board2.turn, PieceColor::Light); -} - -#[test] -fn test_jump_backward_left_specific() { - let board = CheckersBitBoard::new( - 0b110000000000000000000000000, - 0b100000000000000000000000000, - 0, - PieceColor::Dark, - ); - - let board2 = unsafe { board.jump_piece_backward_left_unchecked(25) }; - assert!(!board2.piece_at(25)); - assert!(!board2.piece_at(24)); - assert!(board2.piece_at(23)); - assert_eq!(board2.color_at(23).unwrap(), board.color_at(25).unwrap()); - assert_eq!(board2.king_at(23).unwrap(), board.king_at(25).unwrap()); - assert_eq!(board2.turn, PieceColor::Light); -} - -#[test] -fn test_jump_backward_right_specific() { - let board = CheckersBitBoard::new(0b100000010000, 0b10000, 0, PieceColor::Dark); - - let board2 = unsafe { board.jump_piece_backward_right_unchecked(11) }; - assert!(!board2.piece_at(11)); - assert!(!board2.piece_at(4)); - assert!(board2.piece_at(29)); - assert_eq!(board2.color_at(29).unwrap(), board.color_at(11).unwrap()); - assert_eq!(board2.king_at(29).unwrap(), board.king_at(11).unwrap()); - assert_eq!(board2.turn, PieceColor::Light); -} - -#[test] -fn test_send() { - fn assert_send<T: Send>() {} - assert_send::<CheckersBitBoard>(); -} - -#[test] -fn test_sync() { - fn assert_sync<T: Sync>() {} - assert_sync::<CheckersBitBoard>(); -} +use std::collections::hash_map::DefaultHasher;
+
+use proptest::prelude::*;
+
+use super::*;
+
+proptest! {
+ #[test]
+ fn test_bitboard_new(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX) {
+ let board = CheckersBitBoard::new(p, c, k, PieceColor::Dark);
+ assert_eq!(p, board.pieces);
+ assert_eq!(c, board.color);
+ assert_eq!(k, board.kings);
+ }
+
+ #[test]
+ fn test_bits_fns(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX) {
+ let board = CheckersBitBoard::new(p, c, k, PieceColor::Dark);
+ assert_eq!(p, board.pieces_bits());
+ assert_eq!(c, board.color_bits());
+ assert_eq!(k, board.king_bits());
+ }
+
+ #[test]
+ fn test_bitboard_hash(pieces in 0u32..=u32::MAX, color in 0u32..=u32::MAX, kings in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX) {
+ let board1 = CheckersBitBoard::new(pieces, color, kings, PieceColor::Dark);
+ let board2 = CheckersBitBoard::new(pieces, color, kings, PieceColor::Dark);
+ let mut hasher1 = DefaultHasher::new();
+ let mut hasher2 = DefaultHasher::new();
+ board1.hash(&mut hasher1);
+ board2.hash(&mut hasher2);
+ assert_eq!(hasher1.finish(), hasher2.finish());
+ }
+
+ #[test]
+ fn test_bitboard_eq_identical(pieces in 0u32..=u32::MAX, color in 0u32..u32::MAX, kings in 0u32..=u32::MAX) {
+ let board1 = CheckersBitBoard::new(pieces, color, kings, PieceColor::Dark);
+ let board2 = CheckersBitBoard::new(pieces, color, kings, PieceColor::Dark);
+ assert_eq!(board1, board2);
+ }
+
+ #[test]
+ fn test_bitboard_eq_empty(c1 in 0u32..u32::MAX, k1 in 0u32..=u32::MAX, c2 in 0u32..u32::MAX, k2 in 0u32..=u32::MAX) {
+ let board1 = CheckersBitBoard::new(0, c1, k1, PieceColor::Dark);
+ let board2 = CheckersBitBoard::new(0, c2, k2, PieceColor::Dark);
+ assert_eq!(board1, board2);
+ }
+
+ #[test]
+ fn test_piece_at(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, v in 0usize..32) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+
+ // just test for no crash
+ let _ = board.piece_at(v);
+ }
+
+ #[test]
+ fn test_color_at_unchecked(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, v in 0usize..32) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+
+ // just test for no crash
+ unsafe {let _ = board.color_at_unchecked(v);}
+ }
+
+ #[test]
+ fn test_king_at_unchecked(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, v in 0usize..32) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ unsafe {let _ = board.king_at_unchecked(v);}
+ }
+
+ #[test]
+ fn test_color_at(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, v in 0usize..32) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+
+ // just testing for no crash
+ let _ = board.color_at(v);
+ }
+
+ #[test]
+ fn test_king_at(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, v in 0usize..32) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+
+ // just testing for no crash
+ let _ = board.king_at(v);
+ }
+
+ #[test]
+ fn test_move_piece_to(p in 0u32..=u32::MAX, c in 0u32..=u32::MAX, k in 0u32..=u32::MAX, s in 0usize..32, e in 0usize..32) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ let _ = unsafe {board.move_piece_to_unchecked(s, e)};
+ }
+
+ #[test]
+ fn test_move_forward(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX, v in 0usize..32, a in 0usize..usize::MAX) {
+ if a <= usize::MAX - v { // so there's no overflow
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ let _ = unsafe {board.move_piece_forward_unchecked(v, a)};
+ }
+ }
+
+ #[test]
+ fn test_move_backward(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX, v in 0usize..32, a in 0usize..usize::MAX) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ let _ = unsafe {board.move_piece_backward_unchecked(v, a)};
+ }
+
+ #[test]
+ fn test_move_forward_left(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ if board.piece_at(0) {
+ let board2 = unsafe {board.move_piece_forward_left_unchecked(0)};
+ assert_eq!(board2.color_at(7), board.color_at(0));
+ assert_eq!(board2.king_at(7), board.king_at(0));
+ }
+ }
+
+ #[test]
+ fn test_move_forward_right(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ if board.piece_at(18) {
+ let board2 = unsafe {board.move_piece_forward_right_unchecked(18)};
+ assert_eq!(board2.color_at(19), board.color_at(18));
+ assert_eq!(board2.king_at(19), board.king_at(18));
+ }
+ }
+
+ #[test]
+ fn test_move_backward_left(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ if board.piece_at(25) {
+ let board2 = unsafe {board.move_piece_backward_left_unchecked(25)};
+ assert_eq!(board2.color_at(24), board.color_at(25));
+ assert_eq!(board2.king_at(24), board.king_at(25));
+ }
+ }
+
+ #[test]
+ fn test_move_backward_right(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ if board.piece_at(11) {
+ let board2 = unsafe {board.move_piece_backward_right_unchecked(11)};
+ assert_eq!(board2.color_at(4), board.color_at(11));
+ assert_eq!(board2.king_at(4), board.king_at(11));
+ }
+ }
+
+ #[test]
+ fn test_clear_piece(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX, v in 0usize..32) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ let board = board.clear_piece(v);
+ assert!(!board.piece_at(v));
+ }
+
+ #[test]
+ fn test_jump_forward_left(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ unsafe {
+ if board.piece_at(0) && board.piece_at(7) && !board.piece_at(14) && board.color_at_unchecked(0) != board.color_at_unchecked(7) {
+ let board2 = board.jump_piece_forward_left_unchecked(0);
+ assert!(!board2.piece_at(0));
+ assert!(!board2.piece_at(7));
+ assert!(board2.piece_at(14));
+ assert_eq!(board2.color_at_unchecked(14), board.color_at_unchecked(0));
+ assert_eq!(board2.king_at_unchecked(14), board.king_at_unchecked(0));
+ }
+ }
+ }
+
+ #[test]
+ fn test_jump_forward_right(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ unsafe {
+ if board.piece_at(18) && board.piece_at(19) && !board.piece_at(20) && board.color_at_unchecked(18) != board.color_at_unchecked(19) {
+ let board2 = board.jump_piece_forward_right_unchecked(18);
+ assert!(!board2.piece_at(18));
+ assert!(!board2.piece_at(19));
+ assert!(board2.piece_at(20));
+ assert_eq!(board2.color_at_unchecked(20), board.color_at_unchecked(18));
+ assert_eq!(board2.king_at_unchecked(20), board.king_at_unchecked(18));
+ }
+ }
+ }
+
+ #[test]
+ fn test_jump_backward_left(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ unsafe {
+ if board.piece_at(25) && board.piece_at(24) && !board.piece_at(23) && board.color_at_unchecked(25) != board.color_at_unchecked(24) {
+ let board2 = board.jump_piece_backward_left_unchecked(25);
+ assert!(!board2.piece_at(25));
+ assert!(!board2.piece_at(24));
+ assert!(board2.piece_at(23));
+ assert_eq!(board2.color_at_unchecked(23), board.color_at_unchecked(25));
+ assert_eq!(board2.king_at_unchecked(23), board.king_at_unchecked(25));
+ }
+ }
+ }
+
+ #[test]
+ fn test_jump_backward_right(p in 0..u32::MAX, c in 0..u32::MAX, k in 0..u32::MAX) {
+ let board = CheckersBitBoard ::new(p, c, k, PieceColor::Dark);
+ unsafe {
+ if board.piece_at(11) && board.piece_at(4) && !board.piece_at(29) && board.color_at_unchecked(11) != board.color_at_unchecked(4) {
+ let board2 = board.jump_piece_backward_right_unchecked(11);
+ assert!(!board2.piece_at(11));
+ assert!(!board2.piece_at(4));
+ assert!(board2.piece_at(29));
+ assert_eq!(board2.color_at_unchecked(29), board.color_at_unchecked(11));
+ assert_eq!(board2.king_at_unchecked(29), board.king_at_unchecked(11));
+ }
+ }
+ }
+}
+
+#[test]
+fn test_piece_at_empty_board() {
+ let board = CheckersBitBoard::new(0, 0, 0, PieceColor::Dark);
+
+ // There should be no piece in any space
+ for i in 0..32 {
+ assert!(!board.piece_at(i))
+ }
+}
+
+#[test]
+fn test_piece_at_space_zero() {
+ let board = CheckersBitBoard::new(1, 0, 0, PieceColor::Dark);
+ assert!(board.piece_at(0)); // There should be a piece in space 0
+
+ // There should be no piece in any other square
+ for i in 1..32 {
+ assert!(!board.piece_at(i))
+ }
+}
+
+#[test]
+fn test_color_at_unchecked_all_light() {
+ let board = CheckersBitBoard::new(0, 0, 0, PieceColor::Dark);
+
+ // All squares should be light
+ for i in 0..32 {
+ assert_eq!(unsafe { board.color_at_unchecked(i) }, PieceColor::Light)
+ }
+}
+
+#[test]
+fn test_color_at_unchecked_all_dark() {
+ let board = CheckersBitBoard::new(0, u32::MAX, 0, PieceColor::Dark);
+
+ // All squares should be dark
+ for i in 0..32 {
+ assert_eq!(unsafe { board.color_at_unchecked(i) }, PieceColor::Dark)
+ }
+}
+
+#[test]
+fn test_king_at_unchecked_all_kings() {
+ let board = CheckersBitBoard::new(0, 0, u32::MAX, PieceColor::Dark);
+
+ // All squares should be kings
+ for i in 0..32 {
+ assert!(unsafe { board.king_at_unchecked(i) })
+ }
+}
+
+#[test]
+fn test_king_at_unchecked_one_king() {
+ let board = CheckersBitBoard::new(0, 0, 1, PieceColor::Dark);
+
+ assert!(unsafe { board.king_at_unchecked(0) });
+
+ // All other squares should be peasants
+ for i in 1..32 {
+ assert!(!unsafe { board.king_at_unchecked(i) })
+ }
+}
+
+#[test]
+fn test_default_bitboard() {
+ let board = CheckersBitBoard::default();
+ let exemptions = [2, 28, 22, 16, 27, 21, 15, 9];
+ let black = [18, 12, 6, 0, 19, 13, 7, 1, 26, 20, 14, 8];
+
+ for i in 0..32 {
+ if !exemptions.contains(&i) {
+ assert!(board.piece_at(i));
+ assert!(!unsafe { board.king_at_unchecked(i) });
+
+ if black.contains(&i) {
+ assert_eq!(unsafe { board.color_at_unchecked(i) }, PieceColor::Dark)
+ } else {
+ assert_eq!(unsafe { board.color_at_unchecked(i) }, PieceColor::Light)
+ }
+ } else {
+ assert!(!board.piece_at(i))
+ }
+ }
+}
+
+#[test]
+fn test_bitboard_eq_default() {
+ let board1 = CheckersBitBoard::new(
+ 0b11100111100111100111110111111011,
+ 0b11110011110000110000110000111100,
+ 0,
+ PieceColor::Dark,
+ );
+ let board2 = CheckersBitBoard::new(
+ 0b11100111100111100111110111111011,
+ 0b11110011110000110000110000111100,
+ 0,
+ PieceColor::Dark,
+ );
+ assert_eq!(board1, board2);
+}
+
+#[test]
+fn test_bitboard_neq_color() {
+ let board1 = CheckersBitBoard::new(
+ 0b11100111100111100111110111111011,
+ 0b11110011110000110000110000111100,
+ 0,
+ PieceColor::Dark,
+ );
+ let board2 = CheckersBitBoard::new(
+ 0b11100111100111100111110111111011,
+ 465413646,
+ 0,
+ PieceColor::Dark,
+ );
+ assert_ne!(board1, board2);
+}
+
+#[test]
+fn test_bitboard_neq_kings() {
+ let board1 = CheckersBitBoard::new(
+ 0b11100111100111100111110111111011,
+ 0b11110011110000110000110000111100,
+ 0,
+ PieceColor::Dark,
+ );
+ let board2 = CheckersBitBoard::new(
+ 0b11100111100111100111110111111011,
+ 0b11110011110000110000110000111100,
+ 465413646,
+ PieceColor::Dark,
+ );
+ assert_ne!(board1, board2);
+}
+
+#[test]
+fn test_color_at_empty() {
+ let board = CheckersBitBoard::new(0, 0, 0, PieceColor::Dark);
+
+ for i in 0..32 {
+ assert_eq!(board.color_at(i), None)
+ }
+}
+
+#[test]
+fn test_color_at_specified_empty_colors() {
+ let board = CheckersBitBoard::new(0, 0b01, 0, PieceColor::Dark);
+
+ for i in 0..32 {
+ assert_eq!(board.color_at(i), None)
+ }
+}
+
+#[test]
+fn test_color_at_some_colors() {
+ let board = CheckersBitBoard::new(3, 0b01, 0, PieceColor::Dark);
+
+ assert_eq!(board.color_at(0), Some(PieceColor::Dark));
+ assert_eq!(board.color_at(1), Some(PieceColor::Light));
+
+ for i in 2..32 {
+ assert_eq!(board.color_at(i), None)
+ }
+}
+
+#[test]
+fn test_king_at_empty() {
+ let board = CheckersBitBoard::new(0, 0, 0, PieceColor::Dark);
+
+ for i in 0..32 {
+ assert_eq!(board.king_at(i), None)
+ }
+}
+
+#[test]
+fn test_king_at_specified_empty_colors() {
+ let board = CheckersBitBoard::new(0, 0, 0b01, PieceColor::Dark);
+
+ for i in 0..32 {
+ assert_eq!(board.king_at(i), None)
+ }
+}
+
+#[test]
+fn test_king_at_some_colors() {
+ let board = CheckersBitBoard::new(3, 0, 0b01, PieceColor::Dark);
+
+ assert_eq!(board.king_at(0), Some(true));
+ assert_eq!(board.king_at(1), Some(false));
+
+ for i in 2..32 {
+ assert_eq!(board.king_at(i), None)
+ }
+}
+
+#[test]
+fn test_move_piece_to_default_board() {
+ let board = CheckersBitBoard::default();
+ let board = unsafe { board.move_piece_to_unchecked(0, 5) };
+ assert!(!board.piece_at(0));
+ assert!(board.piece_at(5));
+ assert_eq!(board.color_at(5).unwrap(), PieceColor::Dark);
+ assert!(board.king_at(5).unwrap());
+ assert_eq!(board.turn, PieceColor::Light);
+}
+
+#[test]
+fn test_move_piece_forward_standard() {
+ let board = CheckersBitBoard::default();
+ let board = unsafe { board.move_piece_forward_unchecked(14, 2) }; // go to 16
+ assert!(!board.piece_at(14));
+ assert!(board.piece_at(16));
+ assert_eq!(board.color_at(16).unwrap(), PieceColor::Dark);
+ assert!(!board.king_at(16).unwrap());
+ assert_eq!(board.turn, PieceColor::Light);
+}
+
+#[test]
+fn test_move_piece_forward_wrap() {
+ let board = CheckersBitBoard::default();
+ let board = unsafe { board.move_piece_forward_unchecked(26, 8) }; // go to 9
+ assert!(!board.piece_at(26));
+ assert!(board.piece_at(2));
+ assert_eq!(board.color_at(2).unwrap(), PieceColor::Dark);
+ assert!(!board.king_at(2).unwrap());
+ assert_eq!(board.turn, PieceColor::Light);
+}
+
+#[test]
+fn test_move_piece_forward_left_to_king() {
+ let board = CheckersBitBoard::new(0b10000, 0b10000, 0, PieceColor::Dark);
+ let board = unsafe { board.move_piece_forward_left_unchecked(4) };
+ assert!(board.piece_at(11));
+ assert!(board.king_at(11).unwrap());
+}
+
+#[test]
+fn test_move_piece_backward_left_to_king() {
+ let board = CheckersBitBoard::new(0b10, 0, 0, PieceColor::Dark);
+ let board = unsafe { board.move_piece_backward_left_unchecked(1) };
+ assert!(board.piece_at(0));
+ assert!(board.king_at(0).unwrap());
+}
+
+#[test]
+fn test_move_piece_backward_standard() {
+ let board = CheckersBitBoard::default().flip_turn();
+ let board = unsafe { board.move_piece_backward_unchecked(29, 14) }; // go to 15
+ assert!(!board.piece_at(29));
+ assert!(board.piece_at(15));
+ assert_eq!(board.color_at(15).unwrap(), PieceColor::Light);
+ assert!(!board.king_at(15).unwrap());
+ assert_eq!(board.turn, PieceColor::Dark);
+ assert_eq!(board.previous_turn, PieceColor::Light);
+}
+
+#[test]
+fn test_move_piece_backward_wrap() {
+ let board = CheckersBitBoard::default();
+ let board = unsafe { board.move_piece_backward_unchecked(0, 4) }; // go to 28
+ assert!(!board.piece_at(0));
+ assert!(board.piece_at(28));
+ assert_eq!(board.color_at(28).unwrap(), PieceColor::Dark);
+ assert!(!board.king_at(28).unwrap());
+ assert_eq!(board.turn, PieceColor::Light);
+ assert_eq!(board.previous_turn, PieceColor::Dark);
+}
+
+#[test]
+// the specific tests have special values, and are different from the property tests
+fn test_jump_forward_left_specific() {
+ let board = CheckersBitBoard::new(0b10000001, 1, 0, PieceColor::Dark);
+
+ let board2 = unsafe { board.jump_piece_forward_left_unchecked(0) };
+ assert!(!board2.piece_at(0));
+ assert!(!board2.piece_at(7));
+ assert!(board2.piece_at(14));
+ assert_eq!(board2.color_at(14).unwrap(), board.color_at(0).unwrap());
+ assert_eq!(board2.king_at(14).unwrap(), board.king_at(0).unwrap());
+ assert_eq!(board2.turn, PieceColor::Light);
+}
+
+#[test]
+fn test_jump_forward_right_specific() {
+ let board = CheckersBitBoard::new(
+ 0b11000000000000000000,
+ 0b10000000000000000000,
+ 0,
+ PieceColor::Dark,
+ );
+
+ let board2 = unsafe { board.jump_piece_forward_right_unchecked(18) };
+ assert!(!board2.piece_at(18));
+ assert!(!board2.piece_at(19));
+ assert!(board2.piece_at(20));
+ assert_eq!(board2.color_at(20).unwrap(), board.color_at(18).unwrap());
+ assert_eq!(board2.king_at(20).unwrap(), board.king_at(18).unwrap());
+ assert_eq!(board2.turn, PieceColor::Light);
+}
+
+#[test]
+fn test_jump_backward_left_specific() {
+ let board = CheckersBitBoard::new(
+ 0b110000000000000000000000000,
+ 0b100000000000000000000000000,
+ 0,
+ PieceColor::Dark,
+ );
+
+ let board2 = unsafe { board.jump_piece_backward_left_unchecked(25) };
+ assert!(!board2.piece_at(25));
+ assert!(!board2.piece_at(24));
+ assert!(board2.piece_at(23));
+ assert_eq!(board2.color_at(23).unwrap(), board.color_at(25).unwrap());
+ assert_eq!(board2.king_at(23).unwrap(), board.king_at(25).unwrap());
+ assert_eq!(board2.turn, PieceColor::Light);
+}
+
+#[test]
+fn test_jump_backward_right_specific() {
+ let board = CheckersBitBoard::new(0b100000010000, 0b10000, 0, PieceColor::Dark);
+
+ let board2 = unsafe { board.jump_piece_backward_right_unchecked(11) };
+ assert!(!board2.piece_at(11));
+ assert!(!board2.piece_at(4));
+ assert!(board2.piece_at(29));
+ assert_eq!(board2.color_at(29).unwrap(), board.color_at(11).unwrap());
+ assert_eq!(board2.king_at(29).unwrap(), board.king_at(11).unwrap());
+ assert_eq!(board2.turn, PieceColor::Light);
+}
+
+#[test]
+fn test_send() {
+ fn assert_send<T: Send>() {}
+ assert_send::<CheckersBitBoard>();
+}
+
+#[test]
+fn test_sync() {
+ fn assert_sync<T: Sync>() {}
+ assert_sync::<CheckersBitBoard>();
+}
diff --git a/model/src/color.rs b/model/src/color.rs index 8a4d2a5..8a4d2a5 100644..100755 --- a/model/src/color.rs +++ b/model/src/color.rs diff --git a/model/src/coordinates.rs b/model/src/coordinates.rs index 0f45322..d16f900 100644..100755 --- a/model/src/coordinates.rs +++ b/model/src/coordinates.rs @@ -1,152 +1,152 @@ -use std::fmt::{Display, Formatter}; - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct SquareCoordinate { - rank: u8, - file: u8, -} - -impl SquareCoordinate { - pub fn new(rank: u8, file: u8) -> Self { - if rank > 8 { - panic!("A Square cannot have a rank greater than 8. Got {}", rank) - } else if file > 8 { - panic!("A Square cannot have a file greater than 8. Got {}", file) - } else { - Self { rank, file } - } - } - - pub fn from_normal_value(value: usize) -> Self { - static VALUE_COORDINATE_MAP: [SquareCoordinate; 32] = [ - SquareCoordinate { rank: 0, file: 6 }, - SquareCoordinate { rank: 0, file: 4 }, - SquareCoordinate { rank: 0, file: 2 }, - SquareCoordinate { rank: 0, file: 0 }, - SquareCoordinate { rank: 1, file: 7 }, - SquareCoordinate { rank: 1, file: 5 }, - SquareCoordinate { rank: 1, file: 3 }, - SquareCoordinate { rank: 1, file: 1 }, - SquareCoordinate { rank: 2, file: 6 }, - SquareCoordinate { rank: 2, file: 4 }, - SquareCoordinate { rank: 2, file: 2 }, - SquareCoordinate { rank: 2, file: 0 }, - SquareCoordinate { rank: 3, file: 7 }, - SquareCoordinate { rank: 3, file: 5 }, - SquareCoordinate { rank: 3, file: 3 }, - SquareCoordinate { rank: 3, file: 1 }, - SquareCoordinate { rank: 4, file: 6 }, - SquareCoordinate { rank: 4, file: 4 }, - SquareCoordinate { rank: 4, file: 2 }, - SquareCoordinate { rank: 4, file: 0 }, - SquareCoordinate { rank: 5, file: 7 }, - SquareCoordinate { rank: 5, file: 5 }, - SquareCoordinate { rank: 5, file: 3 }, - SquareCoordinate { rank: 5, file: 1 }, - SquareCoordinate { rank: 6, file: 6 }, - SquareCoordinate { rank: 6, file: 4 }, - SquareCoordinate { rank: 6, file: 2 }, - SquareCoordinate { rank: 6, file: 0 }, - SquareCoordinate { rank: 7, file: 7 }, - SquareCoordinate { rank: 7, file: 5 }, - SquareCoordinate { rank: 7, file: 3 }, - SquareCoordinate { rank: 7, file: 1 }, - ]; - - VALUE_COORDINATE_MAP[value] - } - - pub fn from_ampere_value(value: usize) -> Self { - static VALUE_COORDINATE_MAP: [SquareCoordinate; 32] = [ - SquareCoordinate { rank: 0, file: 6 }, - SquareCoordinate { rank: 1, file: 7 }, - SquareCoordinate { rank: 4, file: 0 }, - SquareCoordinate { rank: 5, file: 1 }, - SquareCoordinate { rank: 6, file: 2 }, - SquareCoordinate { rank: 7, file: 3 }, - SquareCoordinate { rank: 0, file: 4 }, - SquareCoordinate { rank: 1, file: 5 }, - SquareCoordinate { rank: 2, file: 6 }, - SquareCoordinate { rank: 3, file: 7 }, - SquareCoordinate { rank: 6, file: 0 }, - SquareCoordinate { rank: 7, file: 1 }, - SquareCoordinate { rank: 0, file: 2 }, - SquareCoordinate { rank: 1, file: 3 }, - SquareCoordinate { rank: 2, file: 4 }, - SquareCoordinate { rank: 3, file: 5 }, - SquareCoordinate { rank: 4, file: 6 }, - SquareCoordinate { rank: 5, file: 7 }, - SquareCoordinate { rank: 0, file: 0 }, - SquareCoordinate { rank: 1, file: 1 }, - SquareCoordinate { rank: 2, file: 2 }, - SquareCoordinate { rank: 3, file: 3 }, - SquareCoordinate { rank: 4, file: 4 }, - SquareCoordinate { rank: 5, file: 5 }, - SquareCoordinate { rank: 6, file: 6 }, - SquareCoordinate { rank: 7, file: 7 }, - SquareCoordinate { rank: 2, file: 0 }, - SquareCoordinate { rank: 3, file: 1 }, - SquareCoordinate { rank: 4, file: 2 }, - SquareCoordinate { rank: 5, file: 3 }, - SquareCoordinate { rank: 6, file: 4 }, - SquareCoordinate { rank: 7, file: 5 }, - ]; - - VALUE_COORDINATE_MAP[value] - } - - pub fn rank(self) -> u8 { - self.rank - } - - pub fn file(self) -> u8 { - self.file - } - - pub fn to_ampere_value(self) -> Option<usize> { - if self.rank % 2 == 0 { - if self.file % 2 == 0 { - Some(((18 - ((self.file / 2) * 6)) + ((self.rank / 2) * 8)) as usize % 32) - } else { - None - } - } else if self.file % 2 == 1 { - let column_value = match self.file { - 1 => 19, - 3 => 13, - 5 => 7, - 7 => 1, - _ => unreachable!(), - }; - let row_value = match self.rank { - 1 => 0, - 3 => 8, - 5 => 16, - 7 => 24, - _ => unreachable!(), - }; - Some((column_value + row_value) % 32) - } else { - None - } - } - - pub fn to_normal_value(self) -> Option<usize> { - if self.rank % 2 == 0 { - Some(self.rank as usize * 4 + self.file as usize % 4) - } else { - Some(self.rank as usize * 4 + self.file as usize % 4 + 1) - } - } -} - -impl Display for SquareCoordinate { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}{}", - char::from_u32((self.file + b'a') as u32).unwrap(), - self.rank + 1 - ) - } -} +use std::fmt::{Display, Formatter};
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub struct SquareCoordinate {
+ rank: u8,
+ file: u8,
+}
+
+impl SquareCoordinate {
+ pub fn new(rank: u8, file: u8) -> Self {
+ if rank > 8 {
+ panic!("A Square cannot have a rank greater than 8. Got {}", rank)
+ } else if file > 8 {
+ panic!("A Square cannot have a file greater than 8. Got {}", file)
+ } else {
+ Self { rank, file }
+ }
+ }
+
+ pub fn from_normal_value(value: usize) -> Self {
+ static VALUE_COORDINATE_MAP: [SquareCoordinate; 32] = [
+ SquareCoordinate { rank: 0, file: 6 },
+ SquareCoordinate { rank: 0, file: 4 },
+ SquareCoordinate { rank: 0, file: 2 },
+ SquareCoordinate { rank: 0, file: 0 },
+ SquareCoordinate { rank: 1, file: 7 },
+ SquareCoordinate { rank: 1, file: 5 },
+ SquareCoordinate { rank: 1, file: 3 },
+ SquareCoordinate { rank: 1, file: 1 },
+ SquareCoordinate { rank: 2, file: 6 },
+ SquareCoordinate { rank: 2, file: 4 },
+ SquareCoordinate { rank: 2, file: 2 },
+ SquareCoordinate { rank: 2, file: 0 },
+ SquareCoordinate { rank: 3, file: 7 },
+ SquareCoordinate { rank: 3, file: 5 },
+ SquareCoordinate { rank: 3, file: 3 },
+ SquareCoordinate { rank: 3, file: 1 },
+ SquareCoordinate { rank: 4, file: 6 },
+ SquareCoordinate { rank: 4, file: 4 },
+ SquareCoordinate { rank: 4, file: 2 },
+ SquareCoordinate { rank: 4, file: 0 },
+ SquareCoordinate { rank: 5, file: 7 },
+ SquareCoordinate { rank: 5, file: 5 },
+ SquareCoordinate { rank: 5, file: 3 },
+ SquareCoordinate { rank: 5, file: 1 },
+ SquareCoordinate { rank: 6, file: 6 },
+ SquareCoordinate { rank: 6, file: 4 },
+ SquareCoordinate { rank: 6, file: 2 },
+ SquareCoordinate { rank: 6, file: 0 },
+ SquareCoordinate { rank: 7, file: 7 },
+ SquareCoordinate { rank: 7, file: 5 },
+ SquareCoordinate { rank: 7, file: 3 },
+ SquareCoordinate { rank: 7, file: 1 },
+ ];
+
+ VALUE_COORDINATE_MAP[value]
+ }
+
+ pub fn from_ampere_value(value: usize) -> Self {
+ static VALUE_COORDINATE_MAP: [SquareCoordinate; 32] = [
+ SquareCoordinate { rank: 0, file: 6 },
+ SquareCoordinate { rank: 1, file: 7 },
+ SquareCoordinate { rank: 4, file: 0 },
+ SquareCoordinate { rank: 5, file: 1 },
+ SquareCoordinate { rank: 6, file: 2 },
+ SquareCoordinate { rank: 7, file: 3 },
+ SquareCoordinate { rank: 0, file: 4 },
+ SquareCoordinate { rank: 1, file: 5 },
+ SquareCoordinate { rank: 2, file: 6 },
+ SquareCoordinate { rank: 3, file: 7 },
+ SquareCoordinate { rank: 6, file: 0 },
+ SquareCoordinate { rank: 7, file: 1 },
+ SquareCoordinate { rank: 0, file: 2 },
+ SquareCoordinate { rank: 1, file: 3 },
+ SquareCoordinate { rank: 2, file: 4 },
+ SquareCoordinate { rank: 3, file: 5 },
+ SquareCoordinate { rank: 4, file: 6 },
+ SquareCoordinate { rank: 5, file: 7 },
+ SquareCoordinate { rank: 0, file: 0 },
+ SquareCoordinate { rank: 1, file: 1 },
+ SquareCoordinate { rank: 2, file: 2 },
+ SquareCoordinate { rank: 3, file: 3 },
+ SquareCoordinate { rank: 4, file: 4 },
+ SquareCoordinate { rank: 5, file: 5 },
+ SquareCoordinate { rank: 6, file: 6 },
+ SquareCoordinate { rank: 7, file: 7 },
+ SquareCoordinate { rank: 2, file: 0 },
+ SquareCoordinate { rank: 3, file: 1 },
+ SquareCoordinate { rank: 4, file: 2 },
+ SquareCoordinate { rank: 5, file: 3 },
+ SquareCoordinate { rank: 6, file: 4 },
+ SquareCoordinate { rank: 7, file: 5 },
+ ];
+
+ VALUE_COORDINATE_MAP[value]
+ }
+
+ pub fn rank(self) -> u8 {
+ self.rank
+ }
+
+ pub fn file(self) -> u8 {
+ self.file
+ }
+
+ pub fn to_ampere_value(self) -> Option<usize> {
+ if self.rank % 2 == 0 {
+ if self.file % 2 == 0 {
+ Some(((18 - ((self.file / 2) * 6)) + ((self.rank / 2) * 8)) as usize % 32)
+ } else {
+ None
+ }
+ } else if self.file % 2 == 1 {
+ let column_value = match self.file {
+ 1 => 19,
+ 3 => 13,
+ 5 => 7,
+ 7 => 1,
+ _ => unreachable!(),
+ };
+ let row_value = match self.rank {
+ 1 => 0,
+ 3 => 8,
+ 5 => 16,
+ 7 => 24,
+ _ => unreachable!(),
+ };
+ Some((column_value + row_value) % 32)
+ } else {
+ None
+ }
+ }
+
+ pub fn to_normal_value(self) -> Option<usize> {
+ if self.rank % 2 == 0 {
+ Some(self.rank as usize * 4 + self.file as usize % 4)
+ } else {
+ Some(self.rank as usize * 4 + self.file as usize % 4 + 1)
+ }
+ }
+}
+
+impl Display for SquareCoordinate {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}{}",
+ char::from_u32((self.file + b'a') as u32).unwrap(),
+ self.rank + 1
+ )
+ }
+}
diff --git a/model/src/lib.rs b/model/src/lib.rs index b3d8007..76a7419 100644..100755 --- a/model/src/lib.rs +++ b/model/src/lib.rs @@ -1,13 +1,13 @@ -mod board; -mod color; -mod coordinates; -mod moves; -mod piece; -mod possible_moves; - -pub use board::CheckersBitBoard; -pub use color::PieceColor; -pub use coordinates::SquareCoordinate; -pub use moves::{Move, MoveDirection}; -pub use piece::Piece; -pub use possible_moves::PossibleMoves; +mod board;
+mod color;
+mod coordinates;
+mod moves;
+mod piece;
+mod possible_moves;
+
+pub use board::CheckersBitBoard;
+pub use color::PieceColor;
+pub use coordinates::SquareCoordinate;
+pub use moves::{Move, MoveDirection};
+pub use piece::Piece;
+pub use possible_moves::PossibleMoves;
diff --git a/model/src/moves.rs b/model/src/moves.rs index c840e8f..c6dd060 100644..100755 --- a/model/src/moves.rs +++ b/model/src/moves.rs @@ -1,295 +1,295 @@ -use crate::{CheckersBitBoard, SquareCoordinate}; -use std::fmt::{Display, Formatter}; - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -#[repr(C)] -pub enum MoveDirection { - ForwardLeft = 0, - ForwardRight = 1, - BackwardLeft = 2, - BackwardRight = 3, -} - -/// A checkers move -// This is stored as a single byte. The first five bits represent the starting -// position, the next two bits represent the direction, and the last bit -// represents whether or not the move is a jump. -// -// starting position direction jump -// |--------------------|--------|----| -// 5 2 1 -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct Move(u8); - -impl Move { - /// Create a new move - /// - /// # Arguments - /// - /// * `start` - The location of the piece that should move - /// * `direction` - The direction the piece should move in - /// * `jump` - Whether or not the piece should jump - pub const fn new(start: usize, direction: MoveDirection, jump: bool) -> Self { - Self(((start as u8) << 3) | ((direction as u8) << 1) | jump as u8) - } - - /// The stating position of the move - pub const fn start(self) -> u32 { - ((self.0 >> 3) & 0b11111) as u32 - } - - /// The direction the move goes in - pub const fn direction(self) -> MoveDirection { - match (self.0 >> 1) & 0b11 { - 0 => MoveDirection::ForwardLeft, - 1 => MoveDirection::ForwardRight, - 2 => MoveDirection::BackwardLeft, - 3 => MoveDirection::BackwardRight, - _ => unreachable!(), - } - } - - /// Returns `true` if the move is a jump - pub const fn is_jump(self) -> bool { - (self.0 & 1) == 1 - } - - /// Calculates the value of the end position of the move - pub const fn end_position(self) -> usize { - let dest = match self.is_jump() { - false => match self.direction() { - MoveDirection::ForwardLeft => (self.start() + 7) % 32, - MoveDirection::ForwardRight => (self.start() + 1) % 32, - MoveDirection::BackwardLeft => self.start().wrapping_sub(1) % 32, - MoveDirection::BackwardRight => self.start().wrapping_sub(7) % 32, - }, - true => match self.direction() { - MoveDirection::ForwardLeft => (self.start() + 14) % 32, - MoveDirection::ForwardRight => (self.start() + 2) % 32, - MoveDirection::BackwardLeft => self.start().wrapping_sub(2) % 32, - MoveDirection::BackwardRight => self.start().wrapping_sub(14) % 32, - }, - }; - dest as usize - } - - /// Calculates the value of the position that was jumped over - /// - /// # Safety - /// - /// The result of this function is undefined if the move isn't a jump - pub const unsafe fn jump_position(self) -> usize { - let pos = match self.direction() { - MoveDirection::ForwardLeft => (self.start() + 7) % 32, - MoveDirection::ForwardRight => (self.start() + 1) % 32, - MoveDirection::BackwardLeft => self.start().wrapping_sub(1) % 32, - MoveDirection::BackwardRight => self.start().wrapping_sub(7) % 32, - }; - pos as usize - } - - /// Apply the move to a board. This does not mutate the original board, - /// but instead returns a new one. - /// - /// # Arguments - /// - /// * `board` - The board to apply the move to - /// - /// # Panics - /// - /// Panics if the starting position of this move is greater than or equal to 32 - /// - /// # Safety - /// - /// Applying an illegal move to the board is undefined behavior. - /// This functions results in undefined behavior if: - /// * The piece moves in a direction which would move it outside of the board - /// * The starting position of this move doesn't contain a piece - /// * The end position already contains a piece - /// * A jump occurs where jumps are not allowed - /// * A move is not a jump even though jumps are available - pub const unsafe fn apply_to(self, board: CheckersBitBoard) -> CheckersBitBoard { - match self.is_jump() { - false => match self.direction() { - MoveDirection::ForwardLeft => { - board.move_piece_forward_left_unchecked(self.start() as usize) - } - MoveDirection::ForwardRight => { - board.move_piece_forward_right_unchecked(self.start() as usize) - } - MoveDirection::BackwardLeft => { - board.move_piece_backward_left_unchecked(self.start() as usize) - } - MoveDirection::BackwardRight => { - board.move_piece_backward_right_unchecked(self.start() as usize) - } - }, - true => match self.direction() { - MoveDirection::ForwardLeft => { - board.jump_piece_forward_left_unchecked(self.start() as usize) - } - MoveDirection::ForwardRight => { - board.jump_piece_forward_right_unchecked(self.start() as usize) - } - MoveDirection::BackwardLeft => { - board.jump_piece_backward_left_unchecked(self.start() as usize) - } - MoveDirection::BackwardRight => { - board.jump_piece_backward_right_unchecked(self.start() as usize) - } - }, - } - } -} - -impl Display for Move { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let Some(start) = - SquareCoordinate::from_ampere_value(self.start() as usize).to_normal_value() - else { - return Err(std::fmt::Error); - }; - - let separator = if self.is_jump() { "x" } else { "-" }; - - let Some(end) = SquareCoordinate::from_ampere_value(self.end_position()).to_normal_value() - else { - return Err(std::fmt::Error); - }; - - write!(f, "{start}{separator}{end}") - } -} - -#[cfg(test)] -mod tests { - - use super::*; - use proptest::prelude::*; - - proptest! { - #[test] - fn new(start in 0usize..32, jump in proptest::bool::ANY) { - let direction = MoveDirection::ForwardLeft; - let move_test = Move::new(start, direction, jump); - assert_eq!(move_test.start() as usize, start); - assert_eq!(move_test.direction(), direction); - assert_eq!(move_test.is_jump(), jump); - - let direction = MoveDirection::ForwardRight; - let move_test = Move::new(start, direction, jump); - assert_eq!(move_test.start() as usize, start); - assert_eq!(move_test.direction(), direction); - assert_eq!(move_test.is_jump(), jump); - - let direction = MoveDirection::BackwardLeft; - let move_test = Move::new(start, direction, jump); - assert_eq!(move_test.start() as usize, start); - assert_eq!(move_test.direction(), direction); - assert_eq!(move_test.is_jump(), jump); - - let direction = MoveDirection::BackwardRight; - let move_test = Move::new(start, direction, jump); - assert_eq!(move_test.start() as usize, start); - assert_eq!(move_test.direction(), direction); - assert_eq!(move_test.is_jump(), jump); - } - - #[test] - fn start(start in 0usize..32, jump in proptest::bool::ANY) { - let direction = MoveDirection::ForwardLeft; - let move_test = Move::new(start, direction, jump); - assert_eq!(move_test.start(), start as u32); - } - - #[test] - fn direction(start in 0usize..32, jump in proptest::bool::ANY) { - let direction = MoveDirection::ForwardLeft; - let move_test = Move::new(start, direction, jump); - assert_eq!(move_test.direction(), direction); - - let direction = MoveDirection::ForwardRight; - let move_test = Move::new(start, direction, jump); - assert_eq!(move_test.direction(), direction); - - let direction = MoveDirection::BackwardLeft; - let move_test = Move::new(start, direction, jump); - assert_eq!(move_test.direction(), direction); - - let direction = MoveDirection::BackwardRight; - let move_test = Move::new(start, direction, jump); - assert_eq!(move_test.direction(), direction); - } - - #[test] - fn is_jump(start in 0usize..32, jump in proptest::bool::ANY) { - let direction = MoveDirection::ForwardLeft; - let move_test = Move::new(start, direction, jump); - assert_eq!(move_test.is_jump(), jump); - } - } - - #[test] - fn end_position_forward_left_slide() { - let direction = MoveDirection::ForwardLeft; - let start = 8; - let move_test = Move::new(start, direction, false); - assert_eq!(move_test.end_position(), 15); - } - - #[test] - fn end_position_forward_right_slide() { - let direction = MoveDirection::ForwardRight; - let start = 26; - let move_test = Move::new(start, direction, false); - assert_eq!(move_test.end_position(), 27); - } - - #[test] - fn end_position_backward_right_slide() { - let direction = MoveDirection::BackwardRight; - let start = 2; - let move_test = Move::new(start, direction, false); - assert_eq!(move_test.end_position(), 27); - } - - #[test] - fn end_position_backward_left_slide() { - let direction = MoveDirection::BackwardLeft; - let start = 16; - let move_test = Move::new(start, direction, false); - assert_eq!(move_test.end_position(), 15); - } - - #[test] - fn end_position_forward_left_jump() { - let direction = MoveDirection::ForwardLeft; - let start = 8; - let move_test = Move::new(start, direction, true); - assert_eq!(move_test.end_position(), 22); - } - - #[test] - fn end_position_forward_right_jump() { - let direction = MoveDirection::ForwardRight; - let start = 26; - let move_test = Move::new(start, direction, true); - assert_eq!(move_test.end_position(), 28); - } - - #[test] - fn end_position_backward_right_jump() { - let direction = MoveDirection::BackwardRight; - let start = 2; - let move_test = Move::new(start, direction, true); - assert_eq!(move_test.end_position(), 20); - } - - #[test] - fn end_position_backward_left_jump() { - let direction = MoveDirection::BackwardLeft; - let start = 16; - let move_test = Move::new(start, direction, true); - assert_eq!(move_test.end_position(), 14); - } -} +use crate::{CheckersBitBoard, SquareCoordinate};
+use std::fmt::{Display, Formatter};
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+#[repr(C)]
+pub enum MoveDirection {
+ ForwardLeft = 0,
+ ForwardRight = 1,
+ BackwardLeft = 2,
+ BackwardRight = 3,
+}
+
+/// A checkers move
+// This is stored as a single byte. The first five bits represent the starting
+// position, the next two bits represent the direction, and the last bit
+// represents whether or not the move is a jump.
+//
+// starting position direction jump
+// |--------------------|--------|----|
+// 5 2 1
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct Move(u8);
+
+impl Move {
+ /// Create a new move
+ ///
+ /// # Arguments
+ ///
+ /// * `start` - The location of the piece that should move
+ /// * `direction` - The direction the piece should move in
+ /// * `jump` - Whether or not the piece should jump
+ pub const fn new(start: usize, direction: MoveDirection, jump: bool) -> Self {
+ Self(((start as u8) << 3) | ((direction as u8) << 1) | jump as u8)
+ }
+
+ /// The stating position of the move
+ pub const fn start(self) -> u32 {
+ ((self.0 >> 3) & 0b11111) as u32
+ }
+
+ /// The direction the move goes in
+ pub const fn direction(self) -> MoveDirection {
+ match (self.0 >> 1) & 0b11 {
+ 0 => MoveDirection::ForwardLeft,
+ 1 => MoveDirection::ForwardRight,
+ 2 => MoveDirection::BackwardLeft,
+ 3 => MoveDirection::BackwardRight,
+ _ => unreachable!(),
+ }
+ }
+
+ /// Returns `true` if the move is a jump
+ pub const fn is_jump(self) -> bool {
+ (self.0 & 1) == 1
+ }
+
+ /// Calculates the value of the end position of the move
+ pub const fn end_position(self) -> usize {
+ let dest = match self.is_jump() {
+ false => match self.direction() {
+ MoveDirection::ForwardLeft => (self.start() + 7) % 32,
+ MoveDirection::ForwardRight => (self.start() + 1) % 32,
+ MoveDirection::BackwardLeft => self.start().wrapping_sub(1) % 32,
+ MoveDirection::BackwardRight => self.start().wrapping_sub(7) % 32,
+ },
+ true => match self.direction() {
+ MoveDirection::ForwardLeft => (self.start() + 14) % 32,
+ MoveDirection::ForwardRight => (self.start() + 2) % 32,
+ MoveDirection::BackwardLeft => self.start().wrapping_sub(2) % 32,
+ MoveDirection::BackwardRight => self.start().wrapping_sub(14) % 32,
+ },
+ };
+ dest as usize
+ }
+
+ /// Calculates the value of the position that was jumped over
+ ///
+ /// # Safety
+ ///
+ /// The result of this function is undefined if the move isn't a jump
+ pub const unsafe fn jump_position(self) -> usize {
+ let pos = match self.direction() {
+ MoveDirection::ForwardLeft => (self.start() + 7) % 32,
+ MoveDirection::ForwardRight => (self.start() + 1) % 32,
+ MoveDirection::BackwardLeft => self.start().wrapping_sub(1) % 32,
+ MoveDirection::BackwardRight => self.start().wrapping_sub(7) % 32,
+ };
+ pos as usize
+ }
+
+ /// Apply the move to a board. This does not mutate the original board,
+ /// but instead returns a new one.
+ ///
+ /// # Arguments
+ ///
+ /// * `board` - The board to apply the move to
+ ///
+ /// # Panics
+ ///
+ /// Panics if the starting position of this move is greater than or equal to 32
+ ///
+ /// # Safety
+ ///
+ /// Applying an illegal move to the board is undefined behavior.
+ /// This functions results in undefined behavior if:
+ /// * The piece moves in a direction which would move it outside of the board
+ /// * The starting position of this move doesn't contain a piece
+ /// * The end position already contains a piece
+ /// * A jump occurs where jumps are not allowed
+ /// * A move is not a jump even though jumps are available
+ pub const unsafe fn apply_to(self, board: CheckersBitBoard) -> CheckersBitBoard {
+ match self.is_jump() {
+ false => match self.direction() {
+ MoveDirection::ForwardLeft => {
+ board.move_piece_forward_left_unchecked(self.start() as usize)
+ }
+ MoveDirection::ForwardRight => {
+ board.move_piece_forward_right_unchecked(self.start() as usize)
+ }
+ MoveDirection::BackwardLeft => {
+ board.move_piece_backward_left_unchecked(self.start() as usize)
+ }
+ MoveDirection::BackwardRight => {
+ board.move_piece_backward_right_unchecked(self.start() as usize)
+ }
+ },
+ true => match self.direction() {
+ MoveDirection::ForwardLeft => {
+ board.jump_piece_forward_left_unchecked(self.start() as usize)
+ }
+ MoveDirection::ForwardRight => {
+ board.jump_piece_forward_right_unchecked(self.start() as usize)
+ }
+ MoveDirection::BackwardLeft => {
+ board.jump_piece_backward_left_unchecked(self.start() as usize)
+ }
+ MoveDirection::BackwardRight => {
+ board.jump_piece_backward_right_unchecked(self.start() as usize)
+ }
+ },
+ }
+ }
+}
+
+impl Display for Move {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ let Some(start) =
+ SquareCoordinate::from_ampere_value(self.start() as usize).to_normal_value()
+ else {
+ return Err(std::fmt::Error);
+ };
+
+ let separator = if self.is_jump() { "x" } else { "-" };
+
+ let Some(end) = SquareCoordinate::from_ampere_value(self.end_position()).to_normal_value()
+ else {
+ return Err(std::fmt::Error);
+ };
+
+ write!(f, "{start}{separator}{end}")
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+ use proptest::prelude::*;
+
+ proptest! {
+ #[test]
+ fn new(start in 0usize..32, jump in proptest::bool::ANY) {
+ let direction = MoveDirection::ForwardLeft;
+ let move_test = Move::new(start, direction, jump);
+ assert_eq!(move_test.start() as usize, start);
+ assert_eq!(move_test.direction(), direction);
+ assert_eq!(move_test.is_jump(), jump);
+
+ let direction = MoveDirection::ForwardRight;
+ let move_test = Move::new(start, direction, jump);
+ assert_eq!(move_test.start() as usize, start);
+ assert_eq!(move_test.direction(), direction);
+ assert_eq!(move_test.is_jump(), jump);
+
+ let direction = MoveDirection::BackwardLeft;
+ let move_test = Move::new(start, direction, jump);
+ assert_eq!(move_test.start() as usize, start);
+ assert_eq!(move_test.direction(), direction);
+ assert_eq!(move_test.is_jump(), jump);
+
+ let direction = MoveDirection::BackwardRight;
+ let move_test = Move::new(start, direction, jump);
+ assert_eq!(move_test.start() as usize, start);
+ assert_eq!(move_test.direction(), direction);
+ assert_eq!(move_test.is_jump(), jump);
+ }
+
+ #[test]
+ fn start(start in 0usize..32, jump in proptest::bool::ANY) {
+ let direction = MoveDirection::ForwardLeft;
+ let move_test = Move::new(start, direction, jump);
+ assert_eq!(move_test.start(), start as u32);
+ }
+
+ #[test]
+ fn direction(start in 0usize..32, jump in proptest::bool::ANY) {
+ let direction = MoveDirection::ForwardLeft;
+ let move_test = Move::new(start, direction, jump);
+ assert_eq!(move_test.direction(), direction);
+
+ let direction = MoveDirection::ForwardRight;
+ let move_test = Move::new(start, direction, jump);
+ assert_eq!(move_test.direction(), direction);
+
+ let direction = MoveDirection::BackwardLeft;
+ let move_test = Move::new(start, direction, jump);
+ assert_eq!(move_test.direction(), direction);
+
+ let direction = MoveDirection::BackwardRight;
+ let move_test = Move::new(start, direction, jump);
+ assert_eq!(move_test.direction(), direction);
+ }
+
+ #[test]
+ fn is_jump(start in 0usize..32, jump in proptest::bool::ANY) {
+ let direction = MoveDirection::ForwardLeft;
+ let move_test = Move::new(start, direction, jump);
+ assert_eq!(move_test.is_jump(), jump);
+ }
+ }
+
+ #[test]
+ fn end_position_forward_left_slide() {
+ let direction = MoveDirection::ForwardLeft;
+ let start = 8;
+ let move_test = Move::new(start, direction, false);
+ assert_eq!(move_test.end_position(), 15);
+ }
+
+ #[test]
+ fn end_position_forward_right_slide() {
+ let direction = MoveDirection::ForwardRight;
+ let start = 26;
+ let move_test = Move::new(start, direction, false);
+ assert_eq!(move_test.end_position(), 27);
+ }
+
+ #[test]
+ fn end_position_backward_right_slide() {
+ let direction = MoveDirection::BackwardRight;
+ let start = 2;
+ let move_test = Move::new(start, direction, false);
+ assert_eq!(move_test.end_position(), 27);
+ }
+
+ #[test]
+ fn end_position_backward_left_slide() {
+ let direction = MoveDirection::BackwardLeft;
+ let start = 16;
+ let move_test = Move::new(start, direction, false);
+ assert_eq!(move_test.end_position(), 15);
+ }
+
+ #[test]
+ fn end_position_forward_left_jump() {
+ let direction = MoveDirection::ForwardLeft;
+ let start = 8;
+ let move_test = Move::new(start, direction, true);
+ assert_eq!(move_test.end_position(), 22);
+ }
+
+ #[test]
+ fn end_position_forward_right_jump() {
+ let direction = MoveDirection::ForwardRight;
+ let start = 26;
+ let move_test = Move::new(start, direction, true);
+ assert_eq!(move_test.end_position(), 28);
+ }
+
+ #[test]
+ fn end_position_backward_right_jump() {
+ let direction = MoveDirection::BackwardRight;
+ let start = 2;
+ let move_test = Move::new(start, direction, true);
+ assert_eq!(move_test.end_position(), 20);
+ }
+
+ #[test]
+ fn end_position_backward_left_jump() {
+ let direction = MoveDirection::BackwardLeft;
+ let start = 16;
+ let move_test = Move::new(start, direction, true);
+ assert_eq!(move_test.end_position(), 14);
+ }
+}
diff --git a/model/src/piece.rs b/model/src/piece.rs index f36e0a4..860142d 100644..100755 --- a/model/src/piece.rs +++ b/model/src/piece.rs @@ -1,21 +1,21 @@ -use crate::PieceColor; - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct Piece { - king: bool, - color: PieceColor, -} - -impl Piece { - pub(crate) const fn new(king: bool, color: PieceColor) -> Self { - Self { king, color } - } - - pub const fn is_king(self) -> bool { - self.king - } - - pub const fn color(self) -> PieceColor { - self.color - } -} +use crate::PieceColor;
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct Piece {
+ king: bool,
+ color: PieceColor,
+}
+
+impl Piece {
+ pub(crate) const fn new(king: bool, color: PieceColor) -> Self {
+ Self { king, color }
+ }
+
+ pub const fn is_king(self) -> bool {
+ self.king
+ }
+
+ pub const fn color(self) -> PieceColor {
+ self.color
+ }
+}
diff --git a/model/src/possible_moves.rs b/model/src/possible_moves.rs index ef05048..40d9df7 100644..100755 --- a/model/src/possible_moves.rs +++ b/model/src/possible_moves.rs @@ -1,1078 +1,1078 @@ -use crate::moves::{Move, MoveDirection}; -use crate::{CheckersBitBoard, PieceColor}; - -use std::mem::MaybeUninit; - -// The maximum number of available moves in any given position -pub const POSSIBLE_MOVES_ITER_SIZE: usize = 50; - -/// A struct containing the possible moves in a particular checkers position -#[derive(Copy, Clone, Debug)] -pub struct PossibleMoves { - forward_left_movers: u32, - forward_right_movers: u32, - backward_left_movers: u32, - backward_right_movers: u32, -} - -/// An iterator of possible checkers moves for a particular position -pub struct PossibleMovesIter { - /// A pointer to an array of possibly uninitialized checkers moves - moves: [MaybeUninit<Move>; POSSIBLE_MOVES_ITER_SIZE], - - /// The current index into the moves array - index: usize, - - // The number of initialized moves in the array - length: usize, -} - -impl PossibleMovesIter { - fn add_slide_forward_left<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) { - if (possible_moves.forward_left_movers >> SQUARE) & 1 != 0 { - debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE); - let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) }; - *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::ForwardLeft, false)); - self.length += 1; - } - } - - fn add_slide_forward_right<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) { - if (possible_moves.forward_right_movers >> SQUARE) & 1 != 0 { - debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE); - let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) }; - *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::ForwardRight, false)); - self.length += 1; - } - } - - fn add_slide_backward_left<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) { - if (possible_moves.backward_left_movers >> SQUARE) & 1 != 0 { - debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE); - let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) }; - *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::BackwardLeft, false)); - self.length += 1; - } - } - - fn add_slide_backward_right<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) { - if (possible_moves.backward_right_movers >> SQUARE) & 1 != 0 { - debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE); - let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) }; - *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::BackwardRight, false)); - self.length += 1; - } - } - - fn add_jump_forward_left<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) { - if (possible_moves.forward_left_movers >> SQUARE) & 1 != 0 { - debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE); - let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) }; - *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::ForwardLeft, true)); - self.length += 1; - } - } - - fn add_jump_forward_right<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) { - if (possible_moves.forward_right_movers >> SQUARE) & 1 != 0 { - debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE); - let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) }; - *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::ForwardRight, true)); - self.length += 1; - } - } - - fn add_jump_backward_left<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) { - if (possible_moves.backward_left_movers >> SQUARE) & 1 != 0 { - debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE); - let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) }; - *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::BackwardLeft, true)); - self.length += 1; - } - } - - fn add_jump_backward_right<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) { - if (possible_moves.backward_right_movers >> SQUARE) & 1 != 0 { - debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE); - let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) }; - *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::BackwardRight, true)); - self.length += 1; - } - } -} - -unsafe impl Send for PossibleMovesIter {} - -impl Iterator for PossibleMovesIter { - type Item = Move; - - fn next(&mut self) -> Option<Self::Item> { - if self.length > self.index { - debug_assert!(self.index < POSSIBLE_MOVES_ITER_SIZE); - let next_move = unsafe { self.moves.as_ref().get_unchecked(self.index).assume_init() }; - self.index += 1; - Some(next_move) - } else { - None - } - } - - // TODO test - fn size_hint(&self) -> (usize, Option<usize>) { - let remaining = self.length - self.index; - (remaining, Some(remaining)) - } - - // TODO test - fn count(self) -> usize - where - Self: Sized, - { - self.length - self.index - } - - // TODO test - fn last(self) -> Option<Self::Item> - where - Self: Sized, - { - debug_assert!(self.length <= POSSIBLE_MOVES_ITER_SIZE); - if self.length == 0 { - None - } else { - Some(unsafe { - self.moves - .as_ref() - .get_unchecked(self.length - 1) - .assume_init() - }) - } - } - - // TODO test - fn nth(&mut self, n: usize) -> Option<Self::Item> { - if self.length == 0 || self.length - self.index < n { - None - } else { - self.index += n; - let current_move = - unsafe { self.moves.as_ref().get_unchecked(self.index).assume_init() }; - self.index += 1; - Some(current_move) - } - } -} - -impl IntoIterator for PossibleMoves { - type Item = Move; - type IntoIter = PossibleMovesIter; - - // TODO test - fn into_iter(self) -> Self::IntoIter { - let moves = [MaybeUninit::uninit(); POSSIBLE_MOVES_ITER_SIZE]; - let mut iter = PossibleMovesIter { - moves, - index: 0, - length: 0, - }; - - if self.can_jump() { - iter.add_jump_forward_left::<0>(self); - iter.add_jump_forward_left::<1>(self); - iter.add_jump_forward_left::<6>(self); - iter.add_jump_forward_left::<7>(self); - iter.add_jump_forward_left::<8>(self); - iter.add_jump_forward_left::<9>(self); - iter.add_jump_forward_left::<12>(self); - iter.add_jump_forward_left::<13>(self); - iter.add_jump_forward_left::<14>(self); - iter.add_jump_forward_left::<15>(self); - iter.add_jump_forward_left::<16>(self); - iter.add_jump_forward_left::<17>(self); - iter.add_jump_forward_left::<20>(self); - iter.add_jump_forward_left::<21>(self); - iter.add_jump_forward_left::<22>(self); - iter.add_jump_forward_left::<23>(self); - iter.add_jump_forward_left::<28>(self); - iter.add_jump_forward_left::<29>(self); - - iter.add_jump_forward_right::<2>(self); - iter.add_jump_forward_right::<3>(self); - iter.add_jump_forward_right::<6>(self); - iter.add_jump_forward_right::<7>(self); - iter.add_jump_forward_right::<12>(self); - iter.add_jump_forward_right::<13>(self); - iter.add_jump_forward_right::<14>(self); - iter.add_jump_forward_right::<15>(self); - iter.add_jump_forward_right::<18>(self); - iter.add_jump_forward_right::<19>(self); - iter.add_jump_forward_right::<20>(self); - iter.add_jump_forward_right::<21>(self); - iter.add_jump_forward_right::<22>(self); - iter.add_jump_forward_right::<23>(self); - iter.add_jump_forward_right::<26>(self); - iter.add_jump_forward_right::<27>(self); - iter.add_jump_forward_right::<28>(self); - iter.add_jump_forward_right::<29>(self); - - iter.add_jump_backward_left::<4>(self); - iter.add_jump_backward_left::<5>(self); - iter.add_jump_backward_left::<8>(self); - iter.add_jump_backward_left::<9>(self); - iter.add_jump_backward_left::<14>(self); - iter.add_jump_backward_left::<15>(self); - iter.add_jump_backward_left::<16>(self); - iter.add_jump_backward_left::<17>(self); - iter.add_jump_backward_left::<20>(self); - iter.add_jump_backward_left::<21>(self); - iter.add_jump_backward_left::<22>(self); - iter.add_jump_backward_left::<23>(self); - iter.add_jump_backward_left::<24>(self); - iter.add_jump_backward_left::<25>(self); - iter.add_jump_backward_left::<28>(self); - iter.add_jump_backward_left::<29>(self); - iter.add_jump_backward_left::<30>(self); - iter.add_jump_backward_left::<31>(self); - - iter.add_jump_backward_right::<2>(self); - iter.add_jump_backward_right::<3>(self); - iter.add_jump_backward_right::<4>(self); - iter.add_jump_backward_right::<5>(self); - iter.add_jump_backward_right::<10>(self); - iter.add_jump_backward_right::<11>(self); - iter.add_jump_backward_right::<14>(self); - iter.add_jump_backward_right::<15>(self); - iter.add_jump_backward_right::<20>(self); - iter.add_jump_backward_right::<21>(self); - iter.add_jump_backward_right::<22>(self); - iter.add_jump_backward_right::<23>(self); - iter.add_jump_backward_right::<26>(self); - iter.add_jump_backward_right::<27>(self); - iter.add_jump_backward_right::<28>(self); - iter.add_jump_backward_right::<29>(self); - iter.add_jump_backward_right::<30>(self); - iter.add_jump_backward_right::<31>(self); - } else { - iter.add_slide_forward_left::<0>(self); - iter.add_slide_forward_left::<1>(self); - iter.add_slide_forward_left::<3>(self); - iter.add_slide_forward_left::<4>(self); - iter.add_slide_forward_left::<6>(self); - iter.add_slide_forward_left::<7>(self); - iter.add_slide_forward_left::<8>(self); - iter.add_slide_forward_left::<9>(self); - iter.add_slide_forward_left::<12>(self); - iter.add_slide_forward_left::<13>(self); - iter.add_slide_forward_left::<14>(self); - iter.add_slide_forward_left::<15>(self); - iter.add_slide_forward_left::<16>(self); - iter.add_slide_forward_left::<17>(self); - iter.add_slide_forward_left::<19>(self); - iter.add_slide_forward_left::<20>(self); - iter.add_slide_forward_left::<21>(self); - iter.add_slide_forward_left::<22>(self); - iter.add_slide_forward_left::<23>(self); - iter.add_slide_forward_left::<24>(self); - iter.add_slide_forward_left::<27>(self); - iter.add_slide_forward_left::<28>(self); - iter.add_slide_forward_left::<29>(self); - iter.add_slide_forward_left::<30>(self); - - iter.add_slide_forward_right::<0>(self); - iter.add_slide_forward_right::<2>(self); - iter.add_slide_forward_right::<3>(self); - iter.add_slide_forward_right::<4>(self); - iter.add_slide_forward_right::<6>(self); - iter.add_slide_forward_right::<7>(self); - iter.add_slide_forward_right::<8>(self); - iter.add_slide_forward_right::<10>(self); - iter.add_slide_forward_right::<12>(self); - iter.add_slide_forward_right::<13>(self); - iter.add_slide_forward_right::<14>(self); - iter.add_slide_forward_right::<15>(self); - iter.add_slide_forward_right::<16>(self); - iter.add_slide_forward_right::<18>(self); - iter.add_slide_forward_right::<19>(self); - iter.add_slide_forward_right::<20>(self); - iter.add_slide_forward_right::<21>(self); - iter.add_slide_forward_right::<22>(self); - iter.add_slide_forward_right::<23>(self); - iter.add_slide_forward_right::<24>(self); - iter.add_slide_forward_right::<26>(self); - iter.add_slide_forward_right::<27>(self); - iter.add_slide_forward_right::<28>(self); - iter.add_slide_forward_right::<29>(self); - iter.add_slide_forward_right::<30>(self); - - iter.add_slide_backward_left::<1>(self); - iter.add_slide_backward_left::<3>(self); - iter.add_slide_backward_left::<4>(self); - iter.add_slide_backward_left::<5>(self); - iter.add_slide_backward_left::<7>(self); - iter.add_slide_backward_left::<8>(self); - iter.add_slide_backward_left::<9>(self); - iter.add_slide_backward_left::<11>(self); - iter.add_slide_backward_left::<13>(self); - iter.add_slide_backward_left::<14>(self); - iter.add_slide_backward_left::<15>(self); - iter.add_slide_backward_left::<16>(self); - iter.add_slide_backward_left::<17>(self); - iter.add_slide_backward_left::<19>(self); - iter.add_slide_backward_left::<20>(self); - iter.add_slide_backward_left::<21>(self); - iter.add_slide_backward_left::<22>(self); - iter.add_slide_backward_left::<23>(self); - iter.add_slide_backward_left::<24>(self); - iter.add_slide_backward_left::<25>(self); - iter.add_slide_backward_left::<27>(self); - iter.add_slide_backward_left::<28>(self); - iter.add_slide_backward_left::<29>(self); - iter.add_slide_backward_left::<30>(self); - iter.add_slide_backward_left::<31>(self); - - iter.add_slide_backward_right::<2>(self); - iter.add_slide_backward_right::<3>(self); - iter.add_slide_backward_right::<4>(self); - iter.add_slide_backward_right::<5>(self); - iter.add_slide_backward_right::<7>(self); - iter.add_slide_backward_right::<8>(self); - iter.add_slide_backward_right::<10>(self); - iter.add_slide_backward_right::<11>(self); - iter.add_slide_backward_right::<13>(self); - iter.add_slide_backward_right::<14>(self); - iter.add_slide_backward_right::<15>(self); - iter.add_slide_backward_right::<16>(self); - iter.add_slide_backward_right::<19>(self); - iter.add_slide_backward_right::<20>(self); - iter.add_slide_backward_right::<21>(self); - iter.add_slide_backward_right::<22>(self); - iter.add_slide_backward_right::<23>(self); - iter.add_slide_backward_right::<24>(self); - iter.add_slide_backward_right::<26>(self); - iter.add_slide_backward_right::<27>(self); - iter.add_slide_backward_right::<28>(self); - iter.add_slide_backward_right::<29>(self); - iter.add_slide_backward_right::<30>(self); - iter.add_slide_backward_right::<31>(self); - } - - iter - } -} - -impl PossibleMoves { - // TODO test - - /// The highest possible number of valid moves - pub const MAX_POSSIBLE_MOVES: usize = POSSIBLE_MOVES_ITER_SIZE; - - const fn slides_dark(board: CheckersBitBoard) -> Self { - const FORWARD_LEFT_MASK: u32 = 0b01111001111110111111001111011011; - const FORWARD_RIGHT_MASK: u32 = 0b01111101111111011111010111011101; - const BACKWARD_LEFT_MASK: u32 = 0b11111011111110111110101110111010; - const BACKWARD_RIGHT_MASK: u32 = 0b11111001111110011110110110111100; - - let not_occupied = !board.pieces_bits(); - let friendly_pieces = board.pieces_bits() & board.color_bits(); - let friendly_kings = friendly_pieces & board.king_bits(); - - let forward_left_movers = - not_occupied.rotate_right(7) & friendly_pieces & FORWARD_LEFT_MASK; - let forward_right_movers = - not_occupied.rotate_right(1) & friendly_pieces & FORWARD_RIGHT_MASK; - let backward_left_movers; - let backward_right_movers; - - if friendly_kings > 0 { - backward_left_movers = - not_occupied.rotate_left(1) & friendly_kings & BACKWARD_LEFT_MASK; - backward_right_movers = - not_occupied.rotate_left(7) & friendly_kings & BACKWARD_RIGHT_MASK; - } else { - backward_left_movers = 0; - backward_right_movers = 0; - } - - Self { - forward_left_movers, - forward_right_movers, - backward_left_movers, - backward_right_movers, - } - } - - const fn slides_light(board: CheckersBitBoard) -> Self { - const FORWARD_LEFT_MASK: u32 = 0b01111001111110111111001111011011; - const FORWARD_RIGHT_MASK: u32 = 0b01111101111111011111010111011101; - const BACKWARD_LEFT_MASK: u32 = 0b11111011111110111110101110111010; - const BACKWARD_RIGHT_MASK: u32 = 0b11111001111110011110110110111100; - - let not_occupied = !board.pieces_bits(); - let friendly_pieces = board.pieces_bits() & !board.color_bits(); - let friendly_kings = friendly_pieces & board.king_bits(); - - let backward_left_movers = - not_occupied.rotate_left(1) & friendly_pieces & BACKWARD_LEFT_MASK; - let backward_right_movers = - not_occupied.rotate_left(7) & friendly_pieces & BACKWARD_RIGHT_MASK; - let forward_left_movers; - let forward_right_movers; - - if friendly_kings > 0 { - forward_left_movers = not_occupied.rotate_right(7) & friendly_kings & FORWARD_LEFT_MASK; - forward_right_movers = - not_occupied.rotate_right(1) & friendly_kings & FORWARD_RIGHT_MASK; - } else { - forward_left_movers = 0; - forward_right_movers = 0; - } - - Self { - forward_left_movers, - forward_right_movers, - backward_left_movers, - backward_right_movers, - } - } - - const fn jumps_dark(board: CheckersBitBoard) -> Self { - const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011; - const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100; - const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000; - const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100; - - let not_occupied = !board.pieces_bits(); - let enemy_pieces = board.pieces_bits() & !board.color_bits(); - let friendly_pieces = board.pieces_bits() & board.color_bits(); - let friendly_kings = friendly_pieces & board.king_bits(); - - let forward_left_movers = not_occupied.rotate_right(14) - & enemy_pieces.rotate_right(7) - & friendly_pieces - & FORWARD_LEFT_MASK; - let forward_right_movers = not_occupied.rotate_right(2) - & enemy_pieces.rotate_right(1) - & friendly_pieces - & FORWARD_RIGHT_MASK; - let backward_left_movers; - let backward_right_movers; - - if friendly_kings > 0 { - backward_left_movers = not_occupied.rotate_left(2) - & enemy_pieces.rotate_left(1) - & friendly_kings & BACKWARD_LEFT_MASK; - backward_right_movers = not_occupied.rotate_left(14) - & enemy_pieces.rotate_left(7) - & friendly_kings & BACKWARD_RIGHT_MASK; - } else { - backward_left_movers = 0; - backward_right_movers = 0; - } - - let can_jump = if forward_left_movers != 0 - || forward_right_movers != 0 - || backward_left_movers != 0 - || backward_right_movers != 0 - { - 2 - } else { - 0 - }; - - Self { - forward_left_movers, - forward_right_movers, - backward_left_movers, - backward_right_movers: backward_right_movers | can_jump, - } - } - - const fn jumps_light(board: CheckersBitBoard) -> Self { - const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011; - const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100; - const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000; - const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100; - - let not_occupied = !board.pieces_bits(); - let enemy_pieces = board.pieces_bits() & board.color_bits(); - let friendly_pieces = board.pieces_bits() & !board.color_bits(); - let friendly_kings = friendly_pieces & board.king_bits(); - - let backward_left_movers = not_occupied.rotate_left(2) - & enemy_pieces.rotate_left(1) - & friendly_pieces - & BACKWARD_LEFT_MASK; - let backward_right_movers = not_occupied.rotate_left(14) - & enemy_pieces.rotate_left(7) - & friendly_pieces - & BACKWARD_RIGHT_MASK; - let forward_left_movers; - let forward_right_movers; - - if friendly_kings > 0 { - forward_left_movers = not_occupied.rotate_right(14) - & enemy_pieces.rotate_right(7) - & friendly_kings & FORWARD_LEFT_MASK; - forward_right_movers = not_occupied.rotate_right(2) - & enemy_pieces.rotate_right(1) - & friendly_kings & FORWARD_RIGHT_MASK; - } else { - forward_left_movers = 0; - forward_right_movers = 0; - } - - let can_jump = if forward_left_movers != 0 - || forward_right_movers != 0 - || backward_left_movers != 0 - || backward_right_movers != 0 - { - 2 - } else { - 0 - }; - - Self { - forward_left_movers, - forward_right_movers, - backward_left_movers, - backward_right_movers: backward_right_movers | can_jump, - } - } - - const fn has_jumps_dark(board: CheckersBitBoard) -> bool { - const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011; - const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100; - const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000; - const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100; - - let not_occupied = !board.pieces_bits(); - let enemy_pieces = board.pieces_bits() & !board.color_bits(); - let friendly_pieces = board.pieces_bits() & board.color_bits(); - - let forward_left_spaces = - not_occupied.rotate_right(14) & enemy_pieces.rotate_right(7) & FORWARD_LEFT_MASK; - let forward_right_spaces = - not_occupied.rotate_right(2) & enemy_pieces.rotate_right(1) & FORWARD_RIGHT_MASK; - - let forward_spaces = forward_left_spaces | forward_right_spaces; - - if board.king_bits() > 0 { - let backward_left_spaces = - not_occupied.rotate_left(2) & enemy_pieces.rotate_left(1) & BACKWARD_LEFT_MASK; - let backward_right_spaces = - not_occupied.rotate_left(14) & enemy_pieces.rotate_left(7) & BACKWARD_RIGHT_MASK; - let backward_spaces = backward_left_spaces | backward_right_spaces; - - let backward_spaces = board.king_bits() & backward_spaces; - friendly_pieces & (forward_spaces | backward_spaces) != 0 - } else { - friendly_pieces & forward_spaces != 0 - } - } - - const fn has_jumps_light(board: CheckersBitBoard) -> bool { - const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011; - const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100; - const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000; - const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100; - - let not_occupied = !board.pieces_bits(); - let enemy_pieces = board.pieces_bits() & board.color_bits(); - let friendly_pieces = board.pieces_bits() & !board.color_bits(); - - let backward_left_spaces = - not_occupied.rotate_left(2) & enemy_pieces.rotate_left(1) & BACKWARD_LEFT_MASK; - let backward_right_spaces = - not_occupied.rotate_left(14) & enemy_pieces.rotate_left(7) & BACKWARD_RIGHT_MASK; - - let backward_spaces = backward_left_spaces | backward_right_spaces; - - if board.king_bits() > 0 { - let forward_left_spaces = - not_occupied.rotate_right(14) & enemy_pieces.rotate_right(7) & FORWARD_LEFT_MASK; - let forward_right_spaces = - not_occupied.rotate_right(2) & enemy_pieces.rotate_right(1) & FORWARD_RIGHT_MASK; - let forward_spaces = forward_left_spaces | forward_right_spaces; - - let forward_spaces = board.king_bits() & forward_spaces; - friendly_pieces & (forward_spaces | backward_spaces) != 0 - } else { - friendly_pieces & backward_spaces != 0 - } - } - - #[inline(always)] - // TODO optimize - pub const fn has_jumps(board: CheckersBitBoard) -> bool { - match board.turn() { - PieceColor::Light => Self::has_jumps_light(board), - PieceColor::Dark => Self::has_jumps_dark(board), - } - } - - const fn has_jumps_at_dark(board: CheckersBitBoard, value: usize) -> bool { - const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011; - const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100; - const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000; - const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100; - - let not_occupied = !board.pieces_bits(); - let enemy_pieces = board.pieces_bits() & !board.color_bits(); - let friendly_pieces = board.pieces_bits() & board.color_bits(); - - let forward_left_spaces = - not_occupied.rotate_right(14) & enemy_pieces.rotate_right(7) & FORWARD_LEFT_MASK; - let forward_right_spaces = - not_occupied.rotate_right(2) & enemy_pieces.rotate_right(1) & FORWARD_RIGHT_MASK; - - let forward_spaces = forward_left_spaces | forward_right_spaces; - - if board.king_bits() > 0 { - let backward_left_spaces = - not_occupied.rotate_left(2) & enemy_pieces.rotate_left(1) & BACKWARD_LEFT_MASK; - let backward_right_spaces = - not_occupied.rotate_left(14) & enemy_pieces.rotate_left(7) & BACKWARD_RIGHT_MASK; - let backward_spaces = backward_left_spaces | backward_right_spaces; - - let backward_spaces = board.king_bits() & backward_spaces; - ((friendly_pieces & (forward_spaces | backward_spaces)) >> value) & 1 != 0 - } else { - ((friendly_pieces & forward_spaces) >> value) & 1 != 0 - } - } - - const fn has_jumps_at_light(board: CheckersBitBoard, value: usize) -> bool { - const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011; - const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100; - const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000; - const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100; - - let not_occupied = !board.pieces_bits(); - let enemy_pieces = board.pieces_bits() & board.color_bits(); - let friendly_pieces = board.pieces_bits() & !board.color_bits(); - - let backward_left_spaces = - not_occupied.rotate_left(2) & enemy_pieces.rotate_left(1) & BACKWARD_LEFT_MASK; - let backward_right_spaces = - not_occupied.rotate_left(14) & enemy_pieces.rotate_left(7) & BACKWARD_RIGHT_MASK; - - let backward_spaces = backward_left_spaces | backward_right_spaces; - - if board.king_bits() > 0 { - let forward_left_spaces = - not_occupied.rotate_right(14) & enemy_pieces.rotate_right(7) & FORWARD_LEFT_MASK; - let forward_right_spaces = - not_occupied.rotate_right(2) & enemy_pieces.rotate_right(1) & FORWARD_RIGHT_MASK; - let forward_spaces = forward_left_spaces | forward_right_spaces; - - let forward_spaces = board.king_bits() & forward_spaces; - ((friendly_pieces & (forward_spaces | backward_spaces)) >> value) & 1 != 0 - } else { - ((friendly_pieces & backward_spaces) >> value) & 1 != 0 - } - } - - #[inline(always)] - // TODO optimize - pub const fn has_jumps_at(board: CheckersBitBoard, value: usize) -> bool { - match board.turn() { - PieceColor::Light => Self::has_jumps_at_light(board, value), - PieceColor::Dark => Self::has_jumps_at_dark(board, value), - } - } - - const fn light_moves(board: CheckersBitBoard) -> Self { - let jumps = Self::jumps_light(board); - if jumps.is_empty() { - Self::slides_light(board) - } else { - jumps - } - } - - const fn dark_moves(board: CheckersBitBoard) -> Self { - let jumps = Self::jumps_dark(board); - if jumps.is_empty() { - Self::slides_dark(board) - } else { - jumps - } - } - - const fn filter_to_square(self, square: u8) -> Self { - let mask = 1 << square; - Self { - forward_left_movers: self.forward_left_movers & mask, - forward_right_movers: self.forward_right_movers & mask, - backward_left_movers: self.backward_left_movers & mask, - backward_right_movers: self.backward_right_movers & (mask | 2), - } - } - - pub fn moves(board: CheckersBitBoard) -> Self { - let moves = match board.turn() { - PieceColor::Dark => Self::dark_moves(board), - PieceColor::Light => Self::light_moves(board), - }; - - if board.turn == board.previous_turn { - moves.filter_to_square(board.previous_move_to) - } else { - moves - } - } - - /// Returns true if no moves are possible - pub const fn is_empty(self) -> bool { - (self.backward_left_movers - | (self.forward_left_movers) - | self.forward_right_movers - | self.backward_right_movers & 4294967293) - == 0 - } - - /// Returns true if the piece can jump - pub const fn can_jump(self) -> bool { - (self.backward_right_movers & 2) != 0 - } - - /// Returns true if the given move is possible - pub const fn contains(self, checker_move: Move) -> bool { - if checker_move.is_jump() != self.can_jump() { - return false; - } - - let bits = match checker_move.direction() { - MoveDirection::ForwardLeft => self.forward_left_movers, - MoveDirection::ForwardRight => self.forward_right_movers, - MoveDirection::BackwardLeft => self.backward_left_movers, - MoveDirection::BackwardRight => self.backward_right_movers, - }; - - (bits >> checker_move.start()) & 1 == 1 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn setup_empty_iter() -> PossibleMovesIter { - let moves = [MaybeUninit::uninit(); POSSIBLE_MOVES_ITER_SIZE]; - PossibleMovesIter { - moves, - index: 0, - length: 0, - } - } - - fn setup_add_move_to_iter_invalid() -> (PossibleMovesIter, PossibleMoves) { - let moves = PossibleMoves { - forward_left_movers: 0, - forward_right_movers: 0, - backward_left_movers: 0, - backward_right_movers: 0, - }; - let iter = setup_empty_iter(); - - (iter, moves) - } - - fn setup_add_move_to_iter_valid() -> (PossibleMovesIter, PossibleMoves) { - let moves = PossibleMoves { - forward_left_movers: u32::MAX, - forward_right_movers: u32::MAX, - backward_left_movers: u32::MAX, - backward_right_movers: u32::MAX, - }; - let iter = setup_empty_iter(); - - (iter, moves) - } - - #[test] - fn same() { - let start = CheckersBitBoard::new( - 0b11100111100111100111110111111011, - 0b00001100001111001111001111000011, - 0, - PieceColor::Dark, - ); - let flip = CheckersBitBoard::new( - 0b11100111100111100111110111111011, - 0b11110011110000110000110000111100, - 0, - PieceColor::Light, - ); - - assert_eq!( - PossibleMoves::has_jumps(start), - PossibleMoves::has_jumps(flip) - ) - } - - #[test] - fn iter_next() { - let test_move1 = Move::new(8, MoveDirection::ForwardLeft, false); - let test_move2 = Move::new(26, MoveDirection::ForwardRight, true); - let mut iter = setup_empty_iter(); - iter.length = 2; - - let ptr = iter.moves.as_mut().get_mut(0).unwrap(); - *ptr = MaybeUninit::new(test_move1); - - let ptr = iter.moves.as_mut().get_mut(1).unwrap(); - *ptr = MaybeUninit::new(test_move2); - - let recieved_move = iter.next(); - assert!(recieved_move.is_some()); - assert_eq!(recieved_move.unwrap(), test_move1); - - let recieved_move = iter.next(); - assert!(recieved_move.is_some()); - assert_eq!(recieved_move.unwrap(), test_move2); - - let recieved_move = iter.next(); - assert!(recieved_move.is_none()); - } - - #[test] - fn add_slide_forward_left_to_iter_invalid() { - const START: usize = 8; - let (mut iter, moves) = setup_add_move_to_iter_invalid(); - iter.add_slide_forward_left::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 0); - } - - #[test] - fn add_slide_forward_left_to_iter_valid() { - const START: usize = 8; - let (mut iter, moves) = setup_add_move_to_iter_valid(); - iter.add_slide_forward_left::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 1); - - let new_move = iter.next().unwrap(); - assert_eq!(new_move.start(), START as u32); - assert_eq!(new_move.direction(), MoveDirection::ForwardLeft); - assert!(!new_move.is_jump()); - } - - #[test] - fn add_slide_forward_right_to_iter_invalid() { - const START: usize = 26; - let (mut iter, moves) = setup_add_move_to_iter_invalid(); - iter.add_slide_forward_right::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 0); - } - - #[test] - fn add_slide_forward_right_to_iter_valid() { - const START: usize = 26; - let (mut iter, moves) = setup_add_move_to_iter_valid(); - iter.add_slide_forward_right::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 1); - - let new_move = iter.next().unwrap(); - assert_eq!(new_move.start(), START as u32); - assert_eq!(new_move.direction(), MoveDirection::ForwardRight); - assert!(!new_move.is_jump()); - } - - #[test] - fn add_slide_backward_left_to_iter_invalid() { - const START: usize = 17; - let (mut iter, moves) = setup_add_move_to_iter_invalid(); - iter.add_slide_backward_left::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 0); - } - - #[test] - fn add_slide_backward_left_to_iter_valid() { - const START: usize = 17; - let (mut iter, moves) = setup_add_move_to_iter_valid(); - iter.add_slide_backward_left::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 1); - - let new_move = iter.next().unwrap(); - assert_eq!(new_move.start(), START as u32); - assert_eq!(new_move.direction(), MoveDirection::BackwardLeft); - assert!(!new_move.is_jump()); - } - - #[test] - fn add_slide_backward_right_to_iter_invalid() { - const START: usize = 3; - let (mut iter, moves) = setup_add_move_to_iter_invalid(); - iter.add_slide_backward_right::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 0); - } - - #[test] - fn add_slide_backward_right_to_iter_valid() { - const START: usize = 3; - let (mut iter, moves) = setup_add_move_to_iter_valid(); - iter.add_slide_backward_right::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 1); - - let new_move = iter.next().unwrap(); - assert_eq!(new_move.start(), START as u32); - assert_eq!(new_move.direction(), MoveDirection::BackwardRight); - assert!(!new_move.is_jump()); - } - - #[test] - fn add_jump_forward_left_to_iter_invalid() { - const START: usize = 8; - let (mut iter, moves) = setup_add_move_to_iter_invalid(); - iter.add_jump_forward_left::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 0); - } - - #[test] - fn add_jump_forward_left_to_iter_valid() { - const START: usize = 8; - let (mut iter, moves) = setup_add_move_to_iter_valid(); - iter.add_jump_forward_left::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 1); - - let new_move = iter.next().unwrap(); - assert_eq!(new_move.start(), START as u32); - assert_eq!(new_move.direction(), MoveDirection::ForwardLeft); - assert!(new_move.is_jump()); - } - - #[test] - fn add_jump_forward_right_to_iter_invalid() { - const START: usize = 26; - let (mut iter, moves) = setup_add_move_to_iter_invalid(); - iter.add_jump_forward_right::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 0); - } - - #[test] - fn add_jump_forward_right_to_iter_valid() { - const START: usize = 26; - let (mut iter, moves) = setup_add_move_to_iter_valid(); - iter.add_jump_forward_right::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 1); - - let new_move = iter.next().unwrap(); - assert_eq!(new_move.start(), START as u32); - assert_eq!(new_move.direction(), MoveDirection::ForwardRight); - assert!(new_move.is_jump()); - } - - #[test] - fn add_jump_backward_left_to_iter_invalid() { - const START: usize = 17; - let (mut iter, moves) = setup_add_move_to_iter_invalid(); - iter.add_jump_backward_left::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 0); - } - - #[test] - fn add_jump_backward_left_to_iter_valid() { - const START: usize = 17; - let (mut iter, moves) = setup_add_move_to_iter_valid(); - iter.add_jump_backward_left::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 1); - - let new_move = iter.next().unwrap(); - assert_eq!(new_move.start(), START as u32); - assert_eq!(new_move.direction(), MoveDirection::BackwardLeft); - assert!(new_move.is_jump()); - } - - #[test] - fn add_jump_backward_right_to_iter_invalid() { - const START: usize = 3; - let (mut iter, moves) = setup_add_move_to_iter_invalid(); - iter.add_jump_backward_right::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 0); - } - - #[test] - fn add_jump_backward_right_to_iter_valid() { - const START: usize = 3; - let (mut iter, moves) = setup_add_move_to_iter_valid(); - iter.add_jump_backward_right::<START>(moves); - - assert_eq!(iter.index, 0); - assert_eq!(iter.length, 1); - - let new_move = iter.next().unwrap(); - assert_eq!(new_move.start(), START as u32); - assert_eq!(new_move.direction(), MoveDirection::BackwardRight); - assert!(new_move.is_jump()); - } - - #[test] - fn cant_jump_in_position_2_without_26() { - // This bug was bizarre, but it's caused by a white piece being in the - //second bit while there is no piece in the 26th bit. If you don't - // apply the bit mask for collision detection, then all of the light - // player moves become jumps. - let board = CheckersBitBoard::new(16908890, 401395713, 50332352, PieceColor::Light); - let possible_moves = PossibleMoves::moves(board); - assert!(!possible_moves.can_jump()) - } - - #[test] - fn not_has_jump_at_14_when_has_jump_at_20() { - // This bug was caused by me forgetting to `& 1` to the end of the - // `has_jump_at` functions. After playing a jump with one piece, I was - // able to continue jumping with completely different pieces - let board = CheckersBitBoard::new( - 0b11100111001111001111110111111011, - 0b00001100001111001111001111000011, - 0, - PieceColor::Dark, - ); - let possible_moves = PossibleMoves::moves(board); - assert!(!possible_moves.can_jump()) - } - - #[test] - fn test_send() { - fn assert_send<T: Send>() {} - assert_send::<PossibleMoves>(); - assert_send::<PossibleMovesIter>(); - } - - #[test] - fn test_sync() { - fn assert_sync<T: Sync>() {} - assert_sync::<PossibleMoves>(); - assert_sync::<PossibleMovesIter>(); - } -} +use crate::moves::{Move, MoveDirection};
+use crate::{CheckersBitBoard, PieceColor};
+
+use std::mem::MaybeUninit;
+
+// The maximum number of available moves in any given position
+pub const POSSIBLE_MOVES_ITER_SIZE: usize = 50;
+
+/// A struct containing the possible moves in a particular checkers position
+#[derive(Copy, Clone, Debug)]
+pub struct PossibleMoves {
+ forward_left_movers: u32,
+ forward_right_movers: u32,
+ backward_left_movers: u32,
+ backward_right_movers: u32,
+}
+
+/// An iterator of possible checkers moves for a particular position
+pub struct PossibleMovesIter {
+ /// A pointer to an array of possibly uninitialized checkers moves
+ moves: [MaybeUninit<Move>; POSSIBLE_MOVES_ITER_SIZE],
+
+ /// The current index into the moves array
+ index: usize,
+
+ // The number of initialized moves in the array
+ length: usize,
+}
+
+impl PossibleMovesIter {
+ fn add_slide_forward_left<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) {
+ if (possible_moves.forward_left_movers >> SQUARE) & 1 != 0 {
+ debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE);
+ let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) };
+ *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::ForwardLeft, false));
+ self.length += 1;
+ }
+ }
+
+ fn add_slide_forward_right<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) {
+ if (possible_moves.forward_right_movers >> SQUARE) & 1 != 0 {
+ debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE);
+ let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) };
+ *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::ForwardRight, false));
+ self.length += 1;
+ }
+ }
+
+ fn add_slide_backward_left<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) {
+ if (possible_moves.backward_left_movers >> SQUARE) & 1 != 0 {
+ debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE);
+ let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) };
+ *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::BackwardLeft, false));
+ self.length += 1;
+ }
+ }
+
+ fn add_slide_backward_right<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) {
+ if (possible_moves.backward_right_movers >> SQUARE) & 1 != 0 {
+ debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE);
+ let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) };
+ *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::BackwardRight, false));
+ self.length += 1;
+ }
+ }
+
+ fn add_jump_forward_left<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) {
+ if (possible_moves.forward_left_movers >> SQUARE) & 1 != 0 {
+ debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE);
+ let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) };
+ *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::ForwardLeft, true));
+ self.length += 1;
+ }
+ }
+
+ fn add_jump_forward_right<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) {
+ if (possible_moves.forward_right_movers >> SQUARE) & 1 != 0 {
+ debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE);
+ let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) };
+ *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::ForwardRight, true));
+ self.length += 1;
+ }
+ }
+
+ fn add_jump_backward_left<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) {
+ if (possible_moves.backward_left_movers >> SQUARE) & 1 != 0 {
+ debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE);
+ let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) };
+ *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::BackwardLeft, true));
+ self.length += 1;
+ }
+ }
+
+ fn add_jump_backward_right<const SQUARE: usize>(&mut self, possible_moves: PossibleMoves) {
+ if (possible_moves.backward_right_movers >> SQUARE) & 1 != 0 {
+ debug_assert!(self.length < POSSIBLE_MOVES_ITER_SIZE);
+ let ptr = unsafe { self.moves.as_mut().get_unchecked_mut(self.length) };
+ *ptr = MaybeUninit::new(Move::new(SQUARE, MoveDirection::BackwardRight, true));
+ self.length += 1;
+ }
+ }
+}
+
+unsafe impl Send for PossibleMovesIter {}
+
+impl Iterator for PossibleMovesIter {
+ type Item = Move;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.length > self.index {
+ debug_assert!(self.index < POSSIBLE_MOVES_ITER_SIZE);
+ let next_move = unsafe { self.moves.as_ref().get_unchecked(self.index).assume_init() };
+ self.index += 1;
+ Some(next_move)
+ } else {
+ None
+ }
+ }
+
+ // TODO test
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ let remaining = self.length - self.index;
+ (remaining, Some(remaining))
+ }
+
+ // TODO test
+ fn count(self) -> usize
+ where
+ Self: Sized,
+ {
+ self.length - self.index
+ }
+
+ // TODO test
+ fn last(self) -> Option<Self::Item>
+ where
+ Self: Sized,
+ {
+ debug_assert!(self.length <= POSSIBLE_MOVES_ITER_SIZE);
+ if self.length == 0 {
+ None
+ } else {
+ Some(unsafe {
+ self.moves
+ .as_ref()
+ .get_unchecked(self.length - 1)
+ .assume_init()
+ })
+ }
+ }
+
+ // TODO test
+ fn nth(&mut self, n: usize) -> Option<Self::Item> {
+ if self.length == 0 || self.length - self.index < n {
+ None
+ } else {
+ self.index += n;
+ let current_move =
+ unsafe { self.moves.as_ref().get_unchecked(self.index).assume_init() };
+ self.index += 1;
+ Some(current_move)
+ }
+ }
+}
+
+impl IntoIterator for PossibleMoves {
+ type Item = Move;
+ type IntoIter = PossibleMovesIter;
+
+ // TODO test
+ fn into_iter(self) -> Self::IntoIter {
+ let moves = [MaybeUninit::uninit(); POSSIBLE_MOVES_ITER_SIZE];
+ let mut iter = PossibleMovesIter {
+ moves,
+ index: 0,
+ length: 0,
+ };
+
+ if self.can_jump() {
+ iter.add_jump_forward_left::<0>(self);
+ iter.add_jump_forward_left::<1>(self);
+ iter.add_jump_forward_left::<6>(self);
+ iter.add_jump_forward_left::<7>(self);
+ iter.add_jump_forward_left::<8>(self);
+ iter.add_jump_forward_left::<9>(self);
+ iter.add_jump_forward_left::<12>(self);
+ iter.add_jump_forward_left::<13>(self);
+ iter.add_jump_forward_left::<14>(self);
+ iter.add_jump_forward_left::<15>(self);
+ iter.add_jump_forward_left::<16>(self);
+ iter.add_jump_forward_left::<17>(self);
+ iter.add_jump_forward_left::<20>(self);
+ iter.add_jump_forward_left::<21>(self);
+ iter.add_jump_forward_left::<22>(self);
+ iter.add_jump_forward_left::<23>(self);
+ iter.add_jump_forward_left::<28>(self);
+ iter.add_jump_forward_left::<29>(self);
+
+ iter.add_jump_forward_right::<2>(self);
+ iter.add_jump_forward_right::<3>(self);
+ iter.add_jump_forward_right::<6>(self);
+ iter.add_jump_forward_right::<7>(self);
+ iter.add_jump_forward_right::<12>(self);
+ iter.add_jump_forward_right::<13>(self);
+ iter.add_jump_forward_right::<14>(self);
+ iter.add_jump_forward_right::<15>(self);
+ iter.add_jump_forward_right::<18>(self);
+ iter.add_jump_forward_right::<19>(self);
+ iter.add_jump_forward_right::<20>(self);
+ iter.add_jump_forward_right::<21>(self);
+ iter.add_jump_forward_right::<22>(self);
+ iter.add_jump_forward_right::<23>(self);
+ iter.add_jump_forward_right::<26>(self);
+ iter.add_jump_forward_right::<27>(self);
+ iter.add_jump_forward_right::<28>(self);
+ iter.add_jump_forward_right::<29>(self);
+
+ iter.add_jump_backward_left::<4>(self);
+ iter.add_jump_backward_left::<5>(self);
+ iter.add_jump_backward_left::<8>(self);
+ iter.add_jump_backward_left::<9>(self);
+ iter.add_jump_backward_left::<14>(self);
+ iter.add_jump_backward_left::<15>(self);
+ iter.add_jump_backward_left::<16>(self);
+ iter.add_jump_backward_left::<17>(self);
+ iter.add_jump_backward_left::<20>(self);
+ iter.add_jump_backward_left::<21>(self);
+ iter.add_jump_backward_left::<22>(self);
+ iter.add_jump_backward_left::<23>(self);
+ iter.add_jump_backward_left::<24>(self);
+ iter.add_jump_backward_left::<25>(self);
+ iter.add_jump_backward_left::<28>(self);
+ iter.add_jump_backward_left::<29>(self);
+ iter.add_jump_backward_left::<30>(self);
+ iter.add_jump_backward_left::<31>(self);
+
+ iter.add_jump_backward_right::<2>(self);
+ iter.add_jump_backward_right::<3>(self);
+ iter.add_jump_backward_right::<4>(self);
+ iter.add_jump_backward_right::<5>(self);
+ iter.add_jump_backward_right::<10>(self);
+ iter.add_jump_backward_right::<11>(self);
+ iter.add_jump_backward_right::<14>(self);
+ iter.add_jump_backward_right::<15>(self);
+ iter.add_jump_backward_right::<20>(self);
+ iter.add_jump_backward_right::<21>(self);
+ iter.add_jump_backward_right::<22>(self);
+ iter.add_jump_backward_right::<23>(self);
+ iter.add_jump_backward_right::<26>(self);
+ iter.add_jump_backward_right::<27>(self);
+ iter.add_jump_backward_right::<28>(self);
+ iter.add_jump_backward_right::<29>(self);
+ iter.add_jump_backward_right::<30>(self);
+ iter.add_jump_backward_right::<31>(self);
+ } else {
+ iter.add_slide_forward_left::<0>(self);
+ iter.add_slide_forward_left::<1>(self);
+ iter.add_slide_forward_left::<3>(self);
+ iter.add_slide_forward_left::<4>(self);
+ iter.add_slide_forward_left::<6>(self);
+ iter.add_slide_forward_left::<7>(self);
+ iter.add_slide_forward_left::<8>(self);
+ iter.add_slide_forward_left::<9>(self);
+ iter.add_slide_forward_left::<12>(self);
+ iter.add_slide_forward_left::<13>(self);
+ iter.add_slide_forward_left::<14>(self);
+ iter.add_slide_forward_left::<15>(self);
+ iter.add_slide_forward_left::<16>(self);
+ iter.add_slide_forward_left::<17>(self);
+ iter.add_slide_forward_left::<19>(self);
+ iter.add_slide_forward_left::<20>(self);
+ iter.add_slide_forward_left::<21>(self);
+ iter.add_slide_forward_left::<22>(self);
+ iter.add_slide_forward_left::<23>(self);
+ iter.add_slide_forward_left::<24>(self);
+ iter.add_slide_forward_left::<27>(self);
+ iter.add_slide_forward_left::<28>(self);
+ iter.add_slide_forward_left::<29>(self);
+ iter.add_slide_forward_left::<30>(self);
+
+ iter.add_slide_forward_right::<0>(self);
+ iter.add_slide_forward_right::<2>(self);
+ iter.add_slide_forward_right::<3>(self);
+ iter.add_slide_forward_right::<4>(self);
+ iter.add_slide_forward_right::<6>(self);
+ iter.add_slide_forward_right::<7>(self);
+ iter.add_slide_forward_right::<8>(self);
+ iter.add_slide_forward_right::<10>(self);
+ iter.add_slide_forward_right::<12>(self);
+ iter.add_slide_forward_right::<13>(self);
+ iter.add_slide_forward_right::<14>(self);
+ iter.add_slide_forward_right::<15>(self);
+ iter.add_slide_forward_right::<16>(self);
+ iter.add_slide_forward_right::<18>(self);
+ iter.add_slide_forward_right::<19>(self);
+ iter.add_slide_forward_right::<20>(self);
+ iter.add_slide_forward_right::<21>(self);
+ iter.add_slide_forward_right::<22>(self);
+ iter.add_slide_forward_right::<23>(self);
+ iter.add_slide_forward_right::<24>(self);
+ iter.add_slide_forward_right::<26>(self);
+ iter.add_slide_forward_right::<27>(self);
+ iter.add_slide_forward_right::<28>(self);
+ iter.add_slide_forward_right::<29>(self);
+ iter.add_slide_forward_right::<30>(self);
+
+ iter.add_slide_backward_left::<1>(self);
+ iter.add_slide_backward_left::<3>(self);
+ iter.add_slide_backward_left::<4>(self);
+ iter.add_slide_backward_left::<5>(self);
+ iter.add_slide_backward_left::<7>(self);
+ iter.add_slide_backward_left::<8>(self);
+ iter.add_slide_backward_left::<9>(self);
+ iter.add_slide_backward_left::<11>(self);
+ iter.add_slide_backward_left::<13>(self);
+ iter.add_slide_backward_left::<14>(self);
+ iter.add_slide_backward_left::<15>(self);
+ iter.add_slide_backward_left::<16>(self);
+ iter.add_slide_backward_left::<17>(self);
+ iter.add_slide_backward_left::<19>(self);
+ iter.add_slide_backward_left::<20>(self);
+ iter.add_slide_backward_left::<21>(self);
+ iter.add_slide_backward_left::<22>(self);
+ iter.add_slide_backward_left::<23>(self);
+ iter.add_slide_backward_left::<24>(self);
+ iter.add_slide_backward_left::<25>(self);
+ iter.add_slide_backward_left::<27>(self);
+ iter.add_slide_backward_left::<28>(self);
+ iter.add_slide_backward_left::<29>(self);
+ iter.add_slide_backward_left::<30>(self);
+ iter.add_slide_backward_left::<31>(self);
+
+ iter.add_slide_backward_right::<2>(self);
+ iter.add_slide_backward_right::<3>(self);
+ iter.add_slide_backward_right::<4>(self);
+ iter.add_slide_backward_right::<5>(self);
+ iter.add_slide_backward_right::<7>(self);
+ iter.add_slide_backward_right::<8>(self);
+ iter.add_slide_backward_right::<10>(self);
+ iter.add_slide_backward_right::<11>(self);
+ iter.add_slide_backward_right::<13>(self);
+ iter.add_slide_backward_right::<14>(self);
+ iter.add_slide_backward_right::<15>(self);
+ iter.add_slide_backward_right::<16>(self);
+ iter.add_slide_backward_right::<19>(self);
+ iter.add_slide_backward_right::<20>(self);
+ iter.add_slide_backward_right::<21>(self);
+ iter.add_slide_backward_right::<22>(self);
+ iter.add_slide_backward_right::<23>(self);
+ iter.add_slide_backward_right::<24>(self);
+ iter.add_slide_backward_right::<26>(self);
+ iter.add_slide_backward_right::<27>(self);
+ iter.add_slide_backward_right::<28>(self);
+ iter.add_slide_backward_right::<29>(self);
+ iter.add_slide_backward_right::<30>(self);
+ iter.add_slide_backward_right::<31>(self);
+ }
+
+ iter
+ }
+}
+
+impl PossibleMoves {
+ // TODO test
+
+ /// The highest possible number of valid moves
+ pub const MAX_POSSIBLE_MOVES: usize = POSSIBLE_MOVES_ITER_SIZE;
+
+ const fn slides_dark(board: CheckersBitBoard) -> Self {
+ const FORWARD_LEFT_MASK: u32 = 0b01111001111110111111001111011011;
+ const FORWARD_RIGHT_MASK: u32 = 0b01111101111111011111010111011101;
+ const BACKWARD_LEFT_MASK: u32 = 0b11111011111110111110101110111010;
+ const BACKWARD_RIGHT_MASK: u32 = 0b11111001111110011110110110111100;
+
+ let not_occupied = !board.pieces_bits();
+ let friendly_pieces = board.pieces_bits() & board.color_bits();
+ let friendly_kings = friendly_pieces & board.king_bits();
+
+ let forward_left_movers =
+ not_occupied.rotate_right(7) & friendly_pieces & FORWARD_LEFT_MASK;
+ let forward_right_movers =
+ not_occupied.rotate_right(1) & friendly_pieces & FORWARD_RIGHT_MASK;
+ let backward_left_movers;
+ let backward_right_movers;
+
+ if friendly_kings > 0 {
+ backward_left_movers =
+ not_occupied.rotate_left(1) & friendly_kings & BACKWARD_LEFT_MASK;
+ backward_right_movers =
+ not_occupied.rotate_left(7) & friendly_kings & BACKWARD_RIGHT_MASK;
+ } else {
+ backward_left_movers = 0;
+ backward_right_movers = 0;
+ }
+
+ Self {
+ forward_left_movers,
+ forward_right_movers,
+ backward_left_movers,
+ backward_right_movers,
+ }
+ }
+
+ const fn slides_light(board: CheckersBitBoard) -> Self {
+ const FORWARD_LEFT_MASK: u32 = 0b01111001111110111111001111011011;
+ const FORWARD_RIGHT_MASK: u32 = 0b01111101111111011111010111011101;
+ const BACKWARD_LEFT_MASK: u32 = 0b11111011111110111110101110111010;
+ const BACKWARD_RIGHT_MASK: u32 = 0b11111001111110011110110110111100;
+
+ let not_occupied = !board.pieces_bits();
+ let friendly_pieces = board.pieces_bits() & !board.color_bits();
+ let friendly_kings = friendly_pieces & board.king_bits();
+
+ let backward_left_movers =
+ not_occupied.rotate_left(1) & friendly_pieces & BACKWARD_LEFT_MASK;
+ let backward_right_movers =
+ not_occupied.rotate_left(7) & friendly_pieces & BACKWARD_RIGHT_MASK;
+ let forward_left_movers;
+ let forward_right_movers;
+
+ if friendly_kings > 0 {
+ forward_left_movers = not_occupied.rotate_right(7) & friendly_kings & FORWARD_LEFT_MASK;
+ forward_right_movers =
+ not_occupied.rotate_right(1) & friendly_kings & FORWARD_RIGHT_MASK;
+ } else {
+ forward_left_movers = 0;
+ forward_right_movers = 0;
+ }
+
+ Self {
+ forward_left_movers,
+ forward_right_movers,
+ backward_left_movers,
+ backward_right_movers,
+ }
+ }
+
+ const fn jumps_dark(board: CheckersBitBoard) -> Self {
+ const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011;
+ const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100;
+ const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000;
+ const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100;
+
+ let not_occupied = !board.pieces_bits();
+ let enemy_pieces = board.pieces_bits() & !board.color_bits();
+ let friendly_pieces = board.pieces_bits() & board.color_bits();
+ let friendly_kings = friendly_pieces & board.king_bits();
+
+ let forward_left_movers = not_occupied.rotate_right(14)
+ & enemy_pieces.rotate_right(7)
+ & friendly_pieces
+ & FORWARD_LEFT_MASK;
+ let forward_right_movers = not_occupied.rotate_right(2)
+ & enemy_pieces.rotate_right(1)
+ & friendly_pieces
+ & FORWARD_RIGHT_MASK;
+ let backward_left_movers;
+ let backward_right_movers;
+
+ if friendly_kings > 0 {
+ backward_left_movers = not_occupied.rotate_left(2)
+ & enemy_pieces.rotate_left(1)
+ & friendly_kings & BACKWARD_LEFT_MASK;
+ backward_right_movers = not_occupied.rotate_left(14)
+ & enemy_pieces.rotate_left(7)
+ & friendly_kings & BACKWARD_RIGHT_MASK;
+ } else {
+ backward_left_movers = 0;
+ backward_right_movers = 0;
+ }
+
+ let can_jump = if forward_left_movers != 0
+ || forward_right_movers != 0
+ || backward_left_movers != 0
+ || backward_right_movers != 0
+ {
+ 2
+ } else {
+ 0
+ };
+
+ Self {
+ forward_left_movers,
+ forward_right_movers,
+ backward_left_movers,
+ backward_right_movers: backward_right_movers | can_jump,
+ }
+ }
+
+ const fn jumps_light(board: CheckersBitBoard) -> Self {
+ const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011;
+ const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100;
+ const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000;
+ const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100;
+
+ let not_occupied = !board.pieces_bits();
+ let enemy_pieces = board.pieces_bits() & board.color_bits();
+ let friendly_pieces = board.pieces_bits() & !board.color_bits();
+ let friendly_kings = friendly_pieces & board.king_bits();
+
+ let backward_left_movers = not_occupied.rotate_left(2)
+ & enemy_pieces.rotate_left(1)
+ & friendly_pieces
+ & BACKWARD_LEFT_MASK;
+ let backward_right_movers = not_occupied.rotate_left(14)
+ & enemy_pieces.rotate_left(7)
+ & friendly_pieces
+ & BACKWARD_RIGHT_MASK;
+ let forward_left_movers;
+ let forward_right_movers;
+
+ if friendly_kings > 0 {
+ forward_left_movers = not_occupied.rotate_right(14)
+ & enemy_pieces.rotate_right(7)
+ & friendly_kings & FORWARD_LEFT_MASK;
+ forward_right_movers = not_occupied.rotate_right(2)
+ & enemy_pieces.rotate_right(1)
+ & friendly_kings & FORWARD_RIGHT_MASK;
+ } else {
+ forward_left_movers = 0;
+ forward_right_movers = 0;
+ }
+
+ let can_jump = if forward_left_movers != 0
+ || forward_right_movers != 0
+ || backward_left_movers != 0
+ || backward_right_movers != 0
+ {
+ 2
+ } else {
+ 0
+ };
+
+ Self {
+ forward_left_movers,
+ forward_right_movers,
+ backward_left_movers,
+ backward_right_movers: backward_right_movers | can_jump,
+ }
+ }
+
+ const fn has_jumps_dark(board: CheckersBitBoard) -> bool {
+ const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011;
+ const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100;
+ const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000;
+ const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100;
+
+ let not_occupied = !board.pieces_bits();
+ let enemy_pieces = board.pieces_bits() & !board.color_bits();
+ let friendly_pieces = board.pieces_bits() & board.color_bits();
+
+ let forward_left_spaces =
+ not_occupied.rotate_right(14) & enemy_pieces.rotate_right(7) & FORWARD_LEFT_MASK;
+ let forward_right_spaces =
+ not_occupied.rotate_right(2) & enemy_pieces.rotate_right(1) & FORWARD_RIGHT_MASK;
+
+ let forward_spaces = forward_left_spaces | forward_right_spaces;
+
+ if board.king_bits() > 0 {
+ let backward_left_spaces =
+ not_occupied.rotate_left(2) & enemy_pieces.rotate_left(1) & BACKWARD_LEFT_MASK;
+ let backward_right_spaces =
+ not_occupied.rotate_left(14) & enemy_pieces.rotate_left(7) & BACKWARD_RIGHT_MASK;
+ let backward_spaces = backward_left_spaces | backward_right_spaces;
+
+ let backward_spaces = board.king_bits() & backward_spaces;
+ friendly_pieces & (forward_spaces | backward_spaces) != 0
+ } else {
+ friendly_pieces & forward_spaces != 0
+ }
+ }
+
+ const fn has_jumps_light(board: CheckersBitBoard) -> bool {
+ const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011;
+ const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100;
+ const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000;
+ const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100;
+
+ let not_occupied = !board.pieces_bits();
+ let enemy_pieces = board.pieces_bits() & board.color_bits();
+ let friendly_pieces = board.pieces_bits() & !board.color_bits();
+
+ let backward_left_spaces =
+ not_occupied.rotate_left(2) & enemy_pieces.rotate_left(1) & BACKWARD_LEFT_MASK;
+ let backward_right_spaces =
+ not_occupied.rotate_left(14) & enemy_pieces.rotate_left(7) & BACKWARD_RIGHT_MASK;
+
+ let backward_spaces = backward_left_spaces | backward_right_spaces;
+
+ if board.king_bits() > 0 {
+ let forward_left_spaces =
+ not_occupied.rotate_right(14) & enemy_pieces.rotate_right(7) & FORWARD_LEFT_MASK;
+ let forward_right_spaces =
+ not_occupied.rotate_right(2) & enemy_pieces.rotate_right(1) & FORWARD_RIGHT_MASK;
+ let forward_spaces = forward_left_spaces | forward_right_spaces;
+
+ let forward_spaces = board.king_bits() & forward_spaces;
+ friendly_pieces & (forward_spaces | backward_spaces) != 0
+ } else {
+ friendly_pieces & backward_spaces != 0
+ }
+ }
+
+ #[inline(always)]
+ // TODO optimize
+ pub const fn has_jumps(board: CheckersBitBoard) -> bool {
+ match board.turn() {
+ PieceColor::Light => Self::has_jumps_light(board),
+ PieceColor::Dark => Self::has_jumps_dark(board),
+ }
+ }
+
+ const fn has_jumps_at_dark(board: CheckersBitBoard, value: usize) -> bool {
+ const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011;
+ const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100;
+ const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000;
+ const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100;
+
+ let not_occupied = !board.pieces_bits();
+ let enemy_pieces = board.pieces_bits() & !board.color_bits();
+ let friendly_pieces = board.pieces_bits() & board.color_bits();
+
+ let forward_left_spaces =
+ not_occupied.rotate_right(14) & enemy_pieces.rotate_right(7) & FORWARD_LEFT_MASK;
+ let forward_right_spaces =
+ not_occupied.rotate_right(2) & enemy_pieces.rotate_right(1) & FORWARD_RIGHT_MASK;
+
+ let forward_spaces = forward_left_spaces | forward_right_spaces;
+
+ if board.king_bits() > 0 {
+ let backward_left_spaces =
+ not_occupied.rotate_left(2) & enemy_pieces.rotate_left(1) & BACKWARD_LEFT_MASK;
+ let backward_right_spaces =
+ not_occupied.rotate_left(14) & enemy_pieces.rotate_left(7) & BACKWARD_RIGHT_MASK;
+ let backward_spaces = backward_left_spaces | backward_right_spaces;
+
+ let backward_spaces = board.king_bits() & backward_spaces;
+ ((friendly_pieces & (forward_spaces | backward_spaces)) >> value) & 1 != 0
+ } else {
+ ((friendly_pieces & forward_spaces) >> value) & 1 != 0
+ }
+ }
+
+ const fn has_jumps_at_light(board: CheckersBitBoard, value: usize) -> bool {
+ const FORWARD_LEFT_MASK: u32 = 0b00110000111100111111001111000011;
+ const FORWARD_RIGHT_MASK: u32 = 0b00111100111111001111000011001100;
+ const BACKWARD_LEFT_MASK: u32 = 0b11110011111100111100001100110000;
+ const BACKWARD_RIGHT_MASK: u32 = 0b11111100111100001100110000111100;
+
+ let not_occupied = !board.pieces_bits();
+ let enemy_pieces = board.pieces_bits() & board.color_bits();
+ let friendly_pieces = board.pieces_bits() & !board.color_bits();
+
+ let backward_left_spaces =
+ not_occupied.rotate_left(2) & enemy_pieces.rotate_left(1) & BACKWARD_LEFT_MASK;
+ let backward_right_spaces =
+ not_occupied.rotate_left(14) & enemy_pieces.rotate_left(7) & BACKWARD_RIGHT_MASK;
+
+ let backward_spaces = backward_left_spaces | backward_right_spaces;
+
+ if board.king_bits() > 0 {
+ let forward_left_spaces =
+ not_occupied.rotate_right(14) & enemy_pieces.rotate_right(7) & FORWARD_LEFT_MASK;
+ let forward_right_spaces =
+ not_occupied.rotate_right(2) & enemy_pieces.rotate_right(1) & FORWARD_RIGHT_MASK;
+ let forward_spaces = forward_left_spaces | forward_right_spaces;
+
+ let forward_spaces = board.king_bits() & forward_spaces;
+ ((friendly_pieces & (forward_spaces | backward_spaces)) >> value) & 1 != 0
+ } else {
+ ((friendly_pieces & backward_spaces) >> value) & 1 != 0
+ }
+ }
+
+ #[inline(always)]
+ // TODO optimize
+ pub const fn has_jumps_at(board: CheckersBitBoard, value: usize) -> bool {
+ match board.turn() {
+ PieceColor::Light => Self::has_jumps_at_light(board, value),
+ PieceColor::Dark => Self::has_jumps_at_dark(board, value),
+ }
+ }
+
+ const fn light_moves(board: CheckersBitBoard) -> Self {
+ let jumps = Self::jumps_light(board);
+ if jumps.is_empty() {
+ Self::slides_light(board)
+ } else {
+ jumps
+ }
+ }
+
+ const fn dark_moves(board: CheckersBitBoard) -> Self {
+ let jumps = Self::jumps_dark(board);
+ if jumps.is_empty() {
+ Self::slides_dark(board)
+ } else {
+ jumps
+ }
+ }
+
+ const fn filter_to_square(self, square: u8) -> Self {
+ let mask = 1 << square;
+ Self {
+ forward_left_movers: self.forward_left_movers & mask,
+ forward_right_movers: self.forward_right_movers & mask,
+ backward_left_movers: self.backward_left_movers & mask,
+ backward_right_movers: self.backward_right_movers & (mask | 2),
+ }
+ }
+
+ pub fn moves(board: CheckersBitBoard) -> Self {
+ let moves = match board.turn() {
+ PieceColor::Dark => Self::dark_moves(board),
+ PieceColor::Light => Self::light_moves(board),
+ };
+
+ if board.turn == board.previous_turn {
+ moves.filter_to_square(board.previous_move_to)
+ } else {
+ moves
+ }
+ }
+
+ /// Returns true if no moves are possible
+ pub const fn is_empty(self) -> bool {
+ (self.backward_left_movers
+ | (self.forward_left_movers)
+ | self.forward_right_movers
+ | self.backward_right_movers & 4294967293)
+ == 0
+ }
+
+ /// Returns true if the piece can jump
+ pub const fn can_jump(self) -> bool {
+ (self.backward_right_movers & 2) != 0
+ }
+
+ /// Returns true if the given move is possible
+ pub const fn contains(self, checker_move: Move) -> bool {
+ if checker_move.is_jump() != self.can_jump() {
+ return false;
+ }
+
+ let bits = match checker_move.direction() {
+ MoveDirection::ForwardLeft => self.forward_left_movers,
+ MoveDirection::ForwardRight => self.forward_right_movers,
+ MoveDirection::BackwardLeft => self.backward_left_movers,
+ MoveDirection::BackwardRight => self.backward_right_movers,
+ };
+
+ (bits >> checker_move.start()) & 1 == 1
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn setup_empty_iter() -> PossibleMovesIter {
+ let moves = [MaybeUninit::uninit(); POSSIBLE_MOVES_ITER_SIZE];
+ PossibleMovesIter {
+ moves,
+ index: 0,
+ length: 0,
+ }
+ }
+
+ fn setup_add_move_to_iter_invalid() -> (PossibleMovesIter, PossibleMoves) {
+ let moves = PossibleMoves {
+ forward_left_movers: 0,
+ forward_right_movers: 0,
+ backward_left_movers: 0,
+ backward_right_movers: 0,
+ };
+ let iter = setup_empty_iter();
+
+ (iter, moves)
+ }
+
+ fn setup_add_move_to_iter_valid() -> (PossibleMovesIter, PossibleMoves) {
+ let moves = PossibleMoves {
+ forward_left_movers: u32::MAX,
+ forward_right_movers: u32::MAX,
+ backward_left_movers: u32::MAX,
+ backward_right_movers: u32::MAX,
+ };
+ let iter = setup_empty_iter();
+
+ (iter, moves)
+ }
+
+ #[test]
+ fn same() {
+ let start = CheckersBitBoard::new(
+ 0b11100111100111100111110111111011,
+ 0b00001100001111001111001111000011,
+ 0,
+ PieceColor::Dark,
+ );
+ let flip = CheckersBitBoard::new(
+ 0b11100111100111100111110111111011,
+ 0b11110011110000110000110000111100,
+ 0,
+ PieceColor::Light,
+ );
+
+ assert_eq!(
+ PossibleMoves::has_jumps(start),
+ PossibleMoves::has_jumps(flip)
+ )
+ }
+
+ #[test]
+ fn iter_next() {
+ let test_move1 = Move::new(8, MoveDirection::ForwardLeft, false);
+ let test_move2 = Move::new(26, MoveDirection::ForwardRight, true);
+ let mut iter = setup_empty_iter();
+ iter.length = 2;
+
+ let ptr = iter.moves.as_mut().get_mut(0).unwrap();
+ *ptr = MaybeUninit::new(test_move1);
+
+ let ptr = iter.moves.as_mut().get_mut(1).unwrap();
+ *ptr = MaybeUninit::new(test_move2);
+
+ let recieved_move = iter.next();
+ assert!(recieved_move.is_some());
+ assert_eq!(recieved_move.unwrap(), test_move1);
+
+ let recieved_move = iter.next();
+ assert!(recieved_move.is_some());
+ assert_eq!(recieved_move.unwrap(), test_move2);
+
+ let recieved_move = iter.next();
+ assert!(recieved_move.is_none());
+ }
+
+ #[test]
+ fn add_slide_forward_left_to_iter_invalid() {
+ const START: usize = 8;
+ let (mut iter, moves) = setup_add_move_to_iter_invalid();
+ iter.add_slide_forward_left::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 0);
+ }
+
+ #[test]
+ fn add_slide_forward_left_to_iter_valid() {
+ const START: usize = 8;
+ let (mut iter, moves) = setup_add_move_to_iter_valid();
+ iter.add_slide_forward_left::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 1);
+
+ let new_move = iter.next().unwrap();
+ assert_eq!(new_move.start(), START as u32);
+ assert_eq!(new_move.direction(), MoveDirection::ForwardLeft);
+ assert!(!new_move.is_jump());
+ }
+
+ #[test]
+ fn add_slide_forward_right_to_iter_invalid() {
+ const START: usize = 26;
+ let (mut iter, moves) = setup_add_move_to_iter_invalid();
+ iter.add_slide_forward_right::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 0);
+ }
+
+ #[test]
+ fn add_slide_forward_right_to_iter_valid() {
+ const START: usize = 26;
+ let (mut iter, moves) = setup_add_move_to_iter_valid();
+ iter.add_slide_forward_right::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 1);
+
+ let new_move = iter.next().unwrap();
+ assert_eq!(new_move.start(), START as u32);
+ assert_eq!(new_move.direction(), MoveDirection::ForwardRight);
+ assert!(!new_move.is_jump());
+ }
+
+ #[test]
+ fn add_slide_backward_left_to_iter_invalid() {
+ const START: usize = 17;
+ let (mut iter, moves) = setup_add_move_to_iter_invalid();
+ iter.add_slide_backward_left::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 0);
+ }
+
+ #[test]
+ fn add_slide_backward_left_to_iter_valid() {
+ const START: usize = 17;
+ let (mut iter, moves) = setup_add_move_to_iter_valid();
+ iter.add_slide_backward_left::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 1);
+
+ let new_move = iter.next().unwrap();
+ assert_eq!(new_move.start(), START as u32);
+ assert_eq!(new_move.direction(), MoveDirection::BackwardLeft);
+ assert!(!new_move.is_jump());
+ }
+
+ #[test]
+ fn add_slide_backward_right_to_iter_invalid() {
+ const START: usize = 3;
+ let (mut iter, moves) = setup_add_move_to_iter_invalid();
+ iter.add_slide_backward_right::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 0);
+ }
+
+ #[test]
+ fn add_slide_backward_right_to_iter_valid() {
+ const START: usize = 3;
+ let (mut iter, moves) = setup_add_move_to_iter_valid();
+ iter.add_slide_backward_right::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 1);
+
+ let new_move = iter.next().unwrap();
+ assert_eq!(new_move.start(), START as u32);
+ assert_eq!(new_move.direction(), MoveDirection::BackwardRight);
+ assert!(!new_move.is_jump());
+ }
+
+ #[test]
+ fn add_jump_forward_left_to_iter_invalid() {
+ const START: usize = 8;
+ let (mut iter, moves) = setup_add_move_to_iter_invalid();
+ iter.add_jump_forward_left::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 0);
+ }
+
+ #[test]
+ fn add_jump_forward_left_to_iter_valid() {
+ const START: usize = 8;
+ let (mut iter, moves) = setup_add_move_to_iter_valid();
+ iter.add_jump_forward_left::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 1);
+
+ let new_move = iter.next().unwrap();
+ assert_eq!(new_move.start(), START as u32);
+ assert_eq!(new_move.direction(), MoveDirection::ForwardLeft);
+ assert!(new_move.is_jump());
+ }
+
+ #[test]
+ fn add_jump_forward_right_to_iter_invalid() {
+ const START: usize = 26;
+ let (mut iter, moves) = setup_add_move_to_iter_invalid();
+ iter.add_jump_forward_right::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 0);
+ }
+
+ #[test]
+ fn add_jump_forward_right_to_iter_valid() {
+ const START: usize = 26;
+ let (mut iter, moves) = setup_add_move_to_iter_valid();
+ iter.add_jump_forward_right::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 1);
+
+ let new_move = iter.next().unwrap();
+ assert_eq!(new_move.start(), START as u32);
+ assert_eq!(new_move.direction(), MoveDirection::ForwardRight);
+ assert!(new_move.is_jump());
+ }
+
+ #[test]
+ fn add_jump_backward_left_to_iter_invalid() {
+ const START: usize = 17;
+ let (mut iter, moves) = setup_add_move_to_iter_invalid();
+ iter.add_jump_backward_left::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 0);
+ }
+
+ #[test]
+ fn add_jump_backward_left_to_iter_valid() {
+ const START: usize = 17;
+ let (mut iter, moves) = setup_add_move_to_iter_valid();
+ iter.add_jump_backward_left::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 1);
+
+ let new_move = iter.next().unwrap();
+ assert_eq!(new_move.start(), START as u32);
+ assert_eq!(new_move.direction(), MoveDirection::BackwardLeft);
+ assert!(new_move.is_jump());
+ }
+
+ #[test]
+ fn add_jump_backward_right_to_iter_invalid() {
+ const START: usize = 3;
+ let (mut iter, moves) = setup_add_move_to_iter_invalid();
+ iter.add_jump_backward_right::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 0);
+ }
+
+ #[test]
+ fn add_jump_backward_right_to_iter_valid() {
+ const START: usize = 3;
+ let (mut iter, moves) = setup_add_move_to_iter_valid();
+ iter.add_jump_backward_right::<START>(moves);
+
+ assert_eq!(iter.index, 0);
+ assert_eq!(iter.length, 1);
+
+ let new_move = iter.next().unwrap();
+ assert_eq!(new_move.start(), START as u32);
+ assert_eq!(new_move.direction(), MoveDirection::BackwardRight);
+ assert!(new_move.is_jump());
+ }
+
+ #[test]
+ fn cant_jump_in_position_2_without_26() {
+ // This bug was bizarre, but it's caused by a white piece being in the
+ //second bit while there is no piece in the 26th bit. If you don't
+ // apply the bit mask for collision detection, then all of the light
+ // player moves become jumps.
+ let board = CheckersBitBoard::new(16908890, 401395713, 50332352, PieceColor::Light);
+ let possible_moves = PossibleMoves::moves(board);
+ assert!(!possible_moves.can_jump())
+ }
+
+ #[test]
+ fn not_has_jump_at_14_when_has_jump_at_20() {
+ // This bug was caused by me forgetting to `& 1` to the end of the
+ // `has_jump_at` functions. After playing a jump with one piece, I was
+ // able to continue jumping with completely different pieces
+ let board = CheckersBitBoard::new(
+ 0b11100111001111001111110111111011,
+ 0b00001100001111001111001111000011,
+ 0,
+ PieceColor::Dark,
+ );
+ let possible_moves = PossibleMoves::moves(board);
+ assert!(!possible_moves.can_jump())
+ }
+
+ #[test]
+ fn test_send() {
+ fn assert_send<T: Send>() {}
+ assert_send::<PossibleMoves>();
+ assert_send::<PossibleMovesIter>();
+ }
+
+ #[test]
+ fn test_sync() {
+ fn assert_sync<T: Sync>() {}
+ assert_sync::<PossibleMoves>();
+ assert_sync::<PossibleMovesIter>();
+ }
+}
|
