From fdb2804883deb31e3aeb15bbe588dcc9b7b76bd0 Mon Sep 17 00:00:00 2001 From: Mica White Date: Mon, 8 Dec 2025 19:56:48 -0500 Subject: Stuff --- model/Cargo.toml | 36 +- model/benches/bitboard.rs | 182 +-- model/proptest-regressions/board/tests.txt | 0 model/src/board.rs | 1346 ++++++++--------- model/src/board/tests.rs | 1114 +++++++------- model/src/color.rs | 0 model/src/coordinates.rs | 304 ++-- model/src/lib.rs | 26 +- model/src/moves.rs | 590 ++++---- model/src/piece.rs | 42 +- model/src/possible_moves.rs | 2156 ++++++++++++++-------------- 11 files changed, 2898 insertions(+), 2898 deletions(-) mode change 100644 => 100755 model/Cargo.toml mode change 100644 => 100755 model/benches/bitboard.rs mode change 100644 => 100755 model/proptest-regressions/board/tests.txt mode change 100644 => 100755 model/src/board.rs mode change 100644 => 100755 model/src/board/tests.rs mode change 100644 => 100755 model/src/color.rs mode change 100644 => 100755 model/src/coordinates.rs mode change 100644 => 100755 model/src/lib.rs mode change 100644 => 100755 model/src/moves.rs mode change 100644 => 100755 model/src/piece.rs mode change 100644 => 100755 model/src/possible_moves.rs (limited to 'model') diff --git a/model/Cargo.toml b/model/Cargo.toml old mode 100644 new mode 100755 index e732e99..40e8cb8 --- a/model/Cargo.toml +++ b/model/Cargo.toml @@ -1,19 +1,19 @@ -[package] -name = "model" -version = "0.1.0" -authors = ["Mica White "] -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 "] +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 old mode 100644 new mode 100755 index 18d1a84..db70d65 --- 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 old mode 100644 new mode 100755 diff --git a/model/src/board.rs b/model/src/board.rs old mode 100644 new mode 100755 index b722fd6..c6d6551 --- 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(&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 { - 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 { - 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 { - 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(&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 { + 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 { + 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 { + 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 old mode 100644 new mode 100755 index 8c119dc..9c356ae --- 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() {} - assert_send::(); -} - -#[test] -fn test_sync() { - fn assert_sync() {} - assert_sync::(); -} +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() {} + assert_send::(); +} + +#[test] +fn test_sync() { + fn assert_sync() {} + assert_sync::(); +} diff --git a/model/src/color.rs b/model/src/color.rs old mode 100644 new mode 100755 diff --git a/model/src/coordinates.rs b/model/src/coordinates.rs old mode 100644 new mode 100755 index 0f45322..d16f900 --- 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 { - 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 { - 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 { + 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 { + 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 old mode 100644 new mode 100755 index b3d8007..76a7419 --- 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 old mode 100644 new mode 100755 index c840e8f..c6dd060 --- 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 old mode 100644 new mode 100755 index f36e0a4..860142d --- 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 old mode 100644 new mode 100755 index ef05048..40d9df7 --- 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; 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(&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(&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(&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(&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(&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(&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(&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(&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 { - 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) { - 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 - 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 { - 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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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() {} - assert_send::(); - assert_send::(); - } - - #[test] - fn test_sync() { - fn assert_sync() {} - assert_sync::(); - assert_sync::(); - } -} +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; 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(&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(&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(&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(&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(&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(&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(&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(&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 { + 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) { + 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 + 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 { + 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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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() {} + assert_send::(); + assert_send::(); + } + + #[test] + fn test_sync() { + fn assert_sync() {} + assert_sync::(); + assert_sync::(); + } +} -- cgit v1.2.3