Skip to content

Commit

Permalink
chore: refactor move.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
raklaptudirm committed Mar 31, 2024
1 parent 4d6881d commit d7a7e3c
Showing 1 changed file with 155 additions and 2 deletions.
157 changes: 155 additions & 2 deletions src/ataxx/move.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2023 Rak Laptudirm <rak@laptudirm.com>
// Copyright © 2024 Rak Laptudirm <rak@laptudirm.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@ use std::mem::MaybeUninit;

use crate::ataxx::Square;

/// Move represents an Ataxx move which can be played on the Board.
#[derive(Copy, Clone, PartialEq, Eq, Default)]
pub struct Move(u16);

Expand All @@ -35,13 +36,47 @@ impl Move {
/// NULL Move represents an invalid move.
pub const NULL: Move = Move(1 << 15);
/// PASS Move represents a no move, where only the side to move changes.
/// ```
/// use mexx::ataxx::*;
/// use std::str::FromStr;
///
/// let board = Board::from_str("x5o/7/7/7/7/7/o5x x 0 1").unwrap();
/// let old_pos = board.position();
/// let new_pos = old_pos.after_move(Move::PASS);
///
/// assert_eq!(old_pos.bitboard(Color::Black), new_pos.bitboard(Color::Black));
/// assert_eq!(old_pos.bitboard(Color::White), new_pos.bitboard(Color::White));
/// assert_eq!(old_pos.side_to_move, !new_pos.side_to_move);
/// ```
pub const PASS: Move = Move(1 << 15 | 1 << 14);

/// new_single returns a new singular Move, where a piece is cloned to its
/// target Square. For a singular Move, [`Move::source`] and [`Move::target`]
/// are equal since the source Square is irrelevant to the Move.
/// ```
/// use mexx::ataxx::*;
///
/// let mov = Move::new_single(Square::A1);
///
/// assert_eq!(mov.source(), mov.target());
/// assert_eq!(mov.target(), Square::A1);
/// ```
#[inline(always)]
pub fn new_single(square: Square) -> Move {
Move::new(square, square)
}

/// new returns a new jump Move from the given source Square to the given
/// target Square. These Squares can be recovered with the [`Move::source`] and
/// [`Move::target`] methods respectively.
/// ```
/// use mexx::ataxx::*;
///
/// let mov = Move::new(Square::A1, Square::A3);
///
/// assert_eq!(mov.source(), Square::A1);
/// assert_eq!(mov.target(), Square::A3);
/// ```
#[inline(always)]
#[rustfmt::skip]
pub fn new(source: Square, target: Square) -> Move {
Expand All @@ -51,6 +86,15 @@ impl Move {
)
}

/// Source returns the source Square of the moving piece. This is equal to the
/// target Square if the given Move is of singular type.
/// ```
/// use mexx::ataxx::*;
///
/// let mov = Move::new(Square::A1, Square::A3);
///
/// assert_eq!(mov.source(), Square::A1);
/// ```
#[inline(always)]
#[rustfmt::skip]
pub fn source(self) -> Square {
Expand All @@ -59,6 +103,14 @@ impl Move {
).unwrap()
}

/// Target returns the target Square of the moving piece.
/// ```
/// use mexx::ataxx::*;
///
/// let mov = Move::new(Square::A1, Square::A3);
///
/// assert_eq!(mov.target(), Square::A3);
/// ```
#[inline(always)]
#[rustfmt::skip]
pub fn target(self) -> Square {
Expand All @@ -67,15 +119,46 @@ impl Move {
).unwrap()
}

/// is_single checks if the given Move is singular in nature. The result of this
/// function for [`Move::NULL`] and [`Move::PASS`] is undefined.
/// ```
/// use mexx::ataxx::*;
///
/// let sing = Move::new_single(Square::A1);
/// let jump = Move::new(Square::A1, Square::A3);
///
/// assert!(sing.is_single());
/// assert!(!jump.is_single());
/// ```
#[inline(always)]
pub fn is_single(self) -> bool {
self.source() == self.target()
}
}

impl fmt::Display for Move {
/// Display formats the given Move in a human-readable manner. The format used
/// for displaying jump moves is `<source><target>`, while a singular Move is
/// formatted as `<target>`. For the formatting of `<source>` and `<target>`,
/// refer to [`Square::fmt`]. [`Move::NULL`] is formatted as `null`, while
/// [`Move::PASS`] is formatted as `0000`.
/// ```
/// use mexx::ataxx::*;
///
/// let null = Move::NULL;
/// let pass = Move::PASS;
/// let sing = Move::new_single(Square::A1);
/// let jump = Move::new(Square::A1, Square::A3);
///
/// assert_eq!(null.to_string(), "null");
/// assert_eq!(pass.to_string(), "0000");
/// assert_eq!(sing.to_string(), "a1");
/// assert_eq!(jump.to_string(), "a1a3");
/// ```
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == Move::NULL {
write!(f, "null")
} else if *self == Move::PASS {
write!(f, "0000")
} else if self.is_single() {
write!(f, "{}", self.source())
Expand All @@ -85,29 +168,67 @@ impl fmt::Display for Move {
}
}

impl fmt::Debug for Move {
/// Debug formats the given Move into a human-readable debug string. It uses
/// [`Move::fmt`] under the hood for formatting the Move.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}

/// MoveStore is a trait implemented by types which are able to store [Move]s inside
/// themselves and are thus usable in move-generation methods in [Board] like
/// [`Board::generate_moves_into<T>`](ataxx::Board::generate_moves_into<T>).
pub trait MoveStore {
/// push adds the given Move to the MoveStore.
fn push(&mut self, m: Move);

/// len returns the number of [Move]s stored in the MoveStore.
fn len(&self) -> usize;

/// is_empty checks if no [Move]s are stored in the MoveStore.
fn is_empty(&self) -> bool;
}

/// MoveList is a basic implementation of [`MoveStore`] that is used to allow users
/// to utilize move-generation methods without having to implement a [MoveStore] by
/// themselves. It also has utility methods other than the [`MoveStore`] trait.
pub struct MoveList {
// A possibly uninitialized array of Moves. A fixed size array is used to allow
// storage in the stack and thus provides more speed than a dynamic array.
list: [MaybeUninit<Move>; 256],
length: usize,
}

impl MoveList {
/// new creates an empty MoveList ready for use.
/// ```
/// use mexx::ataxx::*;
///
/// let movelist = MoveList::new();
///
/// assert!(movelist.is_empty());
/// ```
#[allow(clippy::new_without_default)]
pub fn new() -> MoveList {
MoveList {
// Initialize an un-initialized array :3
list: [MaybeUninit::uninit(); 256],
length: 0,
}
}

pub const fn at(&self, n: usize) -> Move {
/// at returns the Move stored at the given index. This operation is defined and
/// valid if and only if the 0 <= index <= MoveList length.
pub fn at(&self, n: usize) -> Move {
debug_assert!(n < self.len());

// It is same to assume that the memory is initialized since the length of
// the MoveList can only be increased by pushing Moves into it.
unsafe { self.list[n].assume_init() }
}

/// iter returns an [Iterator] which iterates over the moves in the MoveList.
pub fn iter(&self) -> MoveListIterator {
MoveListIterator {
list: self,
Expand All @@ -116,17 +237,49 @@ impl MoveList {
}
}

// Implement the MoveStore trait to allow usage in move-generation functions.
impl MoveStore for MoveList {
/// push adds the given move to the MoveList.
/// ```
/// use mexx::ataxx::*;
///
/// let mov = Move::new(Square::A1, Square::A3);
///
/// let mut movelist = MoveList::new();
/// movelist.push(mov);
///
/// assert_eq!(movelist.at(0), mov);
/// assert_eq!(movelist.len(), 1);
/// ```
fn push(&mut self, m: Move) {
self.list[self.length] = MaybeUninit::new(m);
self.length += 1;
}

/// len returns the length of the MoveList.
/// ```
/// use mexx::ataxx::*;
///
/// let mut movelist = MoveList::new();
///
/// movelist.push(Move::new_single(Square::A1));
/// movelist.push(Move::new_single(Square::A2));
/// movelist.push(Move::new_single(Square::A3));
///
/// assert_eq!(movelist.len(), 3);
/// ```
fn len(&self) -> usize {
self.length
}

/// is_empty checks if the MoveList is empty, i.e its length is 0.
///
fn is_empty(&self) -> bool {
self.length == 0
}
}

/// MoveListIterator implements an [Iterator] for a [MoveList].
pub struct MoveListIterator<'a> {
list: &'a MoveList,
current: usize,
Expand Down

0 comments on commit d7a7e3c

Please sign in to comment.