use crate::{CheckersBitBoard, SquareCoordinate};
use std::fmt::{Display, Formatter};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum MoveDirection {
ForwardLeft = 0,
ForwardRight = 1,
BackwardLeft = 2,
BackwardRight = 3,
}
/// A checkers move
#[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
}
/// 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 {
write!(
f,
"{}-{}",
SquareCoordinate::from_value(self.start() as usize),
SquareCoordinate::from_value(self.end_position())
)
}
}
#[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);
}
}
|