summaryrefslogtreecommitdiff
path: root/model
diff options
context:
space:
mode:
Diffstat (limited to 'model')
-rwxr-xr-x[-rw-r--r--]model/Cargo.toml36
-rwxr-xr-x[-rw-r--r--]model/benches/bitboard.rs182
-rwxr-xr-x[-rw-r--r--]model/proptest-regressions/board/tests.txt0
-rwxr-xr-x[-rw-r--r--]model/src/board.rs1346
-rwxr-xr-x[-rw-r--r--]model/src/board/tests.rs1114
-rwxr-xr-x[-rw-r--r--]model/src/color.rs0
-rwxr-xr-x[-rw-r--r--]model/src/coordinates.rs304
-rwxr-xr-x[-rw-r--r--]model/src/lib.rs26
-rwxr-xr-x[-rw-r--r--]model/src/moves.rs590
-rwxr-xr-x[-rw-r--r--]model/src/piece.rs42
-rwxr-xr-x[-rw-r--r--]model/src/possible_moves.rs2156
11 files changed, 2898 insertions, 2898 deletions
diff --git a/model/Cargo.toml b/model/Cargo.toml
index e732e99..40e8cb8 100644..100755
--- a/model/Cargo.toml
+++ b/model/Cargo.toml
@@ -1,19 +1,19 @@
-[package]
-name = "model"
-version = "0.1.0"
-authors = ["Mica White <botahamec@outlook.com>"]
-edition = "2021"
-publish = false
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-serde = { version = "1", optional = true, features = ["derive"] }
-
-[dev-dependencies]
-proptest = "1"
-criterion = "0.3"
-
-[[bench]]
-name = "bitboard"
+[package]
+name = "model"
+version = "0.1.0"
+authors = ["Mica White <botahamec@outlook.com>"]
+edition = "2021"
+publish = false
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+serde = { version = "1", optional = true, features = ["derive"] }
+
+[dev-dependencies]
+proptest = "1"
+criterion = "0.3"
+
+[[bench]]
+name = "bitboard"
harness = false \ No newline at end of file
diff --git a/model/benches/bitboard.rs b/model/benches/bitboard.rs
index 18d1a84..db70d65 100644..100755
--- a/model/benches/bitboard.rs
+++ b/model/benches/bitboard.rs
@@ -1,91 +1,91 @@
-use criterion::{black_box, criterion_group, criterion_main, Criterion};
-use model::CheckersBitBoard;
-use std::collections::hash_map::DefaultHasher;
-use std::hash::Hash;
-
-fn clone(c: &mut Criterion) {
- let board = CheckersBitBoard::starting_position();
- c.bench_function("clone", |b| b.iter(|| black_box(board)));
-}
-
-fn hash(c: &mut Criterion) {
- let board = CheckersBitBoard::starting_position();
- let mut hasher = DefaultHasher::new();
- c.bench_function("hash", |b| b.iter(|| board.hash(black_box(&mut hasher))));
-}
-
-fn default(c: &mut Criterion) {
- c.bench_function("default", |b| {
- b.iter(|| black_box(CheckersBitBoard::default()))
- });
-}
-
-fn eq(c: &mut Criterion) {
- let board1 = CheckersBitBoard::default();
- let board2 = CheckersBitBoard::default();
- c.bench_function("equal", |b| {
- b.iter(|| black_box(board1) == black_box(board2))
- });
-}
-
-fn default_const(c: &mut Criterion) {
- c.bench_function("default (const)", |b| {
- b.iter(|| black_box(CheckersBitBoard::starting_position()))
- });
-}
-
-fn piece_at(c: &mut Criterion) {
- let board = CheckersBitBoard::starting_position();
- c.bench_function("piece", |b| b.iter(|| board.piece_at(black_box(0))));
-}
-
-fn color_at_unchecked(c: &mut Criterion) {
- let board = CheckersBitBoard::starting_position();
- c.bench_function("color (unsafe)", |b| {
- b.iter(|| unsafe { board.color_at_unchecked(black_box(1)) })
- });
-}
-
-fn king_at_unchecked(c: &mut Criterion) {
- let board = CheckersBitBoard::starting_position();
- c.bench_function("king (unsafe)", |b| {
- b.iter(|| unsafe { board.king_at_unchecked(black_box(2)) })
- });
-}
-
-fn color_at(c: &mut Criterion) {
- let board = CheckersBitBoard::starting_position();
- c.bench_function("color (safe - filled)", |b| {
- b.iter(|| board.color_at(black_box(3)))
- });
-
- c.bench_function("color (safe - empty)", |b| {
- b.iter(|| board.color_at(black_box(2)))
- });
-}
-
-fn king_at(c: &mut Criterion) {
- let board = CheckersBitBoard::starting_position();
- c.bench_function("king (safe - filled)", |b| {
- b.iter(|| board.king_at(black_box(4)))
- });
-
- c.bench_function("king (safe - empty)", |b| {
- b.iter(|| board.king_at(black_box(9)))
- });
-}
-
-criterion_group!(
- bitboard,
- clone,
- hash,
- eq,
- default,
- default_const,
- piece_at,
- color_at_unchecked,
- king_at_unchecked,
- color_at,
- king_at,
-);
-criterion_main!(bitboard);
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use model::CheckersBitBoard;
+use std::collections::hash_map::DefaultHasher;
+use std::hash::Hash;
+
+fn clone(c: &mut Criterion) {
+ let board = CheckersBitBoard::starting_position();
+ c.bench_function("clone", |b| b.iter(|| black_box(board)));
+}
+
+fn hash(c: &mut Criterion) {
+ let board = CheckersBitBoard::starting_position();
+ let mut hasher = DefaultHasher::new();
+ c.bench_function("hash", |b| b.iter(|| board.hash(black_box(&mut hasher))));
+}
+
+fn default(c: &mut Criterion) {
+ c.bench_function("default", |b| {
+ b.iter(|| black_box(CheckersBitBoard::default()))
+ });
+}
+
+fn eq(c: &mut Criterion) {
+ let board1 = CheckersBitBoard::default();
+ let board2 = CheckersBitBoard::default();
+ c.bench_function("equal", |b| {
+ b.iter(|| black_box(board1) == black_box(board2))
+ });
+}
+
+fn default_const(c: &mut Criterion) {
+ c.bench_function("default (const)", |b| {
+ b.iter(|| black_box(CheckersBitBoard::starting_position()))
+ });
+}
+
+fn piece_at(c: &mut Criterion) {
+ let board = CheckersBitBoard::starting_position();
+ c.bench_function("piece", |b| b.iter(|| board.piece_at(black_box(0))));
+}
+
+fn color_at_unchecked(c: &mut Criterion) {
+ let board = CheckersBitBoard::starting_position();
+ c.bench_function("color (unsafe)", |b| {
+ b.iter(|| unsafe { board.color_at_unchecked(black_box(1)) })
+ });
+}
+
+fn king_at_unchecked(c: &mut Criterion) {
+ let board = CheckersBitBoard::starting_position();
+ c.bench_function("king (unsafe)", |b| {
+ b.iter(|| unsafe { board.king_at_unchecked(black_box(2)) })
+ });
+}
+
+fn color_at(c: &mut Criterion) {
+ let board = CheckersBitBoard::starting_position();
+ c.bench_function("color (safe - filled)", |b| {
+ b.iter(|| board.color_at(black_box(3)))
+ });
+
+ c.bench_function("color (safe - empty)", |b| {
+ b.iter(|| board.color_at(black_box(2)))
+ });
+}
+
+fn king_at(c: &mut Criterion) {
+ let board = CheckersBitBoard::starting_position();
+ c.bench_function("king (safe - filled)", |b| {
+ b.iter(|| board.king_at(black_box(4)))
+ });
+
+ c.bench_function("king (safe - empty)", |b| {
+ b.iter(|| board.king_at(black_box(9)))
+ });
+}
+
+criterion_group!(
+ bitboard,
+ clone,
+ hash,
+ eq,
+ default,
+ default_const,
+ piece_at,
+ color_at_unchecked,
+ king_at_unchecked,
+ color_at,
+ king_at,
+);
+criterion_main!(bitboard);
diff --git a/model/proptest-regressions/board/tests.txt b/model/proptest-regressions/board/tests.txt
index 3261322..3261322 100644..100755
--- a/model/proptest-regressions/board/tests.txt
+++ b/model/proptest-regressions/board/tests.txt
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>();
+ }
+}