summaryrefslogtreecommitdiff
path: root/model/src/board.rs
diff options
context:
space:
mode:
Diffstat (limited to 'model/src/board.rs')
-rwxr-xr-x[-rw-r--r--]model/src/board.rs1346
1 files changed, 673 insertions, 673 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
+ }
+ }
+}