Skip to content

Commit

Permalink
merge: branch ataxx-0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
raklaptudirm committed May 25, 2024
2 parents c611d2d + 0f2cc00 commit 0ef69f5
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 348 deletions.
29 changes: 20 additions & 9 deletions ataxx/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fmt;
use std::{fmt, ops};

use crate::{BitBoard, Color};
use crate::{BitBoard, Piece};

/// Hash represents the semi-unique checksum of a Position used to efficiently
/// check for Position equality. Some properties of a Hash include determinism,
Expand All @@ -22,12 +22,14 @@ use crate::{BitBoard, Color};
pub struct Hash(pub u64);

impl Hash {
/// new creates a new Hash from the given white and black piece BitBoards.
/// new creates a new Hash from the given black and white piece BitBoards.
/// This function is used in the backend by Position, and it is usually
/// unnecessary for it to be used explicitly by end-users.
pub fn new(white: BitBoard, black: BitBoard, stm: Color) -> Hash {
let a = white.0;
let b = black.0;
/// unnecessary for it to be used explicitly by end-users. new doesn't take
/// the blocker configuration into account since that remains unchanged
/// throughout an ataxx game.
pub fn new(black: BitBoard, white: BitBoard, stm: Piece) -> Hash {
let a = black.0;
let b = white.0;

// Currently, an 2^-63-almost delta universal hash function, based on
// https://eprint.iacr.org/2011/116.pdf by Long Hoang Nguyen and Andrew
Expand All @@ -51,16 +53,25 @@ impl Hash {
.wrapping_add(part_3 as u64)
.wrapping_add(part_4 as u64);

// The Hash is bitwise complemented if the given Color is Black. Therefore,
// The Hash is bitwise complemented if the given Piece is Black. Therefore,
// if two Positions only differ in side to move, `a.Hash == !b.Hash`.
if stm == Color::Black {
if stm == Piece::Black {
Hash(!hash)
} else {
Hash(hash)
}
}
}

impl ops::Not for Hash {
type Output = Self;

/// Not operator (!) switches the side to move for the Hash.
fn not(self) -> Self::Output {
Hash(!self.0)
}
}

impl fmt::Display for Hash {
/// Display allows Hash to be formatted in a human-readable form.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Expand Down
10 changes: 6 additions & 4 deletions ataxx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@
// modules public, so they can be accessed
// without their parent namespace.
pub use self::bitboard::*;
pub use self::board::*;
pub use self::color::*;
pub use self::hash::*;
pub use self::perft::*;
pub use self::piece::*;
pub use self::position::*;
pub use self::r#move::*;
pub use self::square::*;

// Non-namespaced modules.
mod bitboard;
mod board;
mod color;
mod hash;
mod r#move;
mod perft;
mod piece;
mod position;
mod square;

// Private modules.
Expand Down
20 changes: 10 additions & 10 deletions ataxx/src/move.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,11 @@ impl Move {
/// use 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 old_pos = Position::from_str("x5o/7/7/7/7/7/o5x x 0 1").unwrap();
/// 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.bitboard(Piece::Black), new_pos.bitboard(Piece::Black));
/// assert_eq!(old_pos.bitboard(Piece::White), new_pos.bitboard(Piece::White));
/// assert_eq!(old_pos.side_to_move, !new_pos.side_to_move);
/// ```
pub const PASS: Move = Move(1 << 15 | 1 << 14);
Expand Down Expand Up @@ -153,10 +152,11 @@ impl FromStr for Move {
type Err = MoveParseError;

/// from_str converts the given string representation of a Move into a [Move].
/// The formats supported are '0000' for a [Move::PASS], <target> for a singular
/// Move, and <source><target> for a jump Move. For how <source> and <target>
/// are parsed, take a look at [`Square::FromStr`](Square::from_str). This
/// function can be treated as the inverse of [`Move::Display`].
/// The formats supported are '0000' for a [Move::PASS], `<target>` for a
/// singular Move, and `<source><target>` for a jump Move. For how `<source>`
/// and `<target>` are parsed, take a look at
/// [`Square::FromStr`](Square::from_str). This function can be treated as the
/// inverse of the [`fmt::Display`] trait for [Move].
/// ```
/// use ataxx::*;
/// use std::str::FromStr;
Expand Down Expand Up @@ -240,8 +240,8 @@ impl fmt::Debug for Move {

/// 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](super::Board) like
/// [`Board::generate_moves_into<T>`](super::Board::generate_moves_into<T>).
/// [Position](super::Position) like
/// [`Position::generate_moves_into<T>`](super::Position::generate_moves_into<T>).
pub trait MoveStore {
/// push adds the given Move to the MoveStore.
fn push(&mut self, m: Move);
Expand Down
46 changes: 46 additions & 0 deletions ataxx/src/perft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use crate::Position;

/// perft is a function to walk the move generation tree of strictly legal moves
/// to count all the leaf nodes of a certain depth.
///
/// If `SPLIT` is set to `true`, the perft value contributed by each legal move
/// in the current position is displayed separately. If `BULK` is set to `true`,
/// a trick known as bulk-counting is used, which makes it significantly faster.
///
/// In perft, nodes are only counted at the end after the last make-move. Thus
/// "higher" terminal nodes (e.g. mate or stalemate) are not counted, instead
/// the number of move paths of a certain depth. Perft ignores draws by
/// repetition, by the fifty-move rule and by insufficient material.
pub fn perft<const SPLIT: bool, const BULK: bool>(position: Position, depth: u8) -> u64 {
// Bulk counting if enabled. Instead of calling make move and perft for each
// move at depth 1, just return the number of legal moves, which is equivalent.
if BULK && depth == 1 {
return position.count_moves() as u64;
}

// At depth 0, perft is defined to be 1.
if depth == 0 {
return 1;
}

let mut nodes: u64 = 0;
let movelist = position.generate_moves();

for m in movelist.iter() {
let new_position = position.after_move(m);

// Spilt should always be disabled for child perft calls, and a child perft
// should have the same bulk counting behavior as the parent perft call.
let new_nodes = perft::<false, BULK>(new_position, depth - 1);

// If spilt perft is enabled, print the nodes added due to this move.
if SPLIT {
println!("{}: {}", m, new_nodes);
}

// Add the new node count to the cumulative total.
nodes += new_nodes;
}

nodes
}
77 changes: 40 additions & 37 deletions ataxx/src/color.rs → ataxx/src/piece.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,94 +22,97 @@ use thiserror::Error;

use crate::type_macros;

/// Color represents all the possible colors that an ataxx piece can have,
/// specifically White, Black, and None(no Color/no piece).
/// Piece represents all the possible pieces that an ataxx piece can have,
/// specifically Black, White, and None(no Piece/no piece).
#[derive(Copy, Clone, PartialEq, Eq, Default, FromPrimitive)]
pub enum Color {
White,
pub enum Piece {
Black,
White,
Block,
#[default]
None,
}

// Implement conversions from numerical types.
type_macros::impl_from_integer_for_enum! {
for Color:
for Piece:

// unsigned integers
usize, Color::from_usize;
u8, Color::from_u8; u16, Color::from_u16;
u32, Color::from_u32; u64, Color::from_u64;
usize, Piece::from_usize;
u8, Piece::from_u8; u16, Piece::from_u16;
u32, Piece::from_u32; u64, Piece::from_u64;

// signed integers
isize, Color::from_isize;
i8, Color::from_i8; i16, Color::from_i16;
i32, Color::from_i32; i64, Color::from_i64;
isize, Piece::from_isize;
i8, Piece::from_i8; i16, Piece::from_i16;
i32, Piece::from_i32; i64, Piece::from_i64;
}

impl Color {
/// N is the number of possible Colors, excluding None.
pub const N: usize = 2;
impl Piece {
/// N is the number of possible Pieces, excluding None.
pub const N: usize = 3;
}

impl ops::Not for Color {
type Output = Color;
impl ops::Not for Piece {
type Output = Piece;

/// not implements the not unary operator (!) which switches the current Color
/// to its opposite, i.e. [`Color::Black`] to [`Color::White`] and vice versa.
/// not implements the not unary operator (!) which switches the current Piece
/// to its opposite, i.e. [`Piece::Black`] to [`Piece::White`] and vice versa.
fn not(self) -> Self::Output {
Color::try_from(self as usize ^ 1).unwrap()
Piece::try_from(self as usize ^ 1).unwrap()
}
}

#[derive(Error, Debug)]
pub enum ColorParseError {
#[error("color identifier string has more than 1 character")]
pub enum PieceParseError {
#[error("piece identifier string has more than 1 character")]
StringTooLong,
#[error("unknown color identifier '{0}'")]
#[error("unknown piece identifier '{0}'")]
StringFormatInvalid(String),
}

impl fmt::Display for Color {
/// Implements displaying the Color in a human-readable form. [`Color::Black`]
/// is formatted as `x` and [`Color::White`] is formatted as `o`.
impl fmt::Display for Piece {
/// Implements displaying the Piece in a human-readable form. [`Piece::Black`]
/// is formatted as `x` and [`Piece::White`] is formatted as `o`.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
Self::Black => "x",
Self::White => "o",
Self::None => "-",
Self::Block => "■",
Self::None => ".",
}
)
}
}

impl fmt::Debug for Color {
/// Debug implements debug printing of a Color in a human-readable form. It uses
/// `Color::Display` under the hood to format and print the Color.
impl fmt::Debug for Piece {
/// Debug implements debug printing of a Piece in a human-readable form. It uses
/// `Piece::Display` under the hood to format and print the Piece.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}

impl FromStr for Color {
type Err = ColorParseError;
impl FromStr for Piece {
type Err = PieceParseError;

/// from_str converts the given human-readable string into its corresponding
/// [`Color`]. `x`, `X`, `b`, `B` are parsed as [`Color::Black`] and `o`, `O`,
/// `w`, `W` are parsed as [`Color::White`]. Best practice is to use `x` and `o`
/// [`Piece`]. `x`, `X`, `b`, `B` are parsed as [`Piece::Black`] and `o`, `O`,
/// `w`, `W` are parsed as [`Piece::White`]. Best practice is to use `x` and `o`
/// respectively for black and white.
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != 1 {
return Err(ColorParseError::StringTooLong);
return Err(PieceParseError::StringTooLong);
}

match s {
"x" | "X" | "b" | "B" => Ok(Color::Black),
"o" | "O" | "w" | "W" => Ok(Color::White),
_ => Err(ColorParseError::StringFormatInvalid(s.to_string())),
"x" | "X" | "b" | "B" => Ok(Piece::Black),
"o" | "O" | "w" | "W" => Ok(Piece::White),
"-" => Ok(Piece::Block),
_ => Err(PieceParseError::StringFormatInvalid(s.to_string())),
}
}
}
Loading

0 comments on commit 0ef69f5

Please sign in to comment.