diff options
| -rw-r--r-- | cli/src/perft.rs | 52 | ||||
| -rw-r--r-- | model/benches/bitboard.rs | 194 | ||||
| -rw-r--r-- | model/src/board.rs | 1178 | ||||
| -rw-r--r-- | model/src/board/tests.rs | 23 | ||||
| -rw-r--r-- | model/src/color.rs | 151 | ||||
| -rw-r--r-- | model/src/moves.rs | 148 | ||||
| -rw-r--r-- | model/src/moves_iter.rs | 287 | ||||
| -rw-r--r-- | model/src/possible_moves.rs | 671 |
8 files changed, 1408 insertions, 1296 deletions
diff --git a/cli/src/perft.rs b/cli/src/perft.rs index eba640e..535aec0 100644 --- a/cli/src/perft.rs +++ b/cli/src/perft.rs @@ -1,26 +1,26 @@ -use ai::{CheckersBitBoard, Move, PossibleMoves};
-use rayon::prelude::*;
-use std::fmt::{Display, Formatter};
-
-#[derive(Clone)]
-struct PerftResult {
- result: Vec<(Move, usize)>,
-}
-
-pub fn positions(board: CheckersBitBoard, depth: usize) -> usize {
- let moves = PossibleMoves::moves(board);
-
- if depth == 0 {
- 1
- } else {
- let mut total = 0;
-
- for current_move in moves {
- // safety: we got this move out of the list of possible moves, so it's definitely valid
- let board = unsafe { current_move.apply_to(board) };
- total += positions(board, depth - 1);
- }
-
- total
- }
-}
+use ai::{CheckersBitBoard, Move, PossibleMoves}; +use rayon::prelude::*; +use std::fmt::{Display, Formatter}; + +#[derive(Clone)] +struct PerftResult { + result: Vec<(Move, usize)>, +} + +pub fn positions(board: CheckersBitBoard, depth: usize) -> usize { + let moves = PossibleMoves::moves(board); + + if depth == 0 { + 1 + } else { + let mut total = 0; + + for current_move in moves { + // safety: we got this move out of the list of possible moves, so it's definitely valid + let board = unsafe { current_move.apply_to(board) }; + total += positions(board, depth - 1); + } + + total + } +} diff --git a/model/benches/bitboard.rs b/model/benches/bitboard.rs index 231f860..f1dd9e1 100644 --- a/model/benches/bitboard.rs +++ b/model/benches/bitboard.rs @@ -1,96 +1,98 @@ -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.clone())));
-}
-
-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 new(c: &mut Criterion) {
- c.bench_function("new", |b| {
- b.iter(|| CheckersBitBoard::new(black_box(7328), black_box(174), black_box(27590)))
- });
-}
-
-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,
- new,
- 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.clone()))); +} + +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 new(c: &mut Criterion) { + c.bench_function("new", |b| { + b.iter(|| CheckersBitBoard::new(black_box(7328), black_box(174), black_box(27590))) + }); +} + +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, + new, + piece_at, + color_at_unchecked, + king_at_unchecked, + color_at, + king_at, +); +criterion_main!(bitboard); diff --git a/model/src/board.rs b/model/src/board.rs index 0128c89..75c29cb 100644 --- a/model/src/board.rs +++ b/model/src/board.rs @@ -1,589 +1,589 @@ -use crate::possible_moves::PossibleMoves;
-use crate::PieceColor;
-#[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
- pieces: u32,
- /// If the piece is black, 1, otherwise 0
- color: u32,
- /// 1 if the piece is a king
- kings: u32,
- /// The player who has the next turn
- turn: PieceColor,
-}
-
-impl Default for CheckersBitBoard {
- /// Returns the starting position
- fn default() -> Self {
- Self::starting_position()
- }
-}
-
-impl PartialEq for CheckersBitBoard {
- fn eq(&self, other: &Self) -> bool {
- self.pieces == other.pieces
- && self.pieces & self.color == other.pieces & other.color
- && self.pieces & self.kings == other.pieces & other.kings
- && self.turn == other.turn
- }
-}
-
-impl Hash for CheckersBitBoard {
- /// Hashes with only the pieces part, to ensure correctness and efficiency
- fn hash<H: Hasher>(&self, hasher: &mut H) {
- self.pieces.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);
- /// ```
- pub const fn new(pieces: u32, color: u32, kings: u32, turn: PieceColor) -> Self {
- Self {
- pieces,
- color,
- kings,
- turn,
- }
- }
-
- /// Creates a board at the starting position
- pub const fn starting_position() -> Self {
- const STARTING_BITBOARD: CheckersBitBoard = CheckersBitBoard::new(
- 0b11100111100111100111110111111011,
- 0b00001100001111001111001111000011,
- 0,
- PieceColor::Dark,
- );
- STARTING_BITBOARD
- }
-
- /// Gets the bits that represent where pieces are on the board
- 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
- 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
- pub const fn king_bits(self) -> u32 {
- self.kings
- }
-
- /// The player whose turn it is
- pub const fn turn(self) -> PieceColor {
- self.turn
- }
-
- /// 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
- 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
- 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
- pub const fn color_at(self, value: usize) -> Option<PieceColor> {
- if self.piece_at(value) {
- // safety: if this block runs, then it's already confirmed a piece exists here
- Some(unsafe { self.color_at_unchecked(value) })
- } else {
- None
- }
- }
-
- /// Checks if the given location has a king, without checking if there's a piece there
- ///
- /// # Arguments
- ///
- /// * `value` - The value of the space to check
- ///
- /// # Example
- ///
- /// ```
- /// use model::CheckersBitBoard;
- /// let board = CheckersBitBoard::default();
- /// if board.piece_at(0) {
- /// match unsafe {board.king_at_unchecked(0)} {
- /// true => println!("The piece in the bottom right is a king"),
- /// false => println!("The piece in the bottom right is a peasant")
- /// }
- /// }
- /// ```
- ///
- /// # Panics
- ///
- /// Panics if `value` is greater than or equal to 32
- ///
- /// # Safety
- ///
- /// Checking a square that is empty results in undefined behavior
- 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
- pub const fn king_at(self, value: usize) -> Option<bool> {
- if self.piece_at(value) {
- // safety: if this block runs, then it's already confirmed a piece exists here
- Some(unsafe { self.king_at_unchecked(value) })
- } else {
- None
- }
- }
-
- pub const fn flip_turn(self) -> Self {
- CheckersBitBoard::new(self.pieces, self.color, self.kings, self.turn.flip())
- }
-
- /// 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
- 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)
- | (self.color & DARK_PROMOTION_MASK)
- | (!self.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
- 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
- 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.
- 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.
- 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.
- 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.
- 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
- 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
- pub const unsafe fn jump_piece_forward_left_unchecked(self, value: usize) -> Self {
- let not_king = !self.king_at_unchecked(value);
- let board = self
- .move_piece_forward_unchecked(value, 14)
- .clear_piece((value + 7) & 31);
-
- const KING_MASK: u32 = 0b01000001000000000000010000010000;
- if PossibleMoves::has_jumps(board.flip_turn())
- && not_king && (((1 << value) & KING_MASK) == 0)
- {
- board.flip_turn()
- } 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
- pub const unsafe fn jump_piece_forward_right_unchecked(self, value: usize) -> Self {
- let not_king = !self.king_at_unchecked(value);
- let board = self
- .move_piece_forward_unchecked(value, 2)
- .clear_piece((value + 1) & 31);
-
- const KING_MASK: u32 = 0b01000001000000000000010000010000;
- if PossibleMoves::has_jumps(board.flip_turn())
- && not_king && (((1 << value) & KING_MASK) == 0)
- {
- board.flip_turn()
- } 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
- pub const unsafe fn jump_piece_backward_left_unchecked(self, value: usize) -> Self {
- let not_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 = 0b00000000000010000010000010000010;
- if PossibleMoves::has_jumps(board.flip_turn())
- && not_king && (((1 << value) & KING_MASK) == 0)
- {
- board.flip_turn()
- } 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
- pub const unsafe fn jump_piece_backward_right_unchecked(self, value: usize) -> Self {
- let not_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 = 0b00000000000010000010000010000010;
- if PossibleMoves::has_jumps(board.flip_turn())
- && not_king && (((1 << value) & KING_MASK) == 0)
- {
- board.flip_turn()
- } else {
- board
- }
- }
-}
+use crate::possible_moves::PossibleMoves; +use crate::PieceColor; +#[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 + pieces: u32, + /// If the piece is black, 1, otherwise 0 + color: u32, + /// 1 if the piece is a king + kings: u32, + /// The player who has the next turn + turn: PieceColor, +} + +impl Default for CheckersBitBoard { + /// Returns the starting position + fn default() -> Self { + Self::starting_position() + } +} + +impl PartialEq for CheckersBitBoard { + fn eq(&self, other: &Self) -> bool { + self.pieces == other.pieces + && self.pieces & self.color == other.pieces & other.color + && self.pieces & self.kings == other.pieces & other.kings + && self.turn == other.turn + } +} + +impl Hash for CheckersBitBoard { + /// Hashes with only the pieces part, to ensure correctness and efficiency + fn hash<H: Hasher>(&self, hasher: &mut H) { + self.pieces.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); + /// ``` + pub const fn new(pieces: u32, color: u32, kings: u32, turn: PieceColor) -> Self { + Self { + pieces, + color, + kings, + turn, + } + } + + /// Creates a board at the starting position + pub const fn starting_position() -> Self { + const STARTING_BITBOARD: CheckersBitBoard = CheckersBitBoard::new( + 0b11100111100111100111110111111011, + 0b00001100001111001111001111000011, + 0, + PieceColor::Dark, + ); + STARTING_BITBOARD + } + + /// Gets the bits that represent where pieces are on the board + 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 + 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 + pub const fn king_bits(self) -> u32 { + self.kings + } + + /// The player whose turn it is + pub const fn turn(self) -> PieceColor { + self.turn + } + + /// 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 + 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 + 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 + pub const fn color_at(self, value: usize) -> Option<PieceColor> { + if self.piece_at(value) { + // safety: if this block runs, then it's already confirmed a piece exists here + Some(unsafe { self.color_at_unchecked(value) }) + } else { + None + } + } + + /// Checks if the given location has a king, without checking if there's a piece there + /// + /// # Arguments + /// + /// * `value` - The value of the space to check + /// + /// # Example + /// + /// ``` + /// use model::CheckersBitBoard; + /// let board = CheckersBitBoard::default(); + /// if board.piece_at(0) { + /// match unsafe {board.king_at_unchecked(0)} { + /// true => println!("The piece in the bottom right is a king"), + /// false => println!("The piece in the bottom right is a peasant") + /// } + /// } + /// ``` + /// + /// # Panics + /// + /// Panics if `value` is greater than or equal to 32 + /// + /// # Safety + /// + /// Checking a square that is empty results in undefined behavior + 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 + pub const fn king_at(self, value: usize) -> Option<bool> { + if self.piece_at(value) { + // safety: if this block runs, then it's already confirmed a piece exists here + Some(unsafe { self.king_at_unchecked(value) }) + } else { + None + } + } + + pub const fn flip_turn(self) -> Self { + CheckersBitBoard::new(self.pieces, self.color, self.kings, self.turn.flip()) + } + + /// 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 + 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) + | (self.color & DARK_PROMOTION_MASK) + | (!self.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 + 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 + 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. + 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. + 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. + 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. + 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 + 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 + pub const unsafe fn jump_piece_forward_left_unchecked(self, value: usize) -> Self { + let not_king = !self.king_at_unchecked(value); + let board = self + .move_piece_forward_unchecked(value, 14) + .clear_piece((value + 7) & 31); + + const KING_MASK: u32 = 0b01000001000000000000010000010000; + if PossibleMoves::has_jumps(board.flip_turn()) + && not_king && (((1 << value) & KING_MASK) == 0) + { + board.flip_turn() + } 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 + pub const unsafe fn jump_piece_forward_right_unchecked(self, value: usize) -> Self { + let not_king = !self.king_at_unchecked(value); + let board = self + .move_piece_forward_unchecked(value, 2) + .clear_piece((value + 1) & 31); + + const KING_MASK: u32 = 0b01000001000000000000010000010000; + if PossibleMoves::has_jumps(board.flip_turn()) + && not_king && (((1 << value) & KING_MASK) == 0) + { + board.flip_turn() + } 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 + pub const unsafe fn jump_piece_backward_left_unchecked(self, value: usize) -> Self { + let not_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 = 0b00000000000010000010000010000010; + if PossibleMoves::has_jumps(board.flip_turn()) + && not_king && (((1 << value) & KING_MASK) == 0) + { + board.flip_turn() + } 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 + pub const unsafe fn jump_piece_backward_right_unchecked(self, value: usize) -> Self { + let not_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 = 0b00000000000010000010000010000010; + if PossibleMoves::has_jumps(board.flip_turn()) + && not_king && (((1 << value) & KING_MASK) == 0) + { + board.flip_turn() + } else { + board + } + } +} diff --git a/model/src/board/tests.rs b/model/src/board/tests.rs index 2afe1da..0de12d5 100644 --- a/model/src/board/tests.rs +++ b/model/src/board/tests.rs @@ -538,6 +538,7 @@ fn test_move_piece_to_default_board() { assert!(board.piece_at(5)); assert_eq!(board.color_at(5).unwrap(), PieceColor::Light); assert!(!board.king_at(5).unwrap()); + assert_eq!(board.turn, PieceColor::Light); } #[test] @@ -548,6 +549,7 @@ fn test_move_piece_forward_standard() { assert!(board.piece_at(16)); assert_eq!(board.color_at(16).unwrap(), PieceColor::Light); assert!(!board.king_at(16).unwrap()); + assert_eq!(board.turn, PieceColor::Light); } #[test] @@ -558,6 +560,21 @@ fn test_move_piece_forward_wrap() { assert!(board.piece_at(9)); assert_eq!(board.color_at(9).unwrap(), PieceColor::Dark); assert!(!board.king_at(9).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.king_at(5)); +} + +#[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.king_at(0)); } #[test] @@ -568,6 +585,7 @@ fn test_move_piece_backward_standard() { assert!(board.piece_at(15)); assert_eq!(board.color_at(15).unwrap(), PieceColor::Dark); assert!(!board.king_at(15).unwrap()); + assert_eq!(board.turn, PieceColor::Light); } #[test] @@ -578,6 +596,7 @@ fn test_move_piece_backward_wrap() { assert!(board.piece_at(28)); assert_eq!(board.color_at(28).unwrap(), PieceColor::Light); assert!(!board.king_at(28).unwrap()); + assert_eq!(board.turn, PieceColor::Light); } #[test] @@ -596,6 +615,7 @@ fn test_jump_forward_left_specific() { 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!(board.turn, PieceColor::Light); } #[test] @@ -613,6 +633,7 @@ fn test_jump_forward_right_specific() { 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!(board.turn, PieceColor::Light); } #[test] @@ -630,6 +651,7 @@ fn test_jump_backward_left_specific() { 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!(board.turn, PieceColor::Light); } #[test] @@ -647,6 +669,7 @@ fn test_jump_backward_right_specific() { 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!(board.turn, PieceColor::Light); } #[test] diff --git a/model/src/color.rs b/model/src/color.rs index 3dd2a64..c223c11 100644 --- a/model/src/color.rs +++ b/model/src/color.rs @@ -1,57 +1,94 @@ -#[cfg(feature = "serde")]
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// The color of a piece
-#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
-#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-pub enum PieceColor {
- Light,
- Dark,
-}
-
-impl Display for PieceColor {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(
- f,
- "{}",
- match self {
- Self::Light => "Light",
- Self::Dark => "Dark",
- }
- )
- }
-}
-
-impl PieceColor {
- pub const fn flip(self) -> Self {
- // TODO optimize
- match self {
- PieceColor::Light => PieceColor::Dark,
- PieceColor::Dark => PieceColor::Light,
- }
- }
-
- pub const fn flip_if(self, statement: bool) -> Self {
- if statement {
- self.flip()
- } else {
- self
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn light_display() {
- assert_eq!(PieceColor::Light.to_string(), "Light");
- }
-
- #[test]
- fn dark_display() {
- assert_eq!(PieceColor::Dark.to_string(), "Dark");
- }
-}
+#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use std::fmt::Display; + +/// The color of a piece +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum PieceColor { + Light, + Dark, +} + +impl Display for PieceColor { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Light => "Light", + Self::Dark => "Dark", + } + ) + } +} + +impl PieceColor { + /// Flips the color + pub const fn flip(self) -> Self { + // TODO optimize + match self { + PieceColor::Light => PieceColor::Dark, + PieceColor::Dark => PieceColor::Light, + } + } + + /// Flips the color if the statement is true + /// + /// # Arguments + /// + /// * `statement` - Flips the color if true + pub const fn flip_if(self, statement: bool) -> Self { + if statement { + self.flip() + } else { + self + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn light_display() { + assert_eq!(PieceColor::Light.to_string(), "Light"); + } + + #[test] + fn dark_display() { + assert_eq!(PieceColor::Dark.to_string(), "Dark"); + } + + #[test] + fn flip() { + let light = PieceColor::Light; + let dark = PieceColor::Dark; + assert_eq!(light.flip(), dark); + assert_eq!(dark.flip(), light); + } + + #[test] + fn flip_if() { + let light = PieceColor::Light; + let dark = PieceColor::Dark; + + assert_eq!(light.flip_if(true), dark); + assert_eq!(light.flip_if(false), light); + assert_eq!(dark.flip_if(true), light); + assert_eq!(dark.flip_if(false), dark); + } + + #[test] + fn test_send() { + fn assert_send<T: Send>() {} + assert_send::<PieceColor>(); + } + + #[test] + fn test_sync() { + fn assert_sync<T: Sync>() {} + assert_sync::<PieceColor>(); + } +} diff --git a/model/src/moves.rs b/model/src/moves.rs index 30024d6..626d85a 100644 --- a/model/src/moves.rs +++ b/model/src/moves.rs @@ -1,59 +1,89 @@ -use crate::CheckersBitBoard;
-
-#[derive(Copy, Clone, Eq, PartialEq)]
-pub enum MoveDirection {
- ForwardLeft = 0,
- ForwardRight = 1,
- BackwardLeft = 2,
- BackwardRight = 3,
-}
-
-#[derive(Copy, Clone)]
-pub struct Move {
- start: u32,
- direction: MoveDirection,
- jump: bool,
-}
-
-impl Move {
- pub const fn new(start: usize, direction: MoveDirection, jump: bool) -> Self {
- Self {
- start: start as u32,
- direction,
- jump,
- }
- }
-
- pub const unsafe fn apply_to(self, board: CheckersBitBoard) -> CheckersBitBoard {
- match self.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)
- }
- },
- }
- }
-}
+use crate::CheckersBitBoard; + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum MoveDirection { + ForwardLeft = 0, + ForwardRight = 1, + BackwardLeft = 2, + BackwardRight = 3, +} + +/// A checkers move +#[derive(Copy, Clone)] +pub struct Move { + /// The position of the piece to move + start: u32, + + /// The direction to move to + direction: MoveDirection, + + /// Whether or not it's a jump + jump: bool, +} + +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: start as u32, + direction, + jump, + } + } + + /// 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 + pub const unsafe fn apply_to(self, board: CheckersBitBoard) -> CheckersBitBoard { + match self.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) + } + }, + } + } +} diff --git a/model/src/moves_iter.rs b/model/src/moves_iter.rs index 307ee96..ffa59b6 100644 --- a/model/src/moves_iter.rs +++ b/model/src/moves_iter.rs @@ -1,143 +1,144 @@ -use crate::moves::{Move, MoveDirection};
-use crate::possible_moves::PossibleMoves;
-
-const FORWARD_LEFT_SLIDE_SQUARES: [usize; 33] = [
- 1, 3, 3, 4, 6, 6, 7, 8, 9, 12, 12, 12, 13, 14, 15, 16, 17, 19, 19, 20, 21, 22, 23, 24, 27, 27,
- 27, 28, 29, 30, 32, 32, 32,
-];
-const FORWARD_RIGHT_SLIDE_SQUARES: [usize; 33] = [
- 2, 2, 3, 4, 6, 6, 7, 8, 10, 10, 12, 12, 13, 14, 15, 16, 18, 18, 19, 20, 21, 22, 23, 24, 26, 26,
- 27, 28, 29, 30, 32, 32, 32,
-];
-const BACKWARD_LEFT_SLIDE_SQUARES: [usize; 33] = [
- 1, 3, 3, 4, 5, 7, 7, 8, 9, 11, 11, 13, 13, 14, 15, 16, 17, 19, 19, 20, 21, 22, 23, 24, 25, 27,
- 27, 28, 29, 30, 31, 32, 32,
-];
-const BACKWARD_RIGHT_SLIDE_SQUARES: [usize; 33] = [
- 2, 2, 3, 4, 5, 7, 7, 8, 10, 10, 11, 13, 13, 14, 15, 16, 19, 19, 19, 20, 21, 22, 23, 24, 26, 26,
- 27, 28, 29, 30, 31, 32, 32,
-];
-
-const FORWARD_LEFT_JUMP_SQUARES: [usize; 33] = [
- 1, 6, 6, 6, 6, 6, 7, 8, 9, 12, 12, 12, 13, 14, 15, 16, 17, 20, 20, 20, 21, 22, 23, 28, 28, 28,
- 28, 28, 29, 32, 32, 32, 32,
-];
-const FORWARD_RIGHT_JUMP_SQUARES: [usize; 33] = [
- 2, 2, 3, 6, 6, 6, 7, 12, 12, 12, 12, 12, 13, 14, 15, 18, 18, 18, 19, 20, 21, 22, 23, 26, 26,
- 26, 27, 28, 29, 32, 32, 32, 32,
-];
-const BACKWARD_LEFT_JUMP_SQUARES: [usize; 33] = [
- 4, 4, 4, 4, 5, 8, 8, 8, 9, 14, 14, 14, 14, 14, 15, 16, 17, 20, 20, 20, 21, 22, 23, 24, 25, 28,
- 28, 28, 29, 30, 31, 32, 32,
-];
-const BACKWARD_RIGHT_JUMP_SQUARES: [usize; 33] = [
- 2, 2, 3, 4, 5, 10, 10, 10, 10, 10, 11, 14, 14, 14, 15, 20, 20, 20, 20, 20, 21, 22, 23, 26, 26,
- 26, 27, 28, 29, 30, 31, 32, 32,
-];
-
-static SLIDE_ARRAYS: [[usize; 33]; 4] = [
- FORWARD_LEFT_SLIDE_SQUARES,
- FORWARD_RIGHT_SLIDE_SQUARES,
- BACKWARD_LEFT_SLIDE_SQUARES,
- BACKWARD_RIGHT_SLIDE_SQUARES,
-];
-
-static JUMP_ARRAYS: [[usize; 33]; 4] = [
- FORWARD_LEFT_JUMP_SQUARES,
- FORWARD_RIGHT_JUMP_SQUARES,
- BACKWARD_LEFT_JUMP_SQUARES,
- BACKWARD_RIGHT_JUMP_SQUARES,
-];
-
-pub struct PossibleMovesIter {
- possible_moves: PossibleMoves,
- current_square: usize,
- current_direction: MoveDirection,
- movers: u32,
- squares: &'static [usize; 33],
-}
-
-impl From<PossibleMoves> for PossibleMovesIter {
- fn from(possible_moves: PossibleMoves) -> Self {
- Self {
- possible_moves,
- current_square: 0,
- current_direction: MoveDirection::ForwardLeft,
- movers: possible_moves.forward_left_bits(),
- squares: unsafe {
- if possible_moves.can_jump() {
- JUMP_ARRAYS.get_unchecked(0)
- } else {
- SLIDE_ARRAYS.get_unchecked(0)
- }
- },
- }
- }
-}
-
-impl Iterator for PossibleMovesIter {
- type Item = Move;
-
- fn next(&mut self) -> Option<Self::Item> {
- loop {
- if self.current_square == 32 {
- if self.current_direction != MoveDirection::BackwardRight {
- self.current_square = 0;
- // safety: only results in undefined variant if equal to backward right
- // this has already been checked for
- self.current_direction =
- unsafe { std::mem::transmute((self.current_direction as u8) + 1) };
- self.movers = self
- .possible_moves
- .get_direction_bits(self.current_direction);
-
- // safety: the max value of the enum is 3
- unsafe {
- self.squares = &*(self.squares as *const [usize; 33]).add(1);
- }
- } else {
- return None;
- }
- }
-
- if (self.movers >> self.current_square) & 1 != 0 {
- let next_move = Move::new(
- self.current_square,
- self.current_direction,
- self.possible_moves.can_jump(),
- );
-
- // safety: self.current_square will never be > 32
- // squares does not contain such a value
- unsafe {
- self.current_square = *self.squares.get_unchecked(self.current_square);
- }
-
- return Some(next_move);
- }
-
- if self.current_square != 32 {
- // safety: self.current_square will never be > 32
- // squares does not contain such a value
- unsafe {
- self.current_square = *self.squares.get_unchecked(self.current_square);
- }
- }
- }
- }
-
- fn size_hint(&self) -> (usize, Option<usize>) {
- (
- 0,
- Some(
- (32 - self.current_square)
- + 32 * match self.current_direction {
- MoveDirection::ForwardLeft => 3,
- MoveDirection::ForwardRight => 2,
- MoveDirection::BackwardLeft => 1,
- MoveDirection::BackwardRight => 0,
- },
- ),
- )
- }
-}
+use crate::moves::{Move, MoveDirection}; +use crate::possible_moves::PossibleMoves; + +const FORWARD_LEFT_SLIDE_SQUARES: [usize; 33] = [ + 1, 3, 3, 4, 6, 6, 7, 8, 9, 12, 12, 12, 13, 14, 15, 16, 17, 19, 19, 20, 21, 22, 23, 24, 27, 27, + 27, 28, 29, 30, 32, 32, 32, +]; +const FORWARD_RIGHT_SLIDE_SQUARES: [usize; 33] = [ + 2, 2, 3, 4, 6, 6, 7, 8, 10, 10, 12, 12, 13, 14, 15, 16, 18, 18, 19, 20, 21, 22, 23, 24, 26, 26, + 27, 28, 29, 30, 32, 32, 32, +]; +const BACKWARD_LEFT_SLIDE_SQUARES: [usize; 33] = [ + 1, 3, 3, 4, 5, 7, 7, 8, 9, 11, 11, 13, 13, 14, 15, 16, 17, 19, 19, 20, 21, 22, 23, 24, 25, 27, + 27, 28, 29, 30, 31, 32, 32, +]; +const BACKWARD_RIGHT_SLIDE_SQUARES: [usize; 33] = [ + 2, 2, 3, 4, 5, 7, 7, 8, 10, 10, 11, 13, 13, 14, 15, 16, 19, 19, 19, 20, 21, 22, 23, 24, 26, 26, + 27, 28, 29, 30, 31, 32, 32, +]; + +const FORWARD_LEFT_JUMP_SQUARES: [usize; 33] = [ + 1, 6, 6, 6, 6, 6, 7, 8, 9, 12, 12, 12, 13, 14, 15, 16, 17, 20, 20, 20, 21, 22, 23, 28, 28, 28, + 28, 28, 29, 32, 32, 32, 32, +]; +const FORWARD_RIGHT_JUMP_SQUARES: [usize; 33] = [ + 2, 2, 3, 6, 6, 6, 7, 12, 12, 12, 12, 12, 13, 14, 15, 18, 18, 18, 19, 20, 21, 22, 23, 26, 26, + 26, 27, 28, 29, 32, 32, 32, 32, +]; +const BACKWARD_LEFT_JUMP_SQUARES: [usize; 33] = [ + 4, 4, 4, 4, 5, 8, 8, 8, 9, 14, 14, 14, 14, 14, 15, 16, 17, 20, 20, 20, 21, 22, 23, 24, 25, 28, + 28, 28, 29, 30, 31, 32, 32, +]; +const BACKWARD_RIGHT_JUMP_SQUARES: [usize; 33] = [ + 2, 2, 3, 4, 5, 10, 10, 10, 10, 10, 11, 14, 14, 14, 15, 20, 20, 20, 20, 20, 21, 22, 23, 26, 26, + 26, 27, 28, 29, 30, 31, 32, 32, +]; + +static SLIDE_ARRAYS: [[usize; 33]; 4] = [ + FORWARD_LEFT_SLIDE_SQUARES, + FORWARD_RIGHT_SLIDE_SQUARES, + BACKWARD_LEFT_SLIDE_SQUARES, + BACKWARD_RIGHT_SLIDE_SQUARES, +]; + +static JUMP_ARRAYS: [[usize; 33]; 4] = [ + FORWARD_LEFT_JUMP_SQUARES, + FORWARD_RIGHT_JUMP_SQUARES, + BACKWARD_LEFT_JUMP_SQUARES, + BACKWARD_RIGHT_JUMP_SQUARES, +]; + +pub struct PossibleMovesIter { + possible_moves: PossibleMoves, + current_square: usize, + current_direction: MoveDirection, + movers: u32, + squares: &'static [usize; 33], +} + +impl From<PossibleMoves> for PossibleMovesIter { + fn from(possible_moves: PossibleMoves) -> Self { + Self { + possible_moves, + current_square: 0, + current_direction: MoveDirection::ForwardLeft, + movers: possible_moves.forward_left_bits(), + squares: unsafe { + if possible_moves.can_jump() { + JUMP_ARRAYS.get_unchecked(0) + } else { + SLIDE_ARRAYS.get_unchecked(0) + } + }, + } + } +} + +impl Iterator for PossibleMovesIter { + type Item = Move; + + fn next(&mut self) -> Option<Self::Item> { + loop { + if self.current_square == 32 { + if self.current_direction != MoveDirection::BackwardRight { + self.current_square = 0; + // safety: only results in undefined variant if equal to backward right + // this has already been checked for + self.current_direction = + unsafe { std::mem::transmute((self.current_direction as u8) + 1) }; + self.movers = self + .possible_moves + .get_direction_bits(self.current_direction); + + // safety: this iterator stops returning values before this + // can result in undefined behavior + unsafe { + self.squares = &*(self.squares as *const [usize; 33]).add(1); + } + } else { + return None; + } + } + + if (self.movers >> self.current_square) & 1 != 0 { + let next_move = Move::new( + self.current_square, + self.current_direction, + self.possible_moves.can_jump(), + ); + + // safety: self.current_square will never be > 32 + // squares does not contain such a value + unsafe { + self.current_square = *self.squares.get_unchecked(self.current_square); + } + + return Some(next_move); + } + + if self.current_square != 32 { + // safety: self.current_square will never be > 32 + // squares does not contain such a value + unsafe { + self.current_square = *self.squares.get_unchecked(self.current_square); + } + } + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + ( + 0, + Some( + (32 - self.current_square) + + 32 * match self.current_direction { + MoveDirection::ForwardLeft => 3, + MoveDirection::ForwardRight => 2, + MoveDirection::BackwardLeft => 1, + MoveDirection::BackwardRight => 0, + }, + ), + ) + } +} diff --git a/model/src/possible_moves.rs b/model/src/possible_moves.rs index 7d4ac29..8c25520 100644 --- a/model/src/possible_moves.rs +++ b/model/src/possible_moves.rs @@ -1,326 +1,345 @@ -use crate::moves::{Move, MoveDirection};
-use crate::moves_iter::PossibleMovesIter;
-use crate::{CheckersBitBoard, PieceColor};
-
-#[derive(Copy, Clone, Debug)]
-pub struct PossibleMoves {
- forward_left_movers: u32,
- forward_right_movers: u32,
- backward_left_movers: u32,
- backward_right_movers: u32,
- jump: bool,
-}
-
-impl IntoIterator for PossibleMoves {
- type Item = Move;
- type IntoIter = PossibleMovesIter;
-
- fn into_iter(self) -> Self::IntoIter {
- self.into()
- }
-}
-
-impl PossibleMoves {
- 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,
- jump: false,
- }
- }
-
- 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,
- jump: false,
- }
- }
-
- 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;
- }
-
- Self {
- forward_left_movers,
- forward_right_movers,
- backward_left_movers,
- backward_right_movers,
- jump: true,
- }
- }
-
- 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;
- }
-
- Self {
- forward_left_movers,
- forward_right_movers,
- backward_left_movers,
- backward_right_movers,
- jump: true,
- }
- }
-
- pub 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 forward_spaces = board.king_bits() & backward_spaces;
- friendly_pieces & (forward_spaces | backward_spaces) != 0
- } else {
- friendly_pieces & forward_spaces != 0
- }
- }
-
- pub 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)]
- 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 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
- }
- }
-
- pub const fn moves(board: CheckersBitBoard) -> Self {
- match board.turn() {
- PieceColor::Dark => Self::dark_moves(board),
- PieceColor::Light => Self::light_moves(board),
- }
- }
-
- pub const fn is_empty(self) -> bool {
- (self.backward_left_movers
- | self.forward_left_movers
- | self.forward_right_movers
- | self.backward_right_movers)
- == 0
- }
-
- pub const fn can_jump(self) -> bool {
- self.jump
- }
-
- pub const fn forward_left_bits(self) -> u32 {
- self.forward_left_movers
- }
-
- pub const fn get_direction_bits(self, direction: MoveDirection) -> u32 {
- match 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,
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[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)
- )
- }
-}
+use crate::moves::{Move, MoveDirection}; +use crate::moves_iter::PossibleMovesIter; +use crate::{CheckersBitBoard, PieceColor}; + +#[derive(Copy, Clone, Debug)] +pub struct PossibleMoves { + forward_left_movers: u32, + forward_right_movers: u32, + backward_left_movers: u32, + backward_right_movers: u32, + jump: bool, +} + +impl IntoIterator for PossibleMoves { + type Item = Move; + type IntoIter = PossibleMovesIter; + + fn into_iter(self) -> Self::IntoIter { + self.into() + } +} + +impl PossibleMoves { + 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, + jump: false, + } + } + + 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, + jump: false, + } + } + + 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; + } + + Self { + forward_left_movers, + forward_right_movers, + backward_left_movers, + backward_right_movers, + jump: true, + } + } + + 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; + } + + Self { + forward_left_movers, + forward_right_movers, + backward_left_movers, + backward_right_movers, + jump: true, + } + } + + // TODO make this private + pub 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 forward_spaces = board.king_bits() & backward_spaces; + friendly_pieces & (forward_spaces | backward_spaces) != 0 + } else { + friendly_pieces & forward_spaces != 0 + } + } + + // TODO make this private + pub 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 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 + } + } + + pub const fn moves(board: CheckersBitBoard) -> Self { + match board.turn() { + PieceColor::Dark => Self::dark_moves(board), + PieceColor::Light => Self::light_moves(board), + } + } + + /// 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) + == 0 + } + + /// Returns true if the piece can jump + pub const fn can_jump(self) -> bool { + self.jump + } + + /// Returns the pieces who can move forward left, + /// with undefined behavior for bits where a forward left move is impossible + /// + /// # Safety + /// + /// This function is inherently unsafe because some bits are undefined + // TODO make this unsafe + pub const fn forward_left_bits(self) -> u32 { + self.forward_left_movers + } + + /// Gets the bits for a certain direction, + /// with undefined behavior for bits where the given move is impossible + /// + /// # Safety + /// + /// This function is inherently unsafe because some bits are undefined + // TODO make this unsafe + pub const fn get_direction_bits(self, direction: MoveDirection) -> u32 { + match 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, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[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) + ) + } +} |
