Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
316 changes: 193 additions & 123 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "monster_chess"
version = "0.0.13"
version = "0.0.24"
edition = "2021"
license = "MIT"
description = "A fairy chess movegen library that can be easily extended to new chess-adjacent games."
Expand All @@ -13,13 +13,13 @@ keywords = ["chess", "ataxx", "movegen"]
debug = true

[dependencies]
fastrand = "1.9.0"
heapless = "0.7.16"
fastrand = "2.0.0"
heapless = "0.8.0"
shell-words = "1.1.0"

[dev-dependencies]
criterion = "0.4"
criterion = "0.5.1"

[[bench]]
name = "chess-perft"
name = "perft"
harness = false
18 changes: 0 additions & 18 deletions benches/chess-perft.rs

This file was deleted.

15 changes: 15 additions & 0 deletions benches/perft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use criterion::{criterion_group, criterion_main, Criterion};
use monster_chess::{games::{chess::Chess, ataxx::Ataxx}, board::game::Game};

fn startpos(game: Game<1>, depth: u32) {
let mut board = game.default();
board.perft(depth, true);
}

fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("chess", |b| b.iter(|| startpos(Chess::create(), 4)));
c.bench_function("ataxx", |b| b.iter(|| startpos(Ataxx::create(), 4)));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
2 changes: 2 additions & 0 deletions src/bitboard/bitscan.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::BitBoard;

/// A direction of either `LEFT` or `RIGHT`, created primarily for bitscans of an arbitrary direction.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Direction {
LEFT,
Expand Down Expand Up @@ -64,6 +65,7 @@ impl<const T: usize> BitBoard<T> {
}
}

/// A bitscan from either given direction, left or right.
pub fn bitscan(&self, direction: Direction) -> u16 {
match direction {
Direction::LEFT => self.bitscan_forward(),
Expand Down
8 changes: 8 additions & 0 deletions src/bitboard/shifts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,42 @@ use crate::board::Cols;
use super::BitBoard;

impl<const T: usize> BitBoard<T> {
/// Shifts the `BitBoard` upwards by a given amount.
pub fn up(&self, shift: u16, cols: Cols) -> BitBoard<T> {
*self >> shift * (cols)
}

/// Shifts the `BitBoard` downwards by a given amount.
pub fn down(&self, shift: u16, cols: Cols) -> BitBoard<T> {
*self << shift * (cols)
}

/// Shifts the `BitBoard` right by a given amount.
pub fn right(&self, shift: u16) -> BitBoard<T> {
*self << shift
}

/// Shifts the `BitBoard` left by a given amount.
pub fn left(&self, shift: u16) -> BitBoard<T> {
*self >> shift
}

/// Shifts the given `BitBoard` upwards by a given amount, mutating it.
pub fn up_mut(&mut self, shift: u16, cols: Cols) {
*self >>= shift * (cols);
}

/// Shifts the given `BitBoard` upwards by a given amount, mutating it.
pub fn down_mut(&mut self, shift: u16, cols: Cols) {
*self <<= shift * (cols);
}

/// Shifts the given `BitBoard` right by a given amount, mutating it.
pub fn right_mut(&mut self, shift: u16) {
*self <<= shift;
}

/// Shifts the given `BitBoard` left by a given amount, mutating it.
pub fn left_mut(&mut self, shift: u16) {
*self >>= shift;
}
Expand Down
8 changes: 6 additions & 2 deletions src/bitboard/transforms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::cmp::Ordering;

use super::BitBoard;

/// Generate `BitBoard`s for each rank of the board.
pub fn generate_ranks<const T: usize>(cols: Cols, rows: Rows) -> Vec<BitBoard<T>> {
let mut ranks: Vec<BitBoard<T>> = Vec::with_capacity(rows as usize);
let mut rank = BitBoard::<T>::starting_at_lsb(0, cols);
Expand All @@ -15,6 +16,7 @@ pub fn generate_ranks<const T: usize>(cols: Cols, rows: Rows) -> Vec<BitBoard<T>
ranks
}

/// Generate `BitBoard`s for each file of the board.
pub fn generate_files<const T: usize>(cols: Cols, rows: Rows) -> Vec<BitBoard<T>> {
let mut files: Vec<BitBoard<T>> = Vec::with_capacity(rows as usize);
let mut file = BitBoard::<T>::from_lsb(0);
Expand All @@ -32,7 +34,8 @@ pub fn generate_files<const T: usize>(cols: Cols, rows: Rows) -> Vec<BitBoard<T>
}

impl<const T: usize> BitBoard<T> {
pub fn flip_vertically(self, ranks: &Vec<BitBoard<T>>, cols: Cols, rows: Rows) -> BitBoard<T> {
/// Flip the `BitBoard` vertically.
pub fn flip_vertically(self, ranks: &[BitBoard<T>], cols: Cols, rows: Rows) -> BitBoard<T> {
let mut new_board = BitBoard::<T>::new();
let max_rank = rows - 1;

Expand All @@ -57,7 +60,8 @@ impl<const T: usize> BitBoard<T> {
new_board
}

pub fn flip_horizontally(self, files: &Vec<BitBoard<T>>, cols: Cols) -> BitBoard<T> {
/// Flip the `BitBoard` horizontally.
pub fn flip_horizontally(self, files: &[BitBoard<T>], cols: Cols) -> BitBoard<T> {
let mut new_board = BitBoard::<T>::new();

let max_file = cols - 1;
Expand Down
33 changes: 29 additions & 4 deletions src/bitboard/util.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
/// I've chosen to use this little utility because of its performance in benchmarks being the best, and because it makes it the easiest to specialize to the needs of this project (in terms of both optimizations and code structure.)
/// In this case, those needs being a way to have bigger integer sizes that are compatible with bit operations at high speeds.

/// `BitBoard<T>` is essentially a wrapper around a `[u128; T]`.
/// It has multiple `u128`s so it can serve as essentially a bigger unsigned integer for `T > 1`.
/// It handles bit operations and even some mathematical operations to operate essentially as if it was any other integer type.
/// It also has methods specifically related to its use as a BitBoard, like moving it up, down, right, left, etc.
/// For `T = 1` (a BitBoard with only one `u128`), it should compile down to esssentially a single `u128`, and should be very fast.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord)]
pub struct BitBoard<const T: usize> {
pub bits: [u128; T],
}

impl<const T: usize> BitBoard<T> {
/// Create a `BitBoard` from a backing array.
pub fn from_data(data: [u128; T]) -> BitBoard<T> {
BitBoard { bits: data }
}

/// Create a `BitBoard` from a single `u128`.
pub fn from_element(el: u128) -> BitBoard<T> {
let mut arr = [0; T];
arr[T - 1] = el;
BitBoard { bits: arr }
}

/// Create a `BitBoard` with a single bit from the index of that bit from the LSB.
pub fn from_lsb(bit: u16) -> BitBoard<T> {
BitBoard::<T>::from_element(1) << bit
}

/// Create a `BitBoard` with a single bit from the index of that bit from the MSB.
pub fn from_msb(bit: u16) -> BitBoard<T> {
!(BitBoard::<T>::max() >> 1) >> bit
}

/// Create a `BitBoard` starting at a given bit, and continuing for a given length.
pub fn starting_at_lsb(bit: u16, length: u16) -> BitBoard<T> {
(BitBoard::<T>::from_lsb(length) - BitBoard::<T>::from_element(1)) << bit
}

/// Check if a `BitBoard` has a given bit.
pub fn has_bit(self, bit: u16) -> bool {
(self & (BitBoard::<T>::from_element(1) << bit)).is_set()
}

/// Check if a `BitBoard` is empty (if that `BitBoard` is `0`.)
pub fn is_empty(&self) -> bool {
if T == 1 {
return self.bits[0] == 0;
Expand All @@ -41,6 +50,7 @@ impl<const T: usize> BitBoard<T> {
self.bits.iter().all(|el| *el == 0)
}

/// Check if a `BitBoard` is set (if that `BitBoard` isn't `0`, if it has at least one set bit.)
pub fn is_set(&self) -> bool {
if T == 1 {
return self.bits[0] != 0;
Expand All @@ -49,14 +59,19 @@ impl<const T: usize> BitBoard<T> {
self.bits.iter().any(|el| *el != 0)
}

/// Get the highest possible value for a given `BitBoard`.
pub fn max() -> BitBoard<T> {
BitBoard::<T>::from_data([u128::MAX; T])
}

/// Initialize a new, empty `BitBoard`.
pub fn new() -> BitBoard<T> {
BitBoard::<T>::from_data([0; T])
}

/// A utility method primarily for internally handling bit operations.
/// Apply a function to each of the `u128`s of two `BitBoards`.
/// For instance, `bitboard.apply(rhs, |el| el.0 | el.1)` would get a `BitBoard` for the `or` bit operation.
#[inline(always)]
pub fn apply(self, rhs: BitBoard<T>, apply: impl Fn((&u128, u128)) -> u128) -> Self {
if T == 1 {
Expand All @@ -77,6 +92,9 @@ impl<const T: usize> BitBoard<T> {
}
}

/// A utility method primarily for internally handling bit operations.
/// Apply a function to each of the `u128`s of two `BitBoard`s, mutating the first of those `BitBoard`s.
/// For instance, `bitboard.effect(rhs, |el| el.0 | el.1)` would apply the `or` operation to this `BitBoard`.
#[inline(always)]
pub fn effect(&mut self, rhs: BitBoard<T>, apply: impl Fn((&u128, u128)) -> u128) {
if T == 1 {
Expand All @@ -94,6 +112,7 @@ impl<const T: usize> BitBoard<T> {
.expect(&format!("Could not convert BitBoard data vector into an array when applying operation with `effect`."));
}

/// Count the number of zero bits in a given `BitBoard`.
pub fn count_zeros(&self) -> u32 {
if T == 1 {
self.bits[0].count_zeros()
Expand All @@ -102,6 +121,7 @@ impl<const T: usize> BitBoard<T> {
}
}

/// Count the number of one bits in a given `BitBoard`.
pub fn count_ones(&self) -> u32 {
if T == 1 {
self.bits[0].count_ones()
Expand All @@ -110,6 +130,7 @@ impl<const T: usize> BitBoard<T> {
}
}

/// Outputs an array of all bits, with `0` if they're off, and `1` otherwise.
/// Not a well optimized method; avoid using in hot loops.
pub fn get_bits(&self) -> Vec<u128> {
let mut bits: Vec<u128> = Vec::with_capacity(128 * T);
Expand All @@ -121,10 +142,13 @@ impl<const T: usize> BitBoard<T> {
bits
}

pub fn iter_set_bits(mut self, end: u16) -> BitIterator<T> {
/// Iterates over all set bits in the `BitBoard`.
pub fn iter_set_bits(self, end: u16) -> BitIterator<T> {
BitIterator(self, end)
}

/// Displays a `BitBoard`, showing ⬛ if the bit is off, and ⬜ if it's on.
/// Consider using this if you need to debug a `BitBoard`.
pub fn display(&self, rows: usize, cols: usize) -> String {
let mut chunks = Vec::<String>::with_capacity(rows);
for (ind, row) in self.get_bits().chunks(cols).enumerate() {
Expand All @@ -144,6 +168,7 @@ impl<const T: usize> BitBoard<T> {
}
}

/// `BitIterator` is used to efficiently iterate over all of the bits in a `BitBoard` with `BitBoard.iter_set_bits()`.
pub struct BitIterator<const T: usize>(pub BitBoard<T>, u16);

impl<const T: usize> Iterator for BitIterator<T> {
Expand Down
2 changes: 1 addition & 1 deletion src/board/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub struct Action {
pub move_type: ActionInfo
}

/// This is a theoretically possible action. It doesn't even have to be actually possible.
/// A theoretically possible action. It doesn't even have to be actually possible.
/// It's mainly there for Neural Networks to be able to index moves.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct TheoreticalAction {
Expand Down
7 changes: 2 additions & 5 deletions src/board/edges.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use crate::bitboard::BitBoard;

use super::{
actions::{Action, HistoryMove, UndoMoveError},
game::Game,
pieces::Piece,
Board, Cols, Rows,
Cols, Rows,
};

pub type EdgeBuffer = u16;
Expand All @@ -24,7 +21,7 @@ pub fn generate_edges<const T: usize>(buffer: EdgeBuffer, rows: Rows, cols: Cols

let mut left = BitBoard::max() & (!(BitBoard::max() << (buffer)));
for _ in 1..rows {
left |= (left << (cols));
left |= left << (cols);
}

let right = left << (cols - buffer);
Expand Down
19 changes: 15 additions & 4 deletions src/board/fen/args.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use std::fmt::Debug;

use super::super::{
actions::{Action, HistoryMove, UndoMoveError},
game::Game,
pieces::Piece,
Board, Cols, Rows,
Board,
};

/// `FenTeamArgument` represents which team is going to move next in your FEN.
#[derive(Clone, Debug)]
pub enum FenTeamArgument {
Number,
Expand Down Expand Up @@ -51,6 +49,9 @@ impl<const T: usize> FenArgument<T> for FenTeamArgument {
}
}

/// `FenTurns` represents how many turns have been played in your game.
/// This counter might not be a counter of all turns in the entire game, but how many since the last of a certain type of move, for instance.
/// Games can manage what counters represent by handling `update` in `MoveController`.
#[derive(Debug)]
pub struct FenTurns;

Expand All @@ -69,6 +70,9 @@ impl<const T: usize> FenArgument<T> for FenTurns {
}
}

/// `FenSubMoves` represents how many sub moves (or in the case of two players, half moves) have been played in your game.
/// This counter might not be a counter of all sub moves in the entire game, but how many since the last of a certain type of move, for instance.
/// Games can manage what counters represent by handling `update` in `MoveController`.
#[derive(Debug)]
pub struct FenSubMoves;

Expand All @@ -87,6 +91,9 @@ impl<const T: usize> FenArgument<T> for FenSubMoves {
}
}

/// `FenFullMoves` represents how many sub moves (or in the case of two players, half moves) have been played in your game.
/// This counter might not be a counter of all full moves in the entire game, but how many since the last of a certain type of move, for instance.
/// Games can manage what counters represent by handling `update` in `MoveController`.
#[derive(Debug)]
pub struct FenFullMoves;

Expand All @@ -105,11 +112,15 @@ impl<const T: usize> FenArgument<T> for FenFullMoves {
}
}

/// This is the error that is shown if a given `FenArgument` has an error when being parsed.
#[derive(Debug, Clone)]
pub enum FenDecodeError {
InvalidArgument(String),
}

/// `FenArgument` represents a given argument in your FEN.
/// All FENs will show the board state, but after that, games can specify a variety of arguments.
/// In chess, the FEN arguments are `FenTeamArgument`, `ChessCastlingRights`, `ChessEnPassant`, `FebSubMoves` and `FenFullMoves`.
pub trait FenArgument<const T: usize> : Debug + Send + Sync {
/// `encode` takes in a board, and outputs what this FEN argument's encoded result would be (eg. for a team argument, it could be `"b"`)
fn encode(&self, board: &Board<T>) -> String;
Expand Down
Loading