Compare commits
No commits in common. "47f532cd0934286e48d335c87fd1a41b3f7272d8" and "b3222276abea45deaf5be7bcbd875f32a7de06a6" have entirely different histories.
47f532cd09
...
b3222276ab
37 changed files with 261 additions and 3831 deletions
31
.drone.yml
Normal file
31
.drone.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: exec
|
||||
name: abacus checks
|
||||
|
||||
steps:
|
||||
- name: flake check
|
||||
commands:
|
||||
- nix flake check
|
||||
|
||||
- name: package check
|
||||
commands:
|
||||
- nix build
|
||||
|
||||
- name: notifiy
|
||||
commands:
|
||||
- nix run github:ambroisie/matrix-notifier
|
||||
environment:
|
||||
ADDRESS:
|
||||
from_secret: matrix_homeserver
|
||||
ROOM:
|
||||
from_secret: matrix_roomid
|
||||
USER:
|
||||
from_secret: matrix_username
|
||||
PASS:
|
||||
from_secret: matrix_password
|
||||
when:
|
||||
status:
|
||||
- failure
|
||||
- success
|
||||
...
|
||||
5
.envrc
5
.envrc
|
|
@ -1,5 +0,0 @@
|
|||
if ! has nix_direnv_version || ! nix_direnv_version 3.0.0; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.0/direnvrc" "sha256-21TMnI2xWX7HkSTjFFri2UaohXVj854mgvWapWrxRXg="
|
||||
fi
|
||||
|
||||
use flake
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -1,6 +1,6 @@
|
|||
# Rust build directory
|
||||
/target
|
||||
|
||||
# Nix generated files
|
||||
/.pre-commit-config.yaml
|
||||
# Nix files
|
||||
/result
|
||||
/.pre-commit-config.yaml
|
||||
|
||||
# Rust files
|
||||
/target
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
labels:
|
||||
backend: local
|
||||
|
||||
steps:
|
||||
- name: pre-commit check
|
||||
image: bash
|
||||
commands:
|
||||
- nix develop --command pre-commit run --all
|
||||
|
||||
- name: nix flake check
|
||||
image: bash
|
||||
commands:
|
||||
- nix flake check
|
||||
|
||||
- name: notifiy
|
||||
image: bash
|
||||
secrets:
|
||||
- source: matrix_homeserver
|
||||
target: address
|
||||
- source: matrix_roomid
|
||||
target: room
|
||||
- source: matrix_username
|
||||
target: user
|
||||
- source: matrix_password
|
||||
target: pass
|
||||
commands:
|
||||
- nix run '.#matrix-notifier'
|
||||
when:
|
||||
status:
|
||||
- failure
|
||||
- success
|
||||
115
flake.lock
generated
115
flake.lock
generated
|
|
@ -1,111 +1,73 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"futils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"lastModified": 1656928814,
|
||||
"narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"ref": "main",
|
||||
"ref": "master",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"pre-commit-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"lastModified": 1655042882,
|
||||
"narHash": "sha256-9BX8Fuez5YJlN7cdPO63InoyBy7dm3VlJkkmTt6fS1A=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "cddffb5aa211f50c4b8750adbec0bbbdfb26bb9f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"owner": "nix-community",
|
||||
"ref": "master",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1711523803,
|
||||
"narHash": "sha256-UKcYiHWHQynzj6CN/vTcix4yd1eCu1uFdsuarupdCQQ=",
|
||||
"lastModified": 1657888067,
|
||||
"narHash": "sha256-GnwJoFBTPfW3+mz7QEeJEEQ9OMHZOiIJ/qDhZxrlKh8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2726f127c15a4cc9810843b96cad73c7eb39e443",
|
||||
"rev": "65fae659e31098ca4ac825a6fef26d890aaf3f4e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1710695816,
|
||||
"narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "614b4613980a522ba49f0d194531beddbb7220d3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.11",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": [
|
||||
"futils"
|
||||
"flake-utils"
|
||||
],
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1711519547,
|
||||
"narHash": "sha256-Q7YmSCUJmDl71fJv/zD9lrOCJ1/SE/okZ2DsrmRjzhY=",
|
||||
"lastModified": 1656169028,
|
||||
"narHash": "sha256-y9DRauokIeVHM7d29lwT8A+0YoGUBXV3H0VErxQeA8s=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "7d47a32e5cd1ea481fab33c516356ce27c8cef4a",
|
||||
"rev": "db3bd555d3a3ceab208bed48f983ccaa6a71a25e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -117,23 +79,34 @@
|
|||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"futils": "futils",
|
||||
"flake-utils": "flake-utils",
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
"pre-commit-hooks": "pre-commit-hooks",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"lastModified": 1657853760,
|
||||
"narHash": "sha256-X6ERAyUXGsrhbhgkxNaQl40wcus5uyQZOCxUh5neK+g=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "a97a761cc11327bb109dc30af1c637b986be7959",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"owner": "oxalica",
|
||||
"ref": "master",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
198
flake.nix
198
flake.nix
|
|
@ -1,19 +1,29 @@
|
|||
{
|
||||
description = "A chess engine";
|
||||
description = "A handy file picker program";
|
||||
|
||||
inputs = {
|
||||
futils = {
|
||||
flake-utils = {
|
||||
type = "github";
|
||||
owner = "numtide";
|
||||
repo = "flake-utils";
|
||||
ref = "main";
|
||||
ref = "master";
|
||||
};
|
||||
|
||||
naersk = {
|
||||
type = "github";
|
||||
owner = "nix-community";
|
||||
repo = "naersk";
|
||||
ref = "master";
|
||||
inputs = {
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
nixpkgs = {
|
||||
type = "github";
|
||||
owner = "NixOS";
|
||||
repo = "nixpkgs";
|
||||
ref = "nixos-unstable";
|
||||
ref = "nixpkgs-unstable";
|
||||
};
|
||||
|
||||
pre-commit-hooks = {
|
||||
|
|
@ -22,91 +32,123 @@
|
|||
repo = "pre-commit-hooks.nix";
|
||||
ref = "master";
|
||||
inputs = {
|
||||
flake-utils.follows = "futils";
|
||||
flake-utils.follows = "flake-utils";
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
rust-overlay = {
|
||||
type = "github";
|
||||
owner = "oxalica";
|
||||
repo = "rust-overlay";
|
||||
ref = "master";
|
||||
inputs = {
|
||||
flake-utils.follows = "flake-utils";
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, futils, nixpkgs, pre-commit-hooks }:
|
||||
{
|
||||
overlays = {
|
||||
default = final: _prev: {
|
||||
seer = with final; rustPlatform.buildRustPackage {
|
||||
pname = "seer";
|
||||
version = (final.lib.importTOML ./Cargo.toml).package.version;
|
||||
outputs =
|
||||
{ self
|
||||
, flake-utils
|
||||
, naersk
|
||||
, nixpkgs
|
||||
, pre-commit-hooks
|
||||
, rust-overlay
|
||||
}:
|
||||
let
|
||||
inherit (flake-utils.lib) eachSystem system;
|
||||
|
||||
mySystems = [
|
||||
system.aarch64-linux
|
||||
system.x86_64-darwin
|
||||
system.x86_64-linux
|
||||
];
|
||||
|
||||
eachMySystem = eachSystem mySystems;
|
||||
in
|
||||
eachMySystem (system:
|
||||
let
|
||||
overlays = [ (import rust-overlay) ];
|
||||
pkgs = import nixpkgs { inherit overlays system; };
|
||||
my-rust = pkgs.rust-bin.stable.latest.default.override {
|
||||
extensions = [ "rust-src" ];
|
||||
};
|
||||
naersk-lib = naersk.lib."${system}".override {
|
||||
cargo = my-rust;
|
||||
rustc = my-rust;
|
||||
};
|
||||
inherit (pkgs) lib;
|
||||
in
|
||||
rec {
|
||||
checks = {
|
||||
pre-commit =
|
||||
let
|
||||
# See https://github.com/cachix/pre-commit-hooks.nix/issues/126
|
||||
rust-env = pkgs.buildEnv {
|
||||
name = "rust-env";
|
||||
buildInputs = [ pkgs.makeWrapper ];
|
||||
paths = [ my-rust ];
|
||||
pathsToLink = [ "/" "/bin" ];
|
||||
postBuild = ''
|
||||
for i in $out/bin/*; do
|
||||
wrapProgram "$i" --prefix PATH : "$out/bin"
|
||||
done
|
||||
'';
|
||||
};
|
||||
in
|
||||
pre-commit-hooks.lib.${system}.run {
|
||||
src = self;
|
||||
|
||||
cargoLock = {
|
||||
lockFile = "${self}/Cargo.lock";
|
||||
};
|
||||
hooks = {
|
||||
clippy = {
|
||||
enable = true;
|
||||
entry = lib.mkForce "${rust-env}/bin/cargo-clippy clippy";
|
||||
};
|
||||
|
||||
meta = with lib; {
|
||||
description = "A chess engine";
|
||||
homepage = "https://git.belanyi.fr/ambroisie/seer";
|
||||
license = licenses.mit;
|
||||
maintainers = with maintainers; [ ambroisie ];
|
||||
nixpkgs-fmt = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
rustfmt = {
|
||||
enable = true;
|
||||
entry = lib.mkForce "${rust-env}/bin/cargo-fmt fmt -- --check --color always";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
devShells = {
|
||||
default = pkgs.mkShell {
|
||||
inputsFrom = [
|
||||
packages.seer
|
||||
];
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
rust-analyzer
|
||||
# Clippy, rustfmt, etc...
|
||||
my-rust
|
||||
];
|
||||
|
||||
inherit (checks.pre-commit) shellHook;
|
||||
|
||||
RUST_SRC_PATH = "${my-rust}/lib/rustlib/src/rust/library";
|
||||
};
|
||||
};
|
||||
|
||||
packages = {
|
||||
default = self.packages."${system}".seer;
|
||||
|
||||
seer = naersk-lib.buildPackage {
|
||||
src = self;
|
||||
|
||||
doCheck = true;
|
||||
|
||||
passthru = {
|
||||
inherit my-rust;
|
||||
};
|
||||
};
|
||||
};
|
||||
} // futils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [
|
||||
self.overlays.default
|
||||
];
|
||||
};
|
||||
|
||||
pre-commit = pre-commit-hooks.lib.${system}.run {
|
||||
src = self;
|
||||
|
||||
hooks = {
|
||||
clippy = {
|
||||
enable = true;
|
||||
settings = {
|
||||
denyWarnings = true;
|
||||
};
|
||||
};
|
||||
|
||||
nixpkgs-fmt = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
rustfmt = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
checks = {
|
||||
inherit (self.packages.${system}) seer;
|
||||
};
|
||||
|
||||
devShells = {
|
||||
default = pkgs.mkShell {
|
||||
inputsFrom = with self.packages.${system}; [
|
||||
seer
|
||||
];
|
||||
|
||||
packages = with pkgs; [
|
||||
clippy
|
||||
rust-analyzer
|
||||
rustfmt
|
||||
];
|
||||
|
||||
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||
|
||||
inherit (pre-commit) shellHook;
|
||||
};
|
||||
};
|
||||
|
||||
packages = futils.lib.flattenTree {
|
||||
default = pkgs.seer;
|
||||
inherit (pkgs) seer;
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum IntoSquareError {
|
||||
/// The board is empty.
|
||||
EmptyBoard,
|
||||
/// The board contains more than one square.
|
||||
TooManySquares,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for IntoSquareError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let error_msg = match self {
|
||||
Self::EmptyBoard => "The board is empty",
|
||||
Self::TooManySquares => "The board contains more than one square",
|
||||
};
|
||||
write!(f, "{}", error_msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for IntoSquareError {}
|
||||
|
|
@ -1,14 +1,6 @@
|
|||
/// An [Iterator](std::iter::Iterator) of [Square](crate::board::Square) contained in a
|
||||
/// [Bitboard].
|
||||
use crate::board::Bitboard;
|
||||
|
||||
pub struct BitboardIterator(u64);
|
||||
|
||||
impl BitboardIterator {
|
||||
pub fn new(board: Bitboard) -> Self {
|
||||
Self(board.0)
|
||||
}
|
||||
}
|
||||
/// [Bitboard](crate::board::Bitboard).
|
||||
pub struct BitboardIterator(pub(crate) u64);
|
||||
|
||||
impl Iterator for BitboardIterator {
|
||||
type Item = crate::board::Square;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
use super::Square;
|
||||
use crate::utils::static_assert;
|
||||
|
||||
mod error;
|
||||
use error::*;
|
||||
mod iterator;
|
||||
use iterator::*;
|
||||
mod superset;
|
||||
use superset::*;
|
||||
|
||||
/// Use a 64-bit number to represent a chessboard. Each bit is mapped from to a specific square, so
|
||||
/// that index 0 -> A1, 1 -> A2, ..., 63 -> H8.
|
||||
|
|
@ -67,22 +63,6 @@ impl Bitboard {
|
|||
pub fn is_empty(self) -> bool {
|
||||
self == Self::EMPTY
|
||||
}
|
||||
|
||||
/// Return true if there are more than piece in the [Bitboard]. This is faster than testing
|
||||
/// `board.count() > 1`.
|
||||
#[inline(always)]
|
||||
pub fn has_more_than_one(self) -> bool {
|
||||
(self.0 & (self.0.wrapping_sub(1))) != 0
|
||||
}
|
||||
|
||||
/// Iterate over the power-set of a given [Bitboard], yielding each possible sub-set of
|
||||
/// [Square] that belong to the [Bitboard]. In other words, generate all set of [Square] that
|
||||
/// contain all, some, or none of the [Square] that are in the given [Bitboard].
|
||||
/// If given an empty [Bitboard], yields the empty [Bitboard] back.
|
||||
#[inline(always)]
|
||||
pub fn iter_power_set(self) -> impl Iterator<Item = Self> {
|
||||
BitboardPowerSetIterator::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure zero-cost (at least size-wise) wrapping.
|
||||
|
|
@ -94,28 +74,13 @@ impl Default for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Iterate over the [Square] values included in the board.
|
||||
/// Iterate over the [Square](crate::board::Square) values included in the board.
|
||||
impl IntoIterator for Bitboard {
|
||||
type IntoIter = BitboardIterator;
|
||||
type Item = Square;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
BitboardIterator::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// If the given [Bitboard] is a singleton piece on a board, return the [Square] that it is
|
||||
/// occupying. Otherwise return `None`.
|
||||
impl TryInto<Square> for Bitboard {
|
||||
type Error = IntoSquareError;
|
||||
|
||||
fn try_into(self) -> Result<Square, Self::Error> {
|
||||
let index = match self.count() {
|
||||
1 => self.0.trailing_zeros() as usize,
|
||||
0 => return Err(IntoSquareError::EmptyBoard),
|
||||
_ => return Err(IntoSquareError::TooManySquares),
|
||||
};
|
||||
Ok(Square::from_index(index))
|
||||
BitboardIterator(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,22 +104,6 @@ impl std::ops::Shr<usize> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat bitboard as a set of squares, shift each square's index left by the amount given.
|
||||
impl std::ops::ShlAssign<usize> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn shl_assign(&mut self, rhs: usize) {
|
||||
*self = *self << rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat bitboard as a set of squares, shift each square's index right by the amount given.
|
||||
impl std::ops::ShrAssign<usize> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn shr_assign(&mut self, rhs: usize) {
|
||||
*self = *self >> rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat bitboard as a set of squares, and invert the set.
|
||||
impl std::ops::Not for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
|
@ -175,7 +124,7 @@ impl std::ops::BitOr<Bitboard> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
/// Treat the [Square](crate::board::Square) as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::BitOr<Square> for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
||||
|
|
@ -185,22 +134,6 @@ impl std::ops::BitOr<Square> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, keep squares that are in either sets.
|
||||
impl std::ops::BitOrAssign<Bitboard> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitor_assign(&mut self, rhs: Bitboard) {
|
||||
*self = *self | rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::BitOrAssign<Square> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitor_assign(&mut self, rhs: Square) {
|
||||
*self = *self | rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, keep squares that are in both sets.
|
||||
impl std::ops::BitAnd<Bitboard> for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
|
@ -211,7 +144,7 @@ impl std::ops::BitAnd<Bitboard> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
/// Treat the [Square](crate::board::Square) as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::BitAnd<Square> for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
||||
|
|
@ -221,22 +154,6 @@ impl std::ops::BitAnd<Square> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, keep squares that are in both sets.
|
||||
impl std::ops::BitAndAssign<Bitboard> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitand_assign(&mut self, rhs: Bitboard) {
|
||||
*self = *self & rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::BitAndAssign<Square> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitand_assign(&mut self, rhs: Square) {
|
||||
*self = *self & rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, keep squares that are in exactly one of either set.
|
||||
impl std::ops::BitXor<Bitboard> for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
|
@ -247,7 +164,7 @@ impl std::ops::BitXor<Bitboard> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
/// Treat the [Square](crate::board::Square) as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::BitXor<Square> for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
||||
|
|
@ -257,22 +174,6 @@ impl std::ops::BitXor<Square> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, keep squares that are in exactly one of either set.
|
||||
impl std::ops::BitXorAssign<Bitboard> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitxor_assign(&mut self, rhs: Bitboard) {
|
||||
*self = *self ^ rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::BitXorAssign<Square> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitxor_assign(&mut self, rhs: Square) {
|
||||
*self = *self ^ rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, and substract one set from another.
|
||||
impl std::ops::Sub<Bitboard> for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
|
@ -283,7 +184,7 @@ impl std::ops::Sub<Bitboard> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
/// Treat the [Square](crate::board::Square) as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::Sub<Square> for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
||||
|
|
@ -293,28 +194,10 @@ impl std::ops::Sub<Square> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, and substract one set from another.
|
||||
impl std::ops::SubAssign<Bitboard> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn sub_assign(&mut self, rhs: Bitboard) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::SubAssign<Square> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn sub_assign(&mut self, rhs: Square) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::*;
|
||||
use crate::board::{square::*, File, Rank};
|
||||
use crate::board::square::*;
|
||||
|
||||
#[test]
|
||||
fn count() {
|
||||
|
|
@ -397,104 +280,4 @@ mod test {
|
|||
assert_eq!(Bitboard::FILES[0] - Bitboard::RANKS[0], Bitboard(0xff - 1));
|
||||
assert_eq!(Bitboard::FILES[0] - Square::A1, Bitboard(0xff - 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn more_than_one() {
|
||||
assert!(!Bitboard::EMPTY.has_more_than_one());
|
||||
for square in Square::iter() {
|
||||
assert!(!square.into_bitboard().has_more_than_one())
|
||||
}
|
||||
assert!((Square::A1 | Square::H8).has_more_than_one());
|
||||
assert!(Bitboard::ALL.has_more_than_one());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_power_set_empty() {
|
||||
assert_eq!(
|
||||
Bitboard::EMPTY.iter_power_set().collect::<Vec<_>>(),
|
||||
vec![Bitboard::EMPTY]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_power_set_one_square() {
|
||||
for square in Square::iter() {
|
||||
assert_eq!(
|
||||
square
|
||||
.into_bitboard()
|
||||
.iter_power_set()
|
||||
.collect::<HashSet<_>>(),
|
||||
[Bitboard::EMPTY, square.into_bitboard()]
|
||||
.into_iter()
|
||||
.collect::<HashSet<_>>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_power_set_two_squares() {
|
||||
assert_eq!(
|
||||
(Square::A1 | Square::H8)
|
||||
.iter_power_set()
|
||||
.collect::<HashSet<_>>(),
|
||||
[
|
||||
Bitboard::EMPTY,
|
||||
Square::A1.into_bitboard(),
|
||||
Square::H8.into_bitboard(),
|
||||
Square::A1 | Square::H8
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashSet<_>>()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_power_set_six_squares_exhaustive() {
|
||||
let mask = (0..6)
|
||||
.map(Square::from_index)
|
||||
.fold(Bitboard::EMPTY, |lhs, rhs| lhs | rhs);
|
||||
assert_eq!(
|
||||
mask.iter_power_set().collect::<HashSet<_>>(),
|
||||
(0..(1 << 6)).map(Bitboard).collect::<HashSet<_>>()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_power_set_eight_squares_length() {
|
||||
assert_eq!(
|
||||
File::A
|
||||
.into_bitboard()
|
||||
.iter_power_set()
|
||||
.collect::<HashSet<_>>()
|
||||
.len(),
|
||||
1 << 8
|
||||
);
|
||||
assert_eq!(
|
||||
Rank::First
|
||||
.into_bitboard()
|
||||
.iter_power_set()
|
||||
.collect::<HashSet<_>>()
|
||||
.len(),
|
||||
1 << 8
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_square() {
|
||||
for square in Square::iter() {
|
||||
assert_eq!(square.into_bitboard().try_into(), Ok(square));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_square_invalid() {
|
||||
assert_eq!(
|
||||
TryInto::<Square>::try_into(Bitboard::EMPTY),
|
||||
Err(IntoSquareError::EmptyBoard)
|
||||
);
|
||||
assert_eq!(
|
||||
TryInto::<Square>::try_into(Square::A1 | Square::A2),
|
||||
Err(IntoSquareError::TooManySquares)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
use super::Bitboard;
|
||||
|
||||
/// Iterator over a [Bitboard] mask, which yields all potential subsets of the given board.
|
||||
/// In other words, for each square that belongs to the mask, this will yield all sets that do
|
||||
/// contain the square, and all sets that do not.
|
||||
pub struct BitboardPowerSetIterator {
|
||||
/// The starting board.
|
||||
board: Bitboard,
|
||||
/// The next subset.
|
||||
subset: Bitboard,
|
||||
/// Whether or not iteration is done.
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl BitboardPowerSetIterator {
|
||||
pub fn new(board: Bitboard) -> Self {
|
||||
Self {
|
||||
board,
|
||||
subset: Bitboard::EMPTY,
|
||||
done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for BitboardPowerSetIterator {
|
||||
type Item = Bitboard;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.done {
|
||||
return None;
|
||||
}
|
||||
let res = self.subset;
|
||||
self.subset = Bitboard(self.subset.0.wrapping_sub(self.board.0)) & self.board;
|
||||
self.done = self.subset.is_empty();
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let size = 1 << self.board.count();
|
||||
(size, Some(size))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for BitboardPowerSetIterator {}
|
||||
|
||||
impl std::iter::FusedIterator for BitboardPowerSetIterator {}
|
||||
|
|
@ -14,22 +14,15 @@ pub enum CastleRights {
|
|||
}
|
||||
|
||||
impl CastleRights {
|
||||
/// The number of [CastleRights] variants.
|
||||
pub const NUM_VARIANTS: usize = 4;
|
||||
|
||||
/// Convert from a castle rights index into a [CastleRights] type.
|
||||
#[inline(always)]
|
||||
pub fn from_index(index: usize) -> Self {
|
||||
assert!(index < Self::NUM_VARIANTS);
|
||||
assert!(index < 4);
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Self::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Convert from a castle rights index into a [CastleRights] type, no bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This should only be called with values that can be output by [CastleRights::index()].
|
||||
#[inline(always)]
|
||||
pub unsafe fn from_index_unchecked(index: usize) -> Self {
|
||||
std::mem::transmute(index as u8)
|
||||
|
|
@ -53,25 +46,6 @@ impl CastleRights {
|
|||
(self.index() & 2) != 0
|
||||
}
|
||||
|
||||
/// Add king-side castling rights.
|
||||
#[inline(always)]
|
||||
pub fn with_king_side(self) -> Self {
|
||||
self.add(Self::KingSide)
|
||||
}
|
||||
|
||||
/// Add queen-side castling rights.
|
||||
#[inline(always)]
|
||||
pub fn with_queen_side(self) -> Self {
|
||||
self.add(Self::QueenSide)
|
||||
}
|
||||
|
||||
/// Add some [CastleRights], and return the resulting [CastleRights].
|
||||
#[inline(always)]
|
||||
fn add(self, additional_rights: CastleRights) -> Self {
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Self::from_index_unchecked(self.index() | additional_rights.index()) }
|
||||
}
|
||||
|
||||
/// Remove king-side castling rights.
|
||||
#[inline(always)]
|
||||
pub fn without_king_side(self) -> Self {
|
||||
|
|
|
|||
|
|
@ -1,161 +0,0 @@
|
|||
use crate::board::{Bitboard, CastleRights, ChessBoard, Color, InvalidError, Piece, Square};
|
||||
|
||||
/// Build a [ChessBoard] one piece at a time.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ChessBoardBuilder {
|
||||
/// The list of [Piece] on the board. Indexed by [Square::index].
|
||||
pieces: [Option<(Piece, Color)>; 64],
|
||||
// Same fields as [ChessBoard].
|
||||
castle_rights: [CastleRights; Color::NUM_VARIANTS],
|
||||
en_passant: Option<Square>,
|
||||
half_move_clock: u8,
|
||||
total_plies: u32,
|
||||
side: Color,
|
||||
}
|
||||
|
||||
impl ChessBoardBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pieces: [None; 64],
|
||||
castle_rights: [CastleRights::NoSide; 2],
|
||||
en_passant: Default::default(),
|
||||
half_move_clock: Default::default(),
|
||||
total_plies: Default::default(),
|
||||
side: Color::White,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_castle_rights(&mut self, rights: CastleRights, color: Color) -> &mut Self {
|
||||
self.castle_rights[color.index()] = rights;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_en_passant(&mut self, square: Square) -> &mut Self {
|
||||
self.en_passant = Some(square);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn without_en_passant(&mut self) -> &mut Self {
|
||||
self.en_passant = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_half_move_clock(&mut self, clock: u8) -> &mut Self {
|
||||
self.half_move_clock = clock;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_total_plies(&mut self, plies: u32) -> &mut Self {
|
||||
self.total_plies = plies;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_current_player(&mut self, color: Color) -> &mut Self {
|
||||
self.side = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChessBoardBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Index a [ChessBoardBuilder] with a [Square] to access its pieces.
|
||||
impl std::ops::Index<Square> for ChessBoardBuilder {
|
||||
type Output = Option<(Piece, Color)>;
|
||||
|
||||
fn index(&self, square: Square) -> &Self::Output {
|
||||
&self.pieces[square.index()]
|
||||
}
|
||||
}
|
||||
|
||||
/// Index a [ChessBoardBuilder] with a [Square] to access its pieces.
|
||||
impl std::ops::IndexMut<Square> for ChessBoardBuilder {
|
||||
fn index_mut(&mut self, square: Square) -> &mut Self::Output {
|
||||
&mut self.pieces[square.index()]
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ChessBoardBuilder> for ChessBoard {
|
||||
type Error = InvalidError;
|
||||
|
||||
fn try_from(builder: ChessBoardBuilder) -> Result<Self, Self::Error> {
|
||||
let mut piece_occupancy: [Bitboard; Piece::NUM_VARIANTS] = Default::default();
|
||||
let mut color_occupancy: [Bitboard; Color::NUM_VARIANTS] = Default::default();
|
||||
let mut combined_occupancy: Bitboard = Default::default();
|
||||
let ChessBoardBuilder {
|
||||
pieces,
|
||||
castle_rights,
|
||||
en_passant,
|
||||
half_move_clock,
|
||||
total_plies,
|
||||
side,
|
||||
} = builder;
|
||||
|
||||
for square in Square::iter() {
|
||||
let Some((piece, color)) = pieces[square.index()] else {
|
||||
continue;
|
||||
};
|
||||
piece_occupancy[piece.index()] |= square;
|
||||
color_occupancy[color.index()] |= square;
|
||||
combined_occupancy |= square;
|
||||
}
|
||||
|
||||
let board = ChessBoard {
|
||||
piece_occupancy,
|
||||
color_occupancy,
|
||||
combined_occupancy,
|
||||
castle_rights,
|
||||
en_passant,
|
||||
half_move_clock,
|
||||
total_plies,
|
||||
side,
|
||||
};
|
||||
|
||||
board.validate()?;
|
||||
Ok(board)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
fn from_board(board: &ChessBoard) -> ChessBoardBuilder {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
|
||||
for piece in Piece::iter() {
|
||||
for color in Color::iter() {
|
||||
for square in board.occupancy(piece, color) {
|
||||
builder[square] = Some((piece, color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for color in Color::iter() {
|
||||
builder.with_castle_rights(board.castle_rights(color), color);
|
||||
}
|
||||
|
||||
if let Some(square) = board.en_passant() {
|
||||
builder.with_en_passant(square);
|
||||
} else {
|
||||
builder.without_en_passant();
|
||||
}
|
||||
|
||||
builder
|
||||
.with_half_move_clock(board.half_move_clock())
|
||||
.with_total_plies(board.total_plies())
|
||||
.with_current_player(board.current_player());
|
||||
|
||||
builder
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_board() {
|
||||
let board = ChessBoard::default();
|
||||
let builder = from_board(&board);
|
||||
assert_eq!(board, builder.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
/// A singular type for all errors that could happen during [ChessBoard::is_valid].
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum InvalidError {
|
||||
/// Too many pieces.
|
||||
TooManyPieces,
|
||||
/// Missing king.
|
||||
MissingKing,
|
||||
/// Pawns on the first/last rank.
|
||||
InvalidPawnPosition,
|
||||
/// Castling rights do not match up with the state of the board.
|
||||
InvalidCastlingRights,
|
||||
/// En-passant target square is not empty, behind an opponent's pawn, on the correct rank.
|
||||
InvalidEnPassant,
|
||||
/// The two kings are next to each other.
|
||||
NeighbouringKings,
|
||||
/// The opponent is currently in check.
|
||||
OpponentInCheck,
|
||||
/// The piece-specific boards are overlapping.
|
||||
OverlappingPieces,
|
||||
/// The color-specific boards are overlapping.
|
||||
OverlappingColors,
|
||||
/// The pre-computed combined occupancy boards does not match the other boards.
|
||||
ErroneousCombinedOccupancy,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InvalidError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let error_msg = match self {
|
||||
Self::TooManyPieces => "Too many pieces.",
|
||||
Self::MissingKing => "Missing king.",
|
||||
Self::InvalidPawnPosition => "Pawns on the first/last rank.",
|
||||
Self::InvalidCastlingRights => {
|
||||
"Castling rights do not match up with the state of the board."
|
||||
}
|
||||
Self::InvalidEnPassant => {
|
||||
"En-passant target square is not empty, behind an opponent's pawn, on the correct rank."
|
||||
}
|
||||
Self::NeighbouringKings => "The two kings are next to each other.",
|
||||
Self::OpponentInCheck => "The opponent is currently in check.",
|
||||
Self::OverlappingPieces => "The piece-specific boards are overlapping.",
|
||||
Self::OverlappingColors => "The color-specific boards are overlapping.",
|
||||
Self::ErroneousCombinedOccupancy => {
|
||||
"The pre-computed combined occupancy boards does not match the other boards."
|
||||
}
|
||||
};
|
||||
write!(f, "{}", error_msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InvalidError {}
|
||||
|
|
@ -1,862 +0,0 @@
|
|||
use crate::movegen;
|
||||
|
||||
use super::{Bitboard, CastleRights, Color, File, Move, Piece, Rank, Square};
|
||||
|
||||
mod builder;
|
||||
pub use builder::*;
|
||||
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
/// Represent an on-going chess game.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ChessBoard {
|
||||
/// A [Bitboard] of occupancy for each piece type, discarding color. Indexed by [Piece::index].
|
||||
piece_occupancy: [Bitboard; Piece::NUM_VARIANTS],
|
||||
/// A [Bitboard] of occupancy for each color, discarding piece type. Indexed by [Piece::index].
|
||||
color_occupancy: [Bitboard; Color::NUM_VARIANTS],
|
||||
/// A [Bitboard] representing all squares currently occupied by a piece.
|
||||
combined_occupancy: Bitboard,
|
||||
/// The allowed [CastleRights] for either color. Indexed by [Color::index].
|
||||
castle_rights: [CastleRights; Color::NUM_VARIANTS],
|
||||
/// A potential en-passant attack.
|
||||
/// Either `None` if no double-step pawn move was made in the previous half-turn, or
|
||||
/// `Some(target_square)` if a double-step move was made.
|
||||
en_passant: Option<Square>,
|
||||
/// The number of half-turns without either a pawn push or capture.
|
||||
half_move_clock: u8, // Should never go higher than 50.
|
||||
/// The number of half-turns so far.
|
||||
total_plies: u32, // Should be plenty.
|
||||
/// The current player turn.
|
||||
side: Color,
|
||||
}
|
||||
|
||||
/// The state which can't be reversed when doing/un-doing a [Move].
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct NonReversibleState {
|
||||
castle_rights: [CastleRights; Color::NUM_VARIANTS],
|
||||
en_passant: Option<Square>,
|
||||
half_move_clock: u8, // Should never go higher than 50.
|
||||
}
|
||||
|
||||
impl ChessBoard {
|
||||
/// Which player's turn is it.
|
||||
#[inline(always)]
|
||||
pub fn current_player(&self) -> Color {
|
||||
self.side
|
||||
}
|
||||
|
||||
/// Return the [Square] currently occupied by a pawn that can be captured en-passant, or `None`
|
||||
#[inline(always)]
|
||||
pub fn en_passant(&self) -> Option<Square> {
|
||||
self.en_passant
|
||||
}
|
||||
|
||||
/// Return the [CastleRights] for the given [Color].
|
||||
#[inline(always)]
|
||||
pub fn castle_rights(&self, color: Color) -> CastleRights {
|
||||
self.castle_rights[color.index()]
|
||||
}
|
||||
|
||||
/// Return the [CastleRights] for the given [Color]. Allow mutations.
|
||||
#[inline(always)]
|
||||
fn castle_rights_mut(&mut self, color: Color) -> &mut CastleRights {
|
||||
&mut self.castle_rights[color.index()]
|
||||
}
|
||||
|
||||
/// Get the [Bitboard] representing all pieces of the given [Piece] and [Color] type.
|
||||
#[inline(always)]
|
||||
pub fn occupancy(&self, piece: Piece, color: Color) -> Bitboard {
|
||||
self.piece_occupancy(piece) & self.color_occupancy(color)
|
||||
}
|
||||
|
||||
/// Get the [Bitboard] representing all pieces of the given [Piece] type, discarding color.
|
||||
#[inline(always)]
|
||||
pub fn piece_occupancy(&self, piece: Piece) -> Bitboard {
|
||||
self.piece_occupancy[piece.index()]
|
||||
}
|
||||
|
||||
/// Get the [Bitboard] representing all pieces of the given [Piece] type, discarding color.
|
||||
/// Allow mutating the state.
|
||||
#[inline(always)]
|
||||
fn piece_occupancy_mut(&mut self, piece: Piece) -> &mut Bitboard {
|
||||
&mut self.piece_occupancy[piece.index()]
|
||||
}
|
||||
|
||||
/// Get the [Bitboard] representing all colors of the given [Color] type, discarding piece
|
||||
/// type.
|
||||
#[inline(always)]
|
||||
pub fn color_occupancy(&self, color: Color) -> Bitboard {
|
||||
self.color_occupancy[color.index()]
|
||||
}
|
||||
|
||||
/// Get the [Bitboard] representing all colors of the given [Color] type, discarding piece
|
||||
/// type. Allow mutating the state.
|
||||
#[inline(always)]
|
||||
fn color_occupancy_mut(&mut self, color: Color) -> &mut Bitboard {
|
||||
&mut self.color_occupancy[color.index()]
|
||||
}
|
||||
|
||||
/// Get the [Bitboard] representing all pieces on the board.
|
||||
#[inline(always)]
|
||||
pub fn combined_occupancy(&self) -> Bitboard {
|
||||
self.combined_occupancy
|
||||
}
|
||||
|
||||
/// Return the number of half-turns without either a pawn push or a capture.
|
||||
#[inline(always)]
|
||||
pub fn half_move_clock(&self) -> u8 {
|
||||
self.half_move_clock
|
||||
}
|
||||
|
||||
/// Return the total number of plies (i.e: half-turns) played so far.
|
||||
#[inline(always)]
|
||||
pub fn total_plies(&self) -> u32 {
|
||||
self.total_plies
|
||||
}
|
||||
|
||||
/// Return the [Bitboard] corresponding to all the opponent's pieces threatening the current
|
||||
/// player's king.
|
||||
#[inline(always)]
|
||||
pub fn checkers(&self) -> Bitboard {
|
||||
self.compute_checkers(self.current_player())
|
||||
}
|
||||
|
||||
/// Quickly do and undo a move on the [Bitboard]s that are part of the [ChessBoard] state. Does
|
||||
/// not account for all non-revertible changes such as en-passant state or half-move clock.
|
||||
#[inline(always)]
|
||||
fn xor(&mut self, color: Color, piece: Piece, start_end: Bitboard) {
|
||||
*self.piece_occupancy_mut(piece) ^= start_end;
|
||||
*self.color_occupancy_mut(color) ^= start_end;
|
||||
self.combined_occupancy ^= start_end;
|
||||
}
|
||||
|
||||
/// Play the given [Move], returning all non-revertible state (e.g: en-passant, etc...).
|
||||
#[inline(always)]
|
||||
pub fn do_move(&mut self, chess_move: Move) -> NonReversibleState {
|
||||
// Save non-revertible state
|
||||
let state = NonReversibleState {
|
||||
castle_rights: self.castle_rights,
|
||||
en_passant: self.en_passant,
|
||||
half_move_clock: self.half_move_clock,
|
||||
};
|
||||
|
||||
// Non-revertible state modification
|
||||
if chess_move.capture().is_some() || chess_move.piece() == Piece::Pawn {
|
||||
self.half_move_clock = 0;
|
||||
} else {
|
||||
self.half_move_clock += 1;
|
||||
}
|
||||
if chess_move.is_double_step() {
|
||||
let target_square = Square::new(
|
||||
chess_move.destination().file(),
|
||||
self.current_player().third_rank(),
|
||||
);
|
||||
self.en_passant = Some(target_square);
|
||||
} else {
|
||||
self.en_passant = None;
|
||||
}
|
||||
if chess_move.is_castling() || chess_move.piece() == Piece::King {
|
||||
*self.castle_rights_mut(self.current_player()) = CastleRights::NoSide;
|
||||
}
|
||||
if chess_move.piece() == Piece::Rook {
|
||||
let castle_rights = self.castle_rights_mut(self.current_player());
|
||||
*castle_rights = match chess_move.start().file() {
|
||||
File::A => castle_rights.without_queen_side(),
|
||||
File::H => castle_rights.without_king_side(),
|
||||
_ => *castle_rights,
|
||||
}
|
||||
}
|
||||
|
||||
// Revertible state modification
|
||||
self.xor(
|
||||
self.current_player(),
|
||||
chess_move.piece(),
|
||||
chess_move.start() | chess_move.destination(),
|
||||
);
|
||||
self.total_plies += 1;
|
||||
self.side = !self.side;
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
/// Reverse the effect of playing the given [Move], and return to the given
|
||||
/// [NonReversibleState].
|
||||
#[inline(always)]
|
||||
pub fn undo_move(&mut self, chess_move: Move, previous: NonReversibleState) {
|
||||
// Restore non-revertible state
|
||||
self.castle_rights = previous.castle_rights;
|
||||
self.en_passant = previous.en_passant;
|
||||
self.half_move_clock = previous.half_move_clock;
|
||||
|
||||
// Restore revertible state
|
||||
self.xor(
|
||||
// The move was applied at the turn *before* the current player
|
||||
!self.current_player(),
|
||||
chess_move.piece(),
|
||||
chess_move.start() | chess_move.destination(),
|
||||
);
|
||||
self.total_plies -= 1;
|
||||
self.side = !self.side;
|
||||
}
|
||||
|
||||
/// Return true if the current state of the board looks valid, false if something is definitely
|
||||
/// wrong.
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.validate().is_ok()
|
||||
}
|
||||
|
||||
/// Validate the state of the board. Return Err([InvalidError]) if an issue is found.
|
||||
pub fn validate(&self) -> Result<(), InvalidError> {
|
||||
// Don't overlap pieces.
|
||||
for piece in Piece::iter() {
|
||||
#[allow(clippy::collapsible_if)]
|
||||
for other in Piece::iter() {
|
||||
if piece != other {
|
||||
if !(self.piece_occupancy(piece) & self.piece_occupancy(other)).is_empty() {
|
||||
return Err(InvalidError::OverlappingPieces);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't overlap colors.
|
||||
if !(self.color_occupancy(Color::White) & self.color_occupancy(Color::Black)).is_empty() {
|
||||
return Err(InvalidError::OverlappingColors);
|
||||
}
|
||||
|
||||
// Calculate the union of all pieces.
|
||||
let combined =
|
||||
Piece::iter().fold(Bitboard::EMPTY, |board, p| board | self.piece_occupancy(p));
|
||||
|
||||
// Ensure that the pre-computed version is accurate.
|
||||
if combined != self.combined_occupancy() {
|
||||
return Err(InvalidError::ErroneousCombinedOccupancy);
|
||||
}
|
||||
|
||||
// Ensure that all pieces belong to a color, and no color has pieces that don't exist.
|
||||
if combined != (self.color_occupancy(Color::White) | self.color_occupancy(Color::Black)) {
|
||||
return Err(InvalidError::ErroneousCombinedOccupancy);
|
||||
}
|
||||
|
||||
for color in Color::iter() {
|
||||
for piece in Piece::iter() {
|
||||
// Check that we have the expected number of piecese.
|
||||
let count = self.occupancy(piece, color).count();
|
||||
let possible = match piece {
|
||||
Piece::King => count <= 1,
|
||||
Piece::Pawn => count <= 8,
|
||||
Piece::Queen => count <= 9,
|
||||
_ => count <= 10,
|
||||
};
|
||||
if !possible {
|
||||
return Err(InvalidError::TooManyPieces);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we have a king
|
||||
if self.occupancy(Piece::King, color).count() != 1 {
|
||||
return Err(InvalidError::MissingKing);
|
||||
}
|
||||
|
||||
// Check that don't have too many pieces in total
|
||||
if self.color_occupancy(color).count() > 16 {
|
||||
return Err(InvalidError::TooManyPieces);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that pawns aren't in first/last rank.
|
||||
if !(self.piece_occupancy(Piece::Pawn)
|
||||
& (Rank::First.into_bitboard() | Rank::Eighth.into_bitboard()))
|
||||
.is_empty()
|
||||
{
|
||||
return Err(InvalidError::InvalidPawnPosition);
|
||||
}
|
||||
|
||||
// Verify that rooks and kings that are allowed to castle have not been moved.
|
||||
for color in Color::iter() {
|
||||
let castle_rights = self.castle_rights(color);
|
||||
|
||||
// Nothing to check if there are no castlings allowed.
|
||||
if castle_rights == CastleRights::NoSide {
|
||||
continue;
|
||||
}
|
||||
|
||||
let actual_rooks = self.occupancy(Piece::Rook, color);
|
||||
let expected_rooks = castle_rights.unmoved_rooks(color);
|
||||
// We must check the intersection, in case there are more than 2 rooks on the board.
|
||||
if (expected_rooks & actual_rooks) != expected_rooks {
|
||||
return Err(InvalidError::InvalidCastlingRights);
|
||||
}
|
||||
|
||||
let actual_king = self.occupancy(Piece::King, color);
|
||||
let expected_king = Square::new(File::E, color.first_rank());
|
||||
// We have checked that there is exactly one king, no need for intersecting the sets.
|
||||
if actual_king != expected_king.into_bitboard() {
|
||||
return Err(InvalidError::InvalidCastlingRights);
|
||||
}
|
||||
}
|
||||
|
||||
// En-passant validation
|
||||
if let Some(square) = self.en_passant() {
|
||||
// Must be empty
|
||||
if !(self.combined_occupancy() & square).is_empty() {
|
||||
return Err(InvalidError::InvalidEnPassant);
|
||||
}
|
||||
|
||||
let opponent = !self.current_player();
|
||||
|
||||
// Must be on the opponent's third rank
|
||||
if (square & opponent.third_rank().into_bitboard()).is_empty() {
|
||||
return Err(InvalidError::InvalidEnPassant);
|
||||
}
|
||||
|
||||
// Must be behind a pawn
|
||||
let opponent_pawns = self.occupancy(Piece::Pawn, opponent);
|
||||
let double_pushed_pawn = self
|
||||
.current_player()
|
||||
.backward_direction()
|
||||
.move_board(square.into_bitboard());
|
||||
if (opponent_pawns & double_pushed_pawn).is_empty() {
|
||||
return Err(InvalidError::InvalidEnPassant);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that kings don't touch each other.
|
||||
let white_king = self.occupancy(Piece::King, Color::White);
|
||||
let black_king = self.occupancy(Piece::King, Color::Black);
|
||||
// Unwrap is fine, we already checked that there is exactly one king of each color
|
||||
if !(movegen::king_moves(white_king.try_into().unwrap()) & black_king).is_empty() {
|
||||
return Err(InvalidError::NeighbouringKings);
|
||||
}
|
||||
|
||||
// Check that the opponent is not currently in check.
|
||||
if !self.compute_checkers(!self.current_player()).is_empty() {
|
||||
return Err(InvalidError::OpponentInCheck);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute all pieces that are currently threatening the given [Color]'s king.
|
||||
fn compute_checkers(&self, color: Color) -> Bitboard {
|
||||
// Unwrap is fine, there should always be exactly one king per color
|
||||
let king = (self.occupancy(Piece::King, color)).try_into().unwrap();
|
||||
|
||||
let opponent = !color;
|
||||
|
||||
// No need to remove our pieces from the generated moves, we just want to check if we
|
||||
// intersect with the opponent's pieces, rather than generate only valid moves.
|
||||
let bishops = {
|
||||
let queens = self.occupancy(Piece::Queen, opponent);
|
||||
let bishops = self.occupancy(Piece::Bishop, opponent);
|
||||
let bishop_attacks = movegen::bishop_moves(king, self.combined_occupancy());
|
||||
(queens | bishops) & bishop_attacks
|
||||
};
|
||||
let rooks = {
|
||||
let queens = self.occupancy(Piece::Queen, opponent);
|
||||
let rooks = self.occupancy(Piece::Rook, opponent);
|
||||
let rook_attacks = movegen::rook_moves(king, self.combined_occupancy());
|
||||
(queens | rooks) & rook_attacks
|
||||
};
|
||||
let knights = {
|
||||
let knights = self.occupancy(Piece::Knight, opponent);
|
||||
let knight_attacks = movegen::knight_moves(king);
|
||||
knights & knight_attacks
|
||||
};
|
||||
let pawns = {
|
||||
let pawns = self.occupancy(Piece::Pawn, opponent);
|
||||
let pawn_attacks = movegen::pawn_attacks(color, king);
|
||||
pawns & pawn_attacks
|
||||
};
|
||||
|
||||
bishops | rooks | knights | pawns
|
||||
}
|
||||
}
|
||||
|
||||
/// Use the starting position as a default value, corresponding to the
|
||||
/// "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" FEN string
|
||||
impl Default for ChessBoard {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
piece_occupancy: [
|
||||
// King
|
||||
Square::E1 | Square::E8,
|
||||
// Queen
|
||||
Square::D1 | Square::D8,
|
||||
// Rook
|
||||
Square::A1 | Square::A8 | Square::H1 | Square::H8,
|
||||
// Bishop
|
||||
Square::C1 | Square::C8 | Square::F1 | Square::F8,
|
||||
// Knight
|
||||
Square::B1 | Square::B8 | Square::G1 | Square::G8,
|
||||
// Pawn
|
||||
Rank::Second.into_bitboard() | Rank::Seventh.into_bitboard(),
|
||||
],
|
||||
color_occupancy: [
|
||||
Rank::First.into_bitboard() | Rank::Second.into_bitboard(),
|
||||
Rank::Seventh.into_bitboard() | Rank::Eighth.into_bitboard(),
|
||||
],
|
||||
combined_occupancy: Rank::First.into_bitboard()
|
||||
| Rank::Second.into_bitboard()
|
||||
| Rank::Seventh.into_bitboard()
|
||||
| Rank::Eighth.into_bitboard(),
|
||||
castle_rights: [CastleRights::BothSides; 2],
|
||||
en_passant: None,
|
||||
half_move_clock: 0,
|
||||
total_plies: 0,
|
||||
side: Color::White,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::board::MoveBuilder;
|
||||
use crate::fen::FromFen;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn valid() {
|
||||
let default_position = ChessBoard::default();
|
||||
assert!(default_position.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_overlapping_pieces() {
|
||||
let position = ChessBoard {
|
||||
piece_occupancy: [
|
||||
// King
|
||||
Square::E1 | Square::E8,
|
||||
// Queen
|
||||
Square::E1 | Square::E8,
|
||||
// Rook
|
||||
Bitboard::EMPTY,
|
||||
// Bishop
|
||||
Bitboard::EMPTY,
|
||||
// Knight
|
||||
Bitboard::EMPTY,
|
||||
// Pawn
|
||||
Bitboard::EMPTY,
|
||||
],
|
||||
color_occupancy: [Square::E1.into_bitboard(), Square::E8.into_bitboard()],
|
||||
combined_occupancy: Square::E1 | Square::E8,
|
||||
castle_rights: [CastleRights::NoSide; 2],
|
||||
en_passant: None,
|
||||
half_move_clock: 0,
|
||||
total_plies: 0,
|
||||
side: Color::White,
|
||||
};
|
||||
assert_eq!(
|
||||
position.validate().err().unwrap(),
|
||||
InvalidError::OverlappingPieces,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_overlapping_colors() {
|
||||
let position = ChessBoard {
|
||||
piece_occupancy: [
|
||||
// King
|
||||
Square::E1 | Square::E8,
|
||||
// Queen
|
||||
Bitboard::EMPTY,
|
||||
// Rook
|
||||
Bitboard::EMPTY,
|
||||
// Bishop
|
||||
Bitboard::EMPTY,
|
||||
// Knight
|
||||
Bitboard::EMPTY,
|
||||
// Pawn
|
||||
Bitboard::EMPTY,
|
||||
],
|
||||
color_occupancy: [Square::E1 | Square::E8, Square::E1 | Square::E8],
|
||||
combined_occupancy: Square::E1 | Square::E8,
|
||||
castle_rights: [CastleRights::NoSide; 2],
|
||||
en_passant: None,
|
||||
half_move_clock: 0,
|
||||
total_plies: 0,
|
||||
side: Color::White,
|
||||
};
|
||||
assert_eq!(
|
||||
position.validate().err().unwrap(),
|
||||
InvalidError::OverlappingColors,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_combined_does_not_equal_pieces() {
|
||||
let position = ChessBoard {
|
||||
piece_occupancy: [
|
||||
// King
|
||||
Square::E1 | Square::E8,
|
||||
// Queen
|
||||
Bitboard::EMPTY,
|
||||
// Rook
|
||||
Bitboard::EMPTY,
|
||||
// Bishop
|
||||
Bitboard::EMPTY,
|
||||
// Knight
|
||||
Bitboard::EMPTY,
|
||||
// Pawn
|
||||
Bitboard::EMPTY,
|
||||
],
|
||||
color_occupancy: [Square::E1.into_bitboard(), Square::E8.into_bitboard()],
|
||||
combined_occupancy: Square::E1.into_bitboard(),
|
||||
castle_rights: [CastleRights::NoSide; 2],
|
||||
en_passant: None,
|
||||
half_move_clock: 0,
|
||||
total_plies: 0,
|
||||
side: Color::White,
|
||||
};
|
||||
assert_eq!(
|
||||
position.validate().err().unwrap(),
|
||||
InvalidError::ErroneousCombinedOccupancy,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_combined_does_not_equal_colors() {
|
||||
let position = ChessBoard {
|
||||
piece_occupancy: [
|
||||
// King
|
||||
Square::E1 | Square::E8,
|
||||
// Queen
|
||||
Bitboard::EMPTY,
|
||||
// Rook
|
||||
Bitboard::EMPTY,
|
||||
// Bishop
|
||||
Bitboard::EMPTY,
|
||||
// Knight
|
||||
Bitboard::EMPTY,
|
||||
// Pawn
|
||||
Bitboard::EMPTY,
|
||||
],
|
||||
color_occupancy: [Square::E1 | Square::H1, Square::E8 | Square::H8],
|
||||
combined_occupancy: Square::E1 | Square::E8,
|
||||
castle_rights: [CastleRights::NoSide; 2],
|
||||
en_passant: None,
|
||||
half_move_clock: 0,
|
||||
total_plies: 0,
|
||||
side: Color::White,
|
||||
};
|
||||
assert_eq!(
|
||||
position.validate().err().unwrap(),
|
||||
InvalidError::ErroneousCombinedOccupancy,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_multiple_kings() {
|
||||
let res = {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
builder[Square::E1] = Some((Piece::King, Color::White));
|
||||
builder[Square::E2] = Some((Piece::King, Color::White));
|
||||
builder[Square::E7] = Some((Piece::King, Color::Black));
|
||||
builder[Square::E8] = Some((Piece::King, Color::Black));
|
||||
TryInto::<ChessBoard>::try_into(builder)
|
||||
};
|
||||
assert_eq!(res.err().unwrap(), InvalidError::TooManyPieces);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_castling_rights_no_rooks() {
|
||||
let res = {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
builder[Square::E1] = Some((Piece::King, Color::White));
|
||||
builder[Square::E8] = Some((Piece::King, Color::Black));
|
||||
builder.with_castle_rights(CastleRights::BothSides, Color::White);
|
||||
TryInto::<ChessBoard>::try_into(builder)
|
||||
};
|
||||
assert_eq!(res.err().unwrap(), InvalidError::InvalidCastlingRights);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_castling_rights_moved_king() {
|
||||
let res = {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
builder[Square::E2] = Some((Piece::King, Color::White));
|
||||
builder[Square::A1] = Some((Piece::Rook, Color::White));
|
||||
builder[Square::H1] = Some((Piece::Rook, Color::White));
|
||||
builder[Square::E7] = Some((Piece::King, Color::Black));
|
||||
builder[Square::A8] = Some((Piece::Rook, Color::Black));
|
||||
builder[Square::H8] = Some((Piece::Rook, Color::Black));
|
||||
builder.with_castle_rights(CastleRights::BothSides, Color::White);
|
||||
TryInto::<ChessBoard>::try_into(builder)
|
||||
};
|
||||
assert_eq!(res.err().unwrap(), InvalidError::InvalidCastlingRights);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_en_passant() {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
builder[Square::E1] = Some((Piece::King, Color::White));
|
||||
builder[Square::E8] = Some((Piece::King, Color::Black));
|
||||
builder[Square::A5] = Some((Piece::Pawn, Color::Black));
|
||||
builder.with_en_passant(Square::A6);
|
||||
TryInto::<ChessBoard>::try_into(builder).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_en_passant_not_empty() {
|
||||
let res = {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
builder[Square::E1] = Some((Piece::King, Color::White));
|
||||
builder[Square::E8] = Some((Piece::King, Color::Black));
|
||||
builder[Square::A6] = Some((Piece::Rook, Color::Black));
|
||||
builder[Square::A5] = Some((Piece::Pawn, Color::Black));
|
||||
builder.with_en_passant(Square::A6);
|
||||
TryInto::<ChessBoard>::try_into(builder)
|
||||
};
|
||||
assert_eq!(res.err().unwrap(), InvalidError::InvalidEnPassant);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_en_passant_not_behind_pawn() {
|
||||
let res = {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
builder[Square::E1] = Some((Piece::King, Color::White));
|
||||
builder[Square::E8] = Some((Piece::King, Color::Black));
|
||||
builder[Square::A5] = Some((Piece::Rook, Color::Black));
|
||||
builder.with_en_passant(Square::A6);
|
||||
TryInto::<ChessBoard>::try_into(builder)
|
||||
};
|
||||
assert_eq!(res.err().unwrap(), InvalidError::InvalidEnPassant);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_en_passant_incorrect_rank() {
|
||||
let res = {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
builder[Square::E1] = Some((Piece::King, Color::White));
|
||||
builder[Square::E8] = Some((Piece::King, Color::Black));
|
||||
builder[Square::A4] = Some((Piece::Pawn, Color::Black));
|
||||
builder.with_en_passant(Square::A5);
|
||||
TryInto::<ChessBoard>::try_into(builder)
|
||||
};
|
||||
assert_eq!(res.err().unwrap(), InvalidError::InvalidEnPassant);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_kings_next_to_each_other() {
|
||||
let res = {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
builder[Square::E1] = Some((Piece::King, Color::White));
|
||||
builder[Square::E2] = Some((Piece::King, Color::Black));
|
||||
TryInto::<ChessBoard>::try_into(builder)
|
||||
};
|
||||
assert_eq!(res.err().unwrap(), InvalidError::NeighbouringKings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_opponent_in_check() {
|
||||
let res = {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
builder[Square::E1] = Some((Piece::King, Color::White));
|
||||
builder[Square::E7] = Some((Piece::Queen, Color::White));
|
||||
builder[Square::E8] = Some((Piece::King, Color::Black));
|
||||
TryInto::<ChessBoard>::try_into(builder)
|
||||
};
|
||||
assert_eq!(res.err().unwrap(), InvalidError::OpponentInCheck);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_pawn_on_first_rank() {
|
||||
let res = {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
builder[Square::H1] = Some((Piece::King, Color::White));
|
||||
builder[Square::A1] = Some((Piece::Pawn, Color::White));
|
||||
builder[Square::H8] = Some((Piece::King, Color::Black));
|
||||
TryInto::<ChessBoard>::try_into(builder)
|
||||
};
|
||||
assert_eq!(res.err().unwrap(), InvalidError::InvalidPawnPosition);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_too_many_pieces() {
|
||||
let res = {
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
builder[Square::H1] = Some((Piece::King, Color::White));
|
||||
builder[Square::H8] = Some((Piece::King, Color::Black));
|
||||
for square in (File::B.into_bitboard() | File::C.into_bitboard()) {
|
||||
builder[square] = Some((Piece::Pawn, Color::White));
|
||||
}
|
||||
for square in (File::F.into_bitboard() | File::G.into_bitboard()) {
|
||||
builder[square] = Some((Piece::Pawn, Color::Black));
|
||||
}
|
||||
TryInto::<ChessBoard>::try_into(builder)
|
||||
};
|
||||
assert_eq!(res.err().unwrap(), InvalidError::TooManyPieces);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checkers() {
|
||||
let position = ChessBoard {
|
||||
piece_occupancy: [
|
||||
// King
|
||||
Square::E2 | Square::E8,
|
||||
// Queen
|
||||
Square::E7 | Square::H2,
|
||||
// Rook
|
||||
Square::A2 | Square::E1,
|
||||
// Bishop
|
||||
Square::D3 | Square::F3,
|
||||
// Knight
|
||||
Square::C1 | Square::G1,
|
||||
// Pawn
|
||||
Bitboard::EMPTY,
|
||||
],
|
||||
color_occupancy: [
|
||||
Square::C1 | Square::D3 | Square::E1 | Square::E2 | Square::H2,
|
||||
Square::A2 | Square::E7 | Square::E8 | Square::F3 | Square::G1,
|
||||
],
|
||||
combined_occupancy: Square::A2
|
||||
| Square::C1
|
||||
| Square::D3
|
||||
| Square::E1
|
||||
| Square::E2
|
||||
| Square::E7
|
||||
| Square::E8
|
||||
| Square::F3
|
||||
| Square::G1
|
||||
| Square::H2,
|
||||
castle_rights: [CastleRights::NoSide; 2],
|
||||
en_passant: None,
|
||||
half_move_clock: 0,
|
||||
total_plies: 0,
|
||||
side: Color::White,
|
||||
};
|
||||
assert_eq!(
|
||||
position.checkers(),
|
||||
Square::A2 | Square::E7 | Square::F3 | Square::G1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_move() {
|
||||
// Start from default position
|
||||
let mut position = ChessBoard::default();
|
||||
// Modify it to account for e4 move
|
||||
position.do_move(
|
||||
MoveBuilder {
|
||||
piece: Piece::Pawn,
|
||||
start: Square::E2,
|
||||
destination: Square::E4,
|
||||
capture: None,
|
||||
promotion: None,
|
||||
en_passant: false,
|
||||
double_step: true,
|
||||
castling: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
position,
|
||||
ChessBoard::from_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1")
|
||||
.unwrap()
|
||||
);
|
||||
// And now c5
|
||||
position.do_move(
|
||||
MoveBuilder {
|
||||
piece: Piece::Pawn,
|
||||
start: Square::C7,
|
||||
destination: Square::C5,
|
||||
capture: None,
|
||||
promotion: None,
|
||||
en_passant: false,
|
||||
double_step: true,
|
||||
castling: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
position,
|
||||
ChessBoard::from_fen("rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2")
|
||||
.unwrap()
|
||||
);
|
||||
// Finally, Nf3
|
||||
position.do_move(
|
||||
MoveBuilder {
|
||||
piece: Piece::Knight,
|
||||
start: Square::G1,
|
||||
destination: Square::F3,
|
||||
capture: None,
|
||||
promotion: None,
|
||||
en_passant: false,
|
||||
double_step: false,
|
||||
castling: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
position,
|
||||
ChessBoard::from_fen("rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2 ")
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_move_and_undo() {
|
||||
// Start from default position
|
||||
let mut position = ChessBoard::default();
|
||||
// Modify it to account for e4 move
|
||||
let move_1 = MoveBuilder {
|
||||
piece: Piece::Pawn,
|
||||
start: Square::E2,
|
||||
destination: Square::E4,
|
||||
capture: None,
|
||||
promotion: None,
|
||||
en_passant: false,
|
||||
double_step: true,
|
||||
castling: false,
|
||||
}
|
||||
.into();
|
||||
let state_1 = position.do_move(move_1);
|
||||
// And now c5
|
||||
let move_2 = MoveBuilder {
|
||||
piece: Piece::Pawn,
|
||||
start: Square::C7,
|
||||
destination: Square::C5,
|
||||
capture: None,
|
||||
promotion: None,
|
||||
en_passant: false,
|
||||
double_step: true,
|
||||
castling: false,
|
||||
}
|
||||
.into();
|
||||
let state_2 = position.do_move(move_2);
|
||||
// Finally, Nf3
|
||||
let move_3 = MoveBuilder {
|
||||
piece: Piece::Knight,
|
||||
start: Square::G1,
|
||||
destination: Square::F3,
|
||||
capture: None,
|
||||
promotion: None,
|
||||
en_passant: false,
|
||||
double_step: false,
|
||||
castling: false,
|
||||
}
|
||||
.into();
|
||||
let state_3 = position.do_move(move_3);
|
||||
// Now revert each move one-by-one
|
||||
position.undo_move(move_3, state_3);
|
||||
assert_eq!(
|
||||
position,
|
||||
ChessBoard::from_fen("rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2")
|
||||
.unwrap()
|
||||
);
|
||||
position.undo_move(move_2, state_2);
|
||||
assert_eq!(
|
||||
position,
|
||||
ChessBoard::from_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1")
|
||||
.unwrap()
|
||||
);
|
||||
position.undo_move(move_1, state_1);
|
||||
assert_eq!(
|
||||
position,
|
||||
ChessBoard::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,29 +8,15 @@ pub enum Color {
|
|||
}
|
||||
|
||||
impl Color {
|
||||
/// The number of [Color] variants.
|
||||
pub const NUM_VARIANTS: usize = 2;
|
||||
|
||||
const ALL: [Self; Self::NUM_VARIANTS] = [Self::White, Self::Black];
|
||||
|
||||
/// Iterate over all colors in order.
|
||||
pub fn iter() -> impl Iterator<Item = Self> {
|
||||
Self::ALL.iter().cloned()
|
||||
}
|
||||
|
||||
/// Convert from a color index into a [Color] type.
|
||||
/// Convert from a file index into a [Color] type.
|
||||
#[inline(always)]
|
||||
pub fn from_index(index: usize) -> Self {
|
||||
assert!(index < Self::NUM_VARIANTS);
|
||||
assert!(index < 2);
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Self::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Convert from a color index into a [Color] type, no bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Should only be called with values that can be output by [Color::index()].
|
||||
/// Convert from a file index into a [Color] type, no bounds checking.
|
||||
#[inline(always)]
|
||||
pub unsafe fn from_index_unchecked(index: usize) -> Self {
|
||||
std::mem::transmute(index as u8)
|
||||
|
|
@ -60,16 +46,6 @@ impl Color {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return the third [Rank] for pieces of the given [Color], where its pawns move to after a
|
||||
/// one-square move on the start position.
|
||||
#[inline(always)]
|
||||
pub fn third_rank(self) -> Rank {
|
||||
match self {
|
||||
Self::White => Rank::Third,
|
||||
Self::Black => Rank::Sixth,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the fourth [Rank] for pieces of the given [Color], where its pawns move to after a
|
||||
/// two-square move.
|
||||
#[inline(always)]
|
||||
|
|
|
|||
|
|
@ -121,28 +121,14 @@ impl Direction {
|
|||
/// It does not make sense to use this method with knight-only directions, and it will panic in
|
||||
/// debug-mode if it happens.
|
||||
#[inline(always)]
|
||||
pub fn slide_board(self, board: Bitboard) -> Bitboard {
|
||||
self.slide_board_with_blockers(board, Bitboard::EMPTY)
|
||||
}
|
||||
|
||||
/// Slide a board along the given [Direction], i.e: return all successive applications of
|
||||
/// [Direction::move_board] until no new squares can be reached.
|
||||
/// Take into account the `blockers` [Bitboard]: a combination of all pieces on the board which
|
||||
/// cannot be slid over. The slide is over once a square that is part of `blockers` is reached.
|
||||
/// It does not make sense to use this method with knight-only directions, and it will panic in
|
||||
/// debug-mode if it happens.
|
||||
#[inline(always)]
|
||||
pub fn slide_board_with_blockers(self, mut board: Bitboard, blockers: Bitboard) -> Bitboard {
|
||||
pub fn slide_board(self, mut board: Bitboard) -> Bitboard {
|
||||
debug_assert!(!Self::KNIGHT_DIRECTIONS.contains(&self));
|
||||
|
||||
let mut res = Default::default();
|
||||
|
||||
while !board.is_empty() {
|
||||
board = self.move_board(board);
|
||||
res |= board;
|
||||
if !(board & blockers).is_empty() {
|
||||
break;
|
||||
}
|
||||
res = res | board;
|
||||
}
|
||||
|
||||
res
|
||||
|
|
@ -669,29 +655,4 @@ mod test {
|
|||
Bitboard::DIAGONAL - Square::A1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocked_slides() {
|
||||
assert_eq!(
|
||||
Direction::North
|
||||
.slide_board_with_blockers(Square::A1.into_bitboard(), Square::A2.into_bitboard()),
|
||||
Square::A2.into_bitboard()
|
||||
);
|
||||
assert_eq!(
|
||||
Direction::North
|
||||
.slide_board_with_blockers(Square::A1.into_bitboard(), Square::A3.into_bitboard()),
|
||||
Square::A2 | Square::A3
|
||||
);
|
||||
assert_eq!(
|
||||
Direction::North
|
||||
.slide_board_with_blockers(Square::A1.into_bitboard(), Square::A4.into_bitboard()),
|
||||
Square::A2 | Square::A3 | Square::A4
|
||||
);
|
||||
// Ensure that the starting square being in `blockers` is not an issue
|
||||
assert_eq!(
|
||||
Direction::North
|
||||
.slide_board_with_blockers(Square::A1.into_bitboard(), Square::A1.into_bitboard()),
|
||||
File::A.into_bitboard() - Square::A1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,38 +15,31 @@ pub enum File {
|
|||
}
|
||||
|
||||
impl File {
|
||||
/// The number of [File] variants.
|
||||
pub const NUM_VARIANTS: usize = 8;
|
||||
|
||||
const ALL: [Self; Self::NUM_VARIANTS] = [
|
||||
Self::A,
|
||||
Self::B,
|
||||
Self::C,
|
||||
Self::D,
|
||||
Self::E,
|
||||
Self::F,
|
||||
Self::G,
|
||||
Self::H,
|
||||
const ALL: [File; 8] = [
|
||||
File::A,
|
||||
File::B,
|
||||
File::C,
|
||||
File::D,
|
||||
File::E,
|
||||
File::F,
|
||||
File::G,
|
||||
File::H,
|
||||
];
|
||||
|
||||
/// Iterate over all files in order.
|
||||
pub fn iter() -> impl Iterator<Item = Self> {
|
||||
pub fn iter() -> impl Iterator<Item = File> {
|
||||
Self::ALL.iter().cloned()
|
||||
}
|
||||
|
||||
/// Convert from a file index into a [File] type.
|
||||
#[inline(always)]
|
||||
pub fn from_index(index: usize) -> Self {
|
||||
assert!(index < Self::NUM_VARIANTS);
|
||||
assert!(index < 8);
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Self::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Convert from a file index into a [File] type, no bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Should only be called with values that can be output by [File::index()].
|
||||
#[inline(always)]
|
||||
pub unsafe fn from_index_unchecked(index: usize) -> Self {
|
||||
std::mem::transmute(index as u8)
|
||||
|
|
|
|||
|
|
@ -4,9 +4,6 @@ pub use bitboard::*;
|
|||
pub mod castle_rights;
|
||||
pub use castle_rights::*;
|
||||
|
||||
pub mod chess_board;
|
||||
pub use chess_board::*;
|
||||
|
||||
pub mod color;
|
||||
pub use color::*;
|
||||
|
||||
|
|
@ -16,12 +13,6 @@ pub use direction::*;
|
|||
pub mod file;
|
||||
pub use file::*;
|
||||
|
||||
pub mod r#move;
|
||||
pub use r#move::*;
|
||||
|
||||
pub mod piece;
|
||||
pub use piece::*;
|
||||
|
||||
pub mod rank;
|
||||
pub use rank::*;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,232 +0,0 @@
|
|||
use super::{Piece, Square};
|
||||
|
||||
type Bitset = u32;
|
||||
|
||||
/// A chess move, containing:
|
||||
/// * Piece type.
|
||||
/// * Starting square.
|
||||
/// * Destination square.
|
||||
/// * Optional capture type.
|
||||
/// * Optional promotion type.
|
||||
/// * Optional captured type.
|
||||
/// * Whether the move was an en-passant capture.
|
||||
/// * Whether the move was a double-step.
|
||||
/// * Whether the move was a castling.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Move(Bitset);
|
||||
|
||||
/// A builder for [Move]. This is the prefered and only way of building a [Move].
|
||||
pub struct MoveBuilder {
|
||||
pub piece: Piece,
|
||||
pub start: Square,
|
||||
pub destination: Square,
|
||||
pub capture: Option<Piece>,
|
||||
pub promotion: Option<Piece>,
|
||||
pub en_passant: bool,
|
||||
pub double_step: bool,
|
||||
pub castling: bool,
|
||||
}
|
||||
|
||||
impl From<MoveBuilder> for Move {
|
||||
#[inline(always)]
|
||||
fn from(builder: MoveBuilder) -> Self {
|
||||
Self::new(
|
||||
builder.piece,
|
||||
builder.start,
|
||||
builder.destination,
|
||||
builder.capture,
|
||||
builder.promotion,
|
||||
builder.en_passant,
|
||||
builder.double_step,
|
||||
builder.castling,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Move] is structured as a bitset with the following fields:
|
||||
/// | Field | Size | Range of values | Note |
|
||||
/// |-------------|------|-----------------|-------------------------------------------------|
|
||||
/// | Piece | 3 | 0-6 | Can be interpreted as a [Piece] index |
|
||||
/// | Start | 6 | 0-63 | Can be interpreted as a [Square] index |
|
||||
/// | Destination | 6 | 0-63 | Can be interpreted as a [Square] index |
|
||||
/// | Capture | 3 | 0-7 | Can be interpreted as a [Piece] index if not 7 |
|
||||
/// | Promotion | 3 | 0-7 | Can be interpreted as a [Piece] index if not 7 |
|
||||
/// | En-pasant | 1 | 0-1 | Boolean value |
|
||||
/// | Double-step | 1 | 0-1 | Boolean value |
|
||||
/// | Castling | 1 | 0-1 | Boolean value |
|
||||
mod shift {
|
||||
use super::Bitset;
|
||||
|
||||
pub const PIECE: usize = 0;
|
||||
pub const PIECE_MASK: Bitset = 0b111;
|
||||
|
||||
pub const START: usize = 3;
|
||||
pub const START_MASK: Bitset = 0b11_1111;
|
||||
|
||||
pub const DESTINATION: usize = 9;
|
||||
pub const DESTINATION_MASK: Bitset = 0b11_1111;
|
||||
|
||||
pub const CAPTURE: usize = 15;
|
||||
pub const CAPTURE_MASK: Bitset = 0b111;
|
||||
|
||||
pub const PROMOTION: usize = 18;
|
||||
pub const PROMOTION_MASK: Bitset = 0b111;
|
||||
|
||||
pub const EN_PASSANT: usize = 21;
|
||||
pub const EN_PASSANT_MASK: Bitset = 0b1;
|
||||
|
||||
pub const DOUBLE_STEP: usize = 22;
|
||||
pub const DOUBLE_STEP_MASK: Bitset = 0b1;
|
||||
|
||||
pub const CASTLING: usize = 23;
|
||||
pub const CASTLING_MASK: Bitset = 0b1;
|
||||
}
|
||||
|
||||
impl Move {
|
||||
/// Construct a new move.
|
||||
#[inline(always)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
piece: Piece,
|
||||
start: Square,
|
||||
destination: Square,
|
||||
capture: Option<Piece>,
|
||||
promotion: Option<Piece>,
|
||||
en_passant: bool,
|
||||
double_step: bool,
|
||||
castling: bool,
|
||||
) -> Self {
|
||||
let mut value = 0;
|
||||
value |= (piece.index() as Bitset) << shift::PIECE;
|
||||
value |= (start.index() as Bitset) << shift::START;
|
||||
value |= (destination.index() as Bitset) << shift::DESTINATION;
|
||||
value |=
|
||||
(capture.map(Piece::index).unwrap_or(Piece::NUM_VARIANTS) as Bitset) << shift::CAPTURE;
|
||||
value |= (promotion.map(Piece::index).unwrap_or(Piece::NUM_VARIANTS) as Bitset)
|
||||
<< shift::PROMOTION;
|
||||
value |= (en_passant as Bitset) << shift::EN_PASSANT;
|
||||
value |= (double_step as Bitset) << shift::DOUBLE_STEP;
|
||||
value |= (castling as Bitset) << shift::CASTLING;
|
||||
Self(value)
|
||||
}
|
||||
|
||||
/// Get the [Piece] that is being moved.
|
||||
#[inline(always)]
|
||||
pub fn piece(self) -> Piece {
|
||||
let index = ((self.0 >> shift::PIECE) & shift::PIECE_MASK) as usize;
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Piece::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Get the [Square] that this move starts from.
|
||||
#[inline(always)]
|
||||
pub fn start(self) -> Square {
|
||||
let index = ((self.0 >> shift::START) & shift::START_MASK) as usize;
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Square::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Get the [Square] that this move ends on.
|
||||
#[inline(always)]
|
||||
pub fn destination(self) -> Square {
|
||||
let index = ((self.0 >> shift::DESTINATION) & shift::DESTINATION_MASK) as usize;
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Square::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Get the [Piece] that this move captures, or `None` if there are no captures.
|
||||
#[inline(always)]
|
||||
pub fn capture(self) -> Option<Piece> {
|
||||
let index = ((self.0 >> shift::CAPTURE) & shift::CAPTURE_MASK) as usize;
|
||||
if index < Piece::NUM_VARIANTS {
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Some(Piece::from_index_unchecked(index)) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the [Piece] that this move promotes to, or `None` if there are no promotions.
|
||||
#[inline(always)]
|
||||
pub fn promotion(self) -> Option<Piece> {
|
||||
let index = ((self.0 >> shift::PROMOTION) & shift::PROMOTION_MASK) as usize;
|
||||
if index < Piece::NUM_VARIANTS {
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Some(Piece::from_index_unchecked(index)) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the whether or not the move is an en-passant capture.
|
||||
#[inline(always)]
|
||||
pub fn is_en_passant(self) -> bool {
|
||||
let index = (self.0 >> shift::EN_PASSANT) & shift::EN_PASSANT_MASK;
|
||||
index != 0
|
||||
}
|
||||
|
||||
/// Get the whether or not the move is a pawn double step.
|
||||
#[inline(always)]
|
||||
pub fn is_double_step(self) -> bool {
|
||||
let index = (self.0 >> shift::DOUBLE_STEP) & shift::DOUBLE_STEP_MASK;
|
||||
index != 0
|
||||
}
|
||||
|
||||
/// Get the whether or not the move is a castling.
|
||||
#[inline(always)]
|
||||
pub fn is_castling(self) -> bool {
|
||||
let index = (self.0 >> shift::CASTLING) & shift::CASTLING_MASK;
|
||||
index != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn builder_simple() {
|
||||
let chess_move: Move = MoveBuilder {
|
||||
piece: Piece::Queen,
|
||||
start: Square::A2,
|
||||
destination: Square::A3,
|
||||
capture: None,
|
||||
promotion: None,
|
||||
en_passant: false,
|
||||
double_step: false,
|
||||
castling: false,
|
||||
}
|
||||
.into();
|
||||
assert_eq!(chess_move.piece(), Piece::Queen);
|
||||
assert_eq!(chess_move.start(), Square::A2);
|
||||
assert_eq!(chess_move.destination(), Square::A3);
|
||||
assert_eq!(chess_move.capture(), None);
|
||||
assert_eq!(chess_move.promotion(), None);
|
||||
assert!(!chess_move.is_en_passant());
|
||||
assert!(!chess_move.is_double_step());
|
||||
assert!(!chess_move.is_castling());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builder_all_fields() {
|
||||
let chess_move: Move = MoveBuilder {
|
||||
piece: Piece::Pawn,
|
||||
start: Square::A7,
|
||||
destination: Square::B8,
|
||||
capture: Some(Piece::Queen),
|
||||
promotion: Some(Piece::Knight),
|
||||
en_passant: true,
|
||||
double_step: true,
|
||||
castling: true,
|
||||
}
|
||||
.into();
|
||||
assert_eq!(chess_move.piece(), Piece::Pawn);
|
||||
assert_eq!(chess_move.start(), Square::A7);
|
||||
assert_eq!(chess_move.destination(), Square::B8);
|
||||
assert_eq!(chess_move.capture(), Some(Piece::Queen));
|
||||
assert_eq!(chess_move.promotion(), Some(Piece::Knight));
|
||||
assert!(chess_move.is_en_passant());
|
||||
assert!(chess_move.is_double_step());
|
||||
assert!(chess_move.is_castling());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
/// An enum representing the type of a piece.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Piece {
|
||||
King,
|
||||
Queen,
|
||||
Rook,
|
||||
Bishop,
|
||||
Knight,
|
||||
Pawn,
|
||||
}
|
||||
|
||||
impl Piece {
|
||||
/// The number of [Piece] variants.
|
||||
pub const NUM_VARIANTS: usize = 6;
|
||||
|
||||
const ALL: [Self; Self::NUM_VARIANTS] = [
|
||||
Self::King,
|
||||
Self::Queen,
|
||||
Self::Rook,
|
||||
Self::Bishop,
|
||||
Self::Knight,
|
||||
Self::Pawn,
|
||||
];
|
||||
|
||||
/// Iterate over all piece types.
|
||||
pub fn iter() -> impl Iterator<Item = Self> {
|
||||
Self::ALL.iter().cloned()
|
||||
}
|
||||
|
||||
/// Convert from a piece index into a [Piece] type.
|
||||
#[inline(always)]
|
||||
pub fn from_index(index: usize) -> Self {
|
||||
assert!(index < Self::NUM_VARIANTS);
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Self::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Convert from a piece index into a [Piece] type, no bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Should only be called with values that can be output by [Piece::index()].
|
||||
#[inline(always)]
|
||||
pub unsafe fn from_index_unchecked(index: usize) -> Self {
|
||||
std::mem::transmute(index as u8)
|
||||
}
|
||||
|
||||
/// Return the index of a given [Piece].
|
||||
#[inline(always)]
|
||||
pub fn index(self) -> usize {
|
||||
self as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn from_index() {
|
||||
assert_eq!(Piece::from_index(0), Piece::King);
|
||||
assert_eq!(Piece::from_index(1), Piece::Queen);
|
||||
assert_eq!(Piece::from_index(5), Piece::Pawn);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index() {
|
||||
assert_eq!(Piece::King.index(), 0);
|
||||
assert_eq!(Piece::Queen.index(), 1);
|
||||
assert_eq!(Piece::Pawn.index(), 5);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,38 +15,31 @@ pub enum Rank {
|
|||
}
|
||||
|
||||
impl Rank {
|
||||
/// The number of [Rank] variants.
|
||||
pub const NUM_VARIANTS: usize = 8;
|
||||
|
||||
const ALL: [Self; Self::NUM_VARIANTS] = [
|
||||
Self::First,
|
||||
Self::Second,
|
||||
Self::Third,
|
||||
Self::Fourth,
|
||||
Self::Fifth,
|
||||
Self::Sixth,
|
||||
Self::Seventh,
|
||||
Self::Eighth,
|
||||
const ALL: [Rank; 8] = [
|
||||
Rank::First,
|
||||
Rank::Second,
|
||||
Rank::Third,
|
||||
Rank::Fourth,
|
||||
Rank::Fifth,
|
||||
Rank::Sixth,
|
||||
Rank::Seventh,
|
||||
Rank::Eighth,
|
||||
];
|
||||
|
||||
/// Iterate over all ranks in order.
|
||||
pub fn iter() -> impl Iterator<Item = Self> {
|
||||
pub fn iter() -> impl Iterator<Item = Rank> {
|
||||
Self::ALL.iter().cloned()
|
||||
}
|
||||
|
||||
/// Convert from a rank index into a [Rank] type.
|
||||
#[inline(always)]
|
||||
pub fn from_index(index: usize) -> Self {
|
||||
assert!(index < Self::NUM_VARIANTS);
|
||||
assert!(index < 8);
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Self::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Convert from a rank index into a [Rank] type, no bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Should only be called with values that can be output by [Rank::index()].
|
||||
#[inline(always)]
|
||||
pub unsafe fn from_index_unchecked(index: usize) -> Self {
|
||||
std::mem::transmute(index as u8)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use super::{Bitboard, File, Rank};
|
|||
use crate::utils::static_assert;
|
||||
|
||||
/// Represent a square on a chessboard. Defined in the same order as the
|
||||
/// [Bitboard] squares.
|
||||
/// [Bitboard](crate::board::Bitboard) squares.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[rustfmt::skip]
|
||||
pub enum Square {
|
||||
|
|
@ -18,16 +18,13 @@ pub enum Square {
|
|||
|
||||
impl std::fmt::Display for Square {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
write!(f, "{}", format!("{:?}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Square {
|
||||
/// The number of [Square] variants.
|
||||
pub const NUM_VARIANTS: usize = 64;
|
||||
|
||||
#[rustfmt::skip]
|
||||
const ALL: [Self; Self::NUM_VARIANTS] = [
|
||||
const ALL: [Self; 64] = [
|
||||
Self::A1, Self::A2, Self::A3, Self::A4, Self::A5, Self::A6, Self::A7, Self::A8,
|
||||
Self::B1, Self::B2, Self::B3, Self::B4, Self::B5, Self::B6, Self::B7, Self::B8,
|
||||
Self::C1, Self::C2, Self::C3, Self::C4, Self::C5, Self::C6, Self::C7, Self::C8,
|
||||
|
|
@ -46,23 +43,19 @@ impl Square {
|
|||
}
|
||||
|
||||
/// Iterate over all squares in order.
|
||||
pub fn iter() -> impl Iterator<Item = Self> {
|
||||
pub fn iter() -> impl Iterator<Item = Square> {
|
||||
Self::ALL.iter().cloned()
|
||||
}
|
||||
|
||||
/// Convert from a square index into a [Square] type.
|
||||
#[inline(always)]
|
||||
pub fn from_index(index: usize) -> Self {
|
||||
assert!(index < Self::NUM_VARIANTS);
|
||||
assert!(index < 64);
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Self::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Convert from a square index into a [Square] type, no bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Should only be called with values that can be output by [Square::index()].
|
||||
#[inline(always)]
|
||||
pub unsafe fn from_index_unchecked(index: usize) -> Self {
|
||||
std::mem::transmute(index as u8)
|
||||
|
|
@ -113,7 +106,6 @@ impl std::ops::Shl<usize> for Square {
|
|||
|
||||
#[inline(always)]
|
||||
fn shl(self, rhs: usize) -> Self::Output {
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
Square::from_index(self as usize + rhs)
|
||||
}
|
||||
}
|
||||
|
|
@ -124,7 +116,6 @@ impl std::ops::Shr<usize> for Square {
|
|||
|
||||
#[inline(always)]
|
||||
fn shr(self, rhs: usize) -> Self::Output {
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
Square::from_index(self as usize - rhs)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
283
src/fen.rs
283
src/fen.rs
|
|
@ -1,283 +0,0 @@
|
|||
use crate::board::{
|
||||
CastleRights, ChessBoard, ChessBoardBuilder, Color, File, InvalidError, Piece, Rank, Square,
|
||||
};
|
||||
|
||||
/// A trait to mark items that can be converted from a FEN input.
|
||||
pub trait FromFen: Sized {
|
||||
type Err;
|
||||
|
||||
fn from_fen(s: &str) -> Result<Self, Self::Err>;
|
||||
}
|
||||
|
||||
/// A singular type for all errors that could happen during FEN parsing.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum FenError {
|
||||
/// Invalid FEN input.
|
||||
InvalidFen,
|
||||
/// Invalid chess position.
|
||||
InvalidPosition(InvalidError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FenError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidFen => write!(f, "Invalid FEN input"),
|
||||
Self::InvalidPosition(err) => write!(f, "Invalid chess position: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for FenError {}
|
||||
|
||||
/// Allow converting a [InvalidError] into [FenError], for use with the '?' operator.
|
||||
impl From<InvalidError> for FenError {
|
||||
fn from(err: InvalidError) -> Self {
|
||||
Self::InvalidPosition(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the castling rights segment of a FEN string to an array of [CastleRights].
|
||||
impl FromFen for [CastleRights; 2] {
|
||||
type Err = FenError;
|
||||
|
||||
fn from_fen(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() > 4 {
|
||||
return Err(FenError::InvalidFen);
|
||||
}
|
||||
|
||||
let mut res = [CastleRights::NoSide; 2];
|
||||
|
||||
if s == "-" {
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
for b in s.chars() {
|
||||
let color = if b.is_uppercase() {
|
||||
Color::White
|
||||
} else {
|
||||
Color::Black
|
||||
};
|
||||
let rights = &mut res[color.index()];
|
||||
match b {
|
||||
'k' | 'K' => *rights = rights.with_king_side(),
|
||||
'q' | 'Q' => *rights = rights.with_queen_side(),
|
||||
_ => return Err(FenError::InvalidFen),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a side to move segment of a FEN string to a [Color].
|
||||
impl FromFen for Color {
|
||||
type Err = FenError;
|
||||
|
||||
fn from_fen(s: &str) -> Result<Self, Self::Err> {
|
||||
let res = match s {
|
||||
"w" => Color::White,
|
||||
"b" => Color::Black,
|
||||
_ => return Err(FenError::InvalidFen),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an en-passant target square segment of a FEN string to an optional [Square].
|
||||
impl FromFen for Option<Square> {
|
||||
type Err = FenError;
|
||||
|
||||
fn from_fen(s: &str) -> Result<Self, Self::Err> {
|
||||
let res = match s.as_bytes() {
|
||||
[b'-'] => None,
|
||||
[file @ b'a'..=b'h', rank @ b'1'..=b'8'] => Some(Square::new(
|
||||
File::from_index((file - b'a') as usize),
|
||||
Rank::from_index((rank - b'1') as usize),
|
||||
)),
|
||||
_ => return Err(FenError::InvalidFen),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a piece in FEN notation to a [Piece].
|
||||
impl FromFen for Piece {
|
||||
type Err = FenError;
|
||||
|
||||
fn from_fen(s: &str) -> Result<Self, Self::Err> {
|
||||
let res = match s {
|
||||
"p" | "P" => Self::Pawn,
|
||||
"n" | "N" => Self::Knight,
|
||||
"b" | "B" => Self::Bishop,
|
||||
"r" | "R" => Self::Rook,
|
||||
"q" | "Q" => Self::Queen,
|
||||
"k" | "K" => Self::King,
|
||||
_ => return Err(FenError::InvalidFen),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a [ChessBoard] from the given FEN string.
|
||||
impl FromFen for ChessBoard {
|
||||
type Err = FenError;
|
||||
|
||||
fn from_fen(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut split = s.split_ascii_whitespace();
|
||||
|
||||
let piece_placement = split.next().ok_or(FenError::InvalidFen)?;
|
||||
let side_to_move = split.next().ok_or(FenError::InvalidFen)?;
|
||||
let castling_rights = split.next().ok_or(FenError::InvalidFen)?;
|
||||
let en_passant_square = split.next().ok_or(FenError::InvalidFen)?;
|
||||
let half_move_clock = split.next().ok_or(FenError::InvalidFen)?;
|
||||
let full_move_counter = split.next().ok_or(FenError::InvalidFen)?;
|
||||
|
||||
let mut builder = ChessBoardBuilder::new();
|
||||
|
||||
let castle_rights = <[CastleRights; 2]>::from_fen(castling_rights)?;
|
||||
for color in Color::iter() {
|
||||
builder.with_castle_rights(castle_rights[color.index()], color);
|
||||
}
|
||||
|
||||
let side = Color::from_fen(side_to_move)?;
|
||||
builder.with_current_player(side);
|
||||
|
||||
if let Some(square) = Option::<Square>::from_fen(en_passant_square)? {
|
||||
builder.with_en_passant(square);
|
||||
};
|
||||
|
||||
let half_move_clock = half_move_clock
|
||||
.parse::<u8>()
|
||||
.map_err(|_| FenError::InvalidFen)?;
|
||||
builder.with_half_move_clock(half_move_clock);
|
||||
|
||||
let full_move_counter = full_move_counter
|
||||
.parse::<u32>()
|
||||
.map_err(|_| FenError::InvalidFen)?;
|
||||
builder.with_total_plies(
|
||||
(full_move_counter - 1) * 2 + if side == Color::White { 0 } else { 1 },
|
||||
);
|
||||
|
||||
{
|
||||
let mut rank: usize = 8;
|
||||
for rank_str in piece_placement.split('/') {
|
||||
rank -= 1;
|
||||
let mut file: usize = 0;
|
||||
for c in rank_str.chars() {
|
||||
let color = if c.is_uppercase() {
|
||||
Color::White
|
||||
} else {
|
||||
Color::Black
|
||||
};
|
||||
let piece = match c {
|
||||
digit @ '1'..='8' => {
|
||||
// Unwrap is fine since this arm is only matched by digits
|
||||
file += digit.to_digit(10).unwrap() as usize;
|
||||
continue;
|
||||
}
|
||||
_ => Piece::from_fen(&c.to_string())?,
|
||||
};
|
||||
|
||||
// Only need to worry about underflow since those are `usize` values.
|
||||
if file >= 8 || rank >= 8 {
|
||||
return Err(FenError::InvalidFen);
|
||||
};
|
||||
|
||||
let square = Square::new(File::from_index(file), Rank::from_index(rank));
|
||||
|
||||
builder[square] = Some((piece, color));
|
||||
file += 1;
|
||||
}
|
||||
// We haven't read exactly 8 files.
|
||||
if file != 8 {
|
||||
return Err(FenError::InvalidFen);
|
||||
}
|
||||
}
|
||||
// We haven't read exactly 8 ranks
|
||||
if rank != 0 {
|
||||
return Err(FenError::InvalidFen);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(builder.try_into()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::board::MoveBuilder;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_position() {
|
||||
let default_position = ChessBoard::default();
|
||||
assert_eq!(
|
||||
ChessBoard::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
.unwrap(),
|
||||
default_position
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn en_passant() {
|
||||
// Start from default position
|
||||
let mut position = ChessBoard::default();
|
||||
position.do_move(
|
||||
MoveBuilder {
|
||||
piece: Piece::Pawn,
|
||||
start: Square::E2,
|
||||
destination: Square::E4,
|
||||
capture: None,
|
||||
promotion: None,
|
||||
en_passant: false,
|
||||
double_step: true,
|
||||
castling: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
ChessBoard::from_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1")
|
||||
.unwrap(),
|
||||
position
|
||||
);
|
||||
// And now c5
|
||||
position.do_move(
|
||||
MoveBuilder {
|
||||
piece: Piece::Pawn,
|
||||
start: Square::C7,
|
||||
destination: Square::C5,
|
||||
capture: None,
|
||||
promotion: None,
|
||||
en_passant: false,
|
||||
double_step: true,
|
||||
castling: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
ChessBoard::from_fen("rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2")
|
||||
.unwrap(),
|
||||
position
|
||||
);
|
||||
// Finally, Nf3
|
||||
position.do_move(
|
||||
MoveBuilder {
|
||||
piece: Piece::Knight,
|
||||
start: Square::G1,
|
||||
destination: Square::F3,
|
||||
capture: None,
|
||||
promotion: None,
|
||||
en_passant: false,
|
||||
double_step: false,
|
||||
castling: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
ChessBoard::from_fen("rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2 ")
|
||||
.unwrap(),
|
||||
position
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,2 @@
|
|||
pub mod board;
|
||||
pub mod fen;
|
||||
pub mod movegen;
|
||||
pub mod utils;
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
// Naive move generation
|
||||
mod naive;
|
||||
|
||||
// Magic bitboard generation
|
||||
mod wizardry;
|
||||
|
||||
// Magic bitboard definitions
|
||||
mod moves;
|
||||
pub use moves::*;
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
use std::sync::OnceLock;
|
||||
|
||||
use crate::{
|
||||
board::{Bitboard, Color, File, Square},
|
||||
movegen::{
|
||||
naive,
|
||||
wizardry::{
|
||||
generate_bishop_magics, generate_rook_magics, MagicMoves, RandGen, BISHOP_SEED,
|
||||
ROOK_SEED,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// A pre-rolled RNG for magic bitboard generation, using pre-determined values.
|
||||
struct PreRolledRng {
|
||||
numbers: [u64; 64],
|
||||
current_index: usize,
|
||||
}
|
||||
|
||||
impl PreRolledRng {
|
||||
pub fn new(numbers: [u64; 64]) -> Self {
|
||||
Self {
|
||||
numbers,
|
||||
current_index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RandGen for PreRolledRng {
|
||||
fn gen(&mut self) -> u64 {
|
||||
// We roll 3 numbers per square to bitwise-and them together.
|
||||
// Just return the same one 3 times as a work-around.
|
||||
let res = self.numbers[self.current_index / 3];
|
||||
self.current_index += 1;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the set of possible non-attack moves for a pawn on a [Square], given its [Color] and
|
||||
/// set of blockers.
|
||||
pub fn pawn_quiet_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard {
|
||||
static PAWN_MOVES: OnceLock<[[Bitboard; 64]; 2]> = OnceLock::new();
|
||||
|
||||
// If there is a piece in front of the pawn, it can't advance
|
||||
if !(color.backward_direction().move_board(blockers) & square).is_empty() {
|
||||
return Bitboard::EMPTY;
|
||||
}
|
||||
|
||||
PAWN_MOVES.get_or_init(|| {
|
||||
let mut res = [[Bitboard::EMPTY; 64]; 2];
|
||||
for color in Color::iter() {
|
||||
for square in Square::iter() {
|
||||
res[color.index()][square.index()] =
|
||||
naive::pawn_moves(color, square, Bitboard::EMPTY);
|
||||
}
|
||||
}
|
||||
res
|
||||
})[color.index()][square.index()]
|
||||
}
|
||||
|
||||
/// Compute the set of possible attacks for a pawn on a [Square], given its [Color].
|
||||
pub fn pawn_attacks(color: Color, square: Square) -> Bitboard {
|
||||
static PAWN_ATTACKS: OnceLock<[[Bitboard; 64]; 2]> = OnceLock::new();
|
||||
|
||||
PAWN_ATTACKS.get_or_init(|| {
|
||||
let mut res = [[Bitboard::EMPTY; 64]; 2];
|
||||
for color in Color::iter() {
|
||||
for square in Square::iter() {
|
||||
res[color.index()][square.index()] = naive::pawn_captures(color, square);
|
||||
}
|
||||
}
|
||||
res
|
||||
})[color.index()][square.index()]
|
||||
}
|
||||
|
||||
/// Compute the set of possible moves for a pawn on a [Square], given its [Color] and set of
|
||||
/// blockers.
|
||||
pub fn pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard {
|
||||
pawn_quiet_moves(color, square, blockers) | pawn_attacks(color, square)
|
||||
}
|
||||
|
||||
/// Compute the set of possible moves for a knight on a [Square].
|
||||
pub fn knight_moves(square: Square) -> Bitboard {
|
||||
static KNIGHT_MOVES: OnceLock<[Bitboard; 64]> = OnceLock::new();
|
||||
KNIGHT_MOVES.get_or_init(|| {
|
||||
let mut res = [Bitboard::EMPTY; 64];
|
||||
for square in Square::iter() {
|
||||
res[square.index()] = naive::knight_moves(square)
|
||||
}
|
||||
res
|
||||
})[square.index()]
|
||||
}
|
||||
|
||||
/// Compute the set of possible moves for a bishop on a [Square], given its set of blockers.
|
||||
pub fn bishop_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
static BISHOP_MAGICS: OnceLock<MagicMoves> = OnceLock::new();
|
||||
BISHOP_MAGICS
|
||||
.get_or_init(|| {
|
||||
let (magics, moves) = generate_bishop_magics(&mut PreRolledRng::new(BISHOP_SEED));
|
||||
// SAFETY: we used the generator function to compute these values
|
||||
unsafe { MagicMoves::new(magics, moves) }
|
||||
})
|
||||
.query(square, blockers)
|
||||
}
|
||||
|
||||
/// Compute the set of possible moves for a rook on a [Square], given its set of blockers.
|
||||
pub fn rook_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
static ROOK_MAGICS: OnceLock<MagicMoves> = OnceLock::new();
|
||||
ROOK_MAGICS
|
||||
.get_or_init(|| {
|
||||
let (magics, moves) = generate_rook_magics(&mut PreRolledRng::new(ROOK_SEED));
|
||||
// SAFETY: we used the generator function to compute these values
|
||||
unsafe { MagicMoves::new(magics, moves) }
|
||||
})
|
||||
.query(square, blockers)
|
||||
}
|
||||
|
||||
/// Compute the set of possible moves for a queen on a [Square], given its set of blockers.
|
||||
pub fn queen_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
bishop_moves(square, blockers) | rook_moves(square, blockers)
|
||||
}
|
||||
|
||||
/// Compute the set of possible moves for a king on a [Square].
|
||||
pub fn king_moves(square: Square) -> Bitboard {
|
||||
static KING_MOVES: OnceLock<[Bitboard; 64]> = OnceLock::new();
|
||||
KING_MOVES.get_or_init(|| {
|
||||
let mut res = [Bitboard::EMPTY; 64];
|
||||
for square in Square::iter() {
|
||||
res[square.index()] = naive::king_moves(square)
|
||||
}
|
||||
res
|
||||
})[square.index()]
|
||||
}
|
||||
|
||||
/// Compute the squares which should be empty for a king-side castle of the given [Color].
|
||||
pub fn kind_side_castle_blockers(color: Color) -> Bitboard {
|
||||
let rank = color.first_rank();
|
||||
Square::new(File::F, rank) | Square::new(File::G, rank)
|
||||
}
|
||||
|
||||
/// Compute the squares which should be empty for a queen-side castle of the given [Color].
|
||||
pub fn queen_side_castle_blockers(color: Color) -> Bitboard {
|
||||
let rank = color.first_rank();
|
||||
Square::new(File::B, rank) | Square::new(File::C, rank) | Square::new(File::D, rank)
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
use crate::board::{Bitboard, Direction, Square};
|
||||
|
||||
/// Compute a bishop's movement given a set of blockers that cannot be moved past.
|
||||
pub fn bishop_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
Direction::iter_bishop()
|
||||
.map(|dir| dir.slide_board_with_blockers(square.into_bitboard(), blockers))
|
||||
.fold(Bitboard::EMPTY, |lhs, rhs| lhs | rhs)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::board::{File, Rank};
|
||||
|
||||
#[test]
|
||||
fn moves_lower_left_square() {
|
||||
assert_eq!(
|
||||
bishop_moves(Square::A1, Bitboard::EMPTY),
|
||||
Bitboard::DIAGONAL - Square::A1
|
||||
);
|
||||
assert_eq!(
|
||||
bishop_moves(Square::A1, Bitboard::ALL),
|
||||
Square::B2.into_bitboard()
|
||||
);
|
||||
assert_eq!(
|
||||
bishop_moves(Square::A1, Square::D4.into_bitboard()),
|
||||
Square::B2 | Square::C3 | Square::D4
|
||||
);
|
||||
assert_eq!(
|
||||
bishop_moves(Square::A1, File::D.into_bitboard()),
|
||||
Square::B2 | Square::C3 | Square::D4
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moves_middle() {
|
||||
let cross = Bitboard::DIAGONAL | Direction::South.move_board(Bitboard::ANTI_DIAGONAL);
|
||||
assert_eq!(
|
||||
bishop_moves(Square::D4, Bitboard::EMPTY),
|
||||
cross - Square::D4
|
||||
);
|
||||
assert_eq!(
|
||||
bishop_moves(Square::D4, Bitboard::ALL),
|
||||
Square::C3 | Square::C5 | Square::E3 | Square::E5
|
||||
);
|
||||
assert_eq!(
|
||||
bishop_moves(Square::D4, Rank::Fifth.into_bitboard()),
|
||||
Square::A1
|
||||
| Square::B2
|
||||
| Square::C3
|
||||
| Square::C5
|
||||
| Square::E3
|
||||
| Square::E5
|
||||
| Square::F2
|
||||
| Square::G1
|
||||
);
|
||||
assert_eq!(
|
||||
bishop_moves(Square::D4, File::E.into_bitboard()),
|
||||
Square::A1
|
||||
| Square::A7
|
||||
| Square::B2
|
||||
| Square::B6
|
||||
| Square::C3
|
||||
| Square::C5
|
||||
| Square::E3
|
||||
| Square::E5
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
use crate::board::{Bitboard, Direction, Square};
|
||||
|
||||
/// Compute a king's movement. No castling moves included
|
||||
pub fn king_moves(square: Square) -> Bitboard {
|
||||
let board = square.into_bitboard();
|
||||
|
||||
Direction::iter_royalty()
|
||||
.map(|dir| dir.move_board(board))
|
||||
.fold(Bitboard::EMPTY, |lhs, rhs| lhs | rhs)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn moves_first_rank() {
|
||||
assert_eq!(king_moves(Square::A1), Square::A2 | Square::B1 | Square::B2);
|
||||
assert_eq!(
|
||||
king_moves(Square::B1),
|
||||
Square::A1 | Square::A2 | Square::B2 | Square::C1 | Square::C2
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::C1),
|
||||
Square::B1 | Square::B2 | Square::C2 | Square::D1 | Square::D2
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::D1),
|
||||
Square::C1 | Square::C2 | Square::D2 | Square::E1 | Square::E2
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::E1),
|
||||
Square::D1 | Square::D2 | Square::E2 | Square::F1 | Square::F2
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::F1),
|
||||
Square::E1 | Square::E2 | Square::F2 | Square::G1 | Square::G2
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::G1),
|
||||
Square::F1 | Square::F2 | Square::G2 | Square::H1 | Square::H2
|
||||
);
|
||||
assert_eq!(king_moves(Square::H1), Square::G1 | Square::G2 | Square::H2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moves_last_rank() {
|
||||
assert_eq!(king_moves(Square::A8), Square::A7 | Square::B8 | Square::B7);
|
||||
assert_eq!(
|
||||
king_moves(Square::B8),
|
||||
Square::A8 | Square::A7 | Square::B7 | Square::C8 | Square::C7
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::C8),
|
||||
Square::B8 | Square::B7 | Square::C7 | Square::D8 | Square::D7
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::D8),
|
||||
Square::C8 | Square::C7 | Square::D7 | Square::E8 | Square::E7
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::E8),
|
||||
Square::D8 | Square::D7 | Square::E7 | Square::F8 | Square::F7
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::F8),
|
||||
Square::E8 | Square::E7 | Square::F7 | Square::G8 | Square::G7
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::G8),
|
||||
Square::F8 | Square::F7 | Square::G7 | Square::H8 | Square::H7
|
||||
);
|
||||
assert_eq!(king_moves(Square::H8), Square::G8 | Square::G7 | Square::H7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moves_first_file() {
|
||||
assert_eq!(king_moves(Square::A1), Square::A2 | Square::B1 | Square::B2);
|
||||
assert_eq!(
|
||||
king_moves(Square::A2),
|
||||
Square::A1 | Square::A3 | Square::B1 | Square::B2 | Square::B3
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::A3),
|
||||
Square::A2 | Square::A4 | Square::B2 | Square::B3 | Square::B4
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::A4),
|
||||
Square::A3 | Square::A5 | Square::B3 | Square::B4 | Square::B5
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::A5),
|
||||
Square::A4 | Square::A6 | Square::B4 | Square::B5 | Square::B6
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::A6),
|
||||
Square::A5 | Square::A7 | Square::B5 | Square::B6 | Square::B7
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::A7),
|
||||
Square::A6 | Square::A8 | Square::B6 | Square::B7 | Square::B8
|
||||
);
|
||||
assert_eq!(king_moves(Square::A8), Square::A7 | Square::B7 | Square::B8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moves_last_file() {
|
||||
assert_eq!(king_moves(Square::H1), Square::H2 | Square::G1 | Square::G2);
|
||||
assert_eq!(
|
||||
king_moves(Square::H2),
|
||||
Square::H1 | Square::H3 | Square::G1 | Square::G2 | Square::G3
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::H3),
|
||||
Square::H2 | Square::H4 | Square::G2 | Square::G3 | Square::G4
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::H4),
|
||||
Square::H3 | Square::H5 | Square::G3 | Square::G4 | Square::G5
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::H5),
|
||||
Square::H4 | Square::H6 | Square::G4 | Square::G5 | Square::G6
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::H6),
|
||||
Square::H5 | Square::H7 | Square::G5 | Square::G6 | Square::G7
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::H7),
|
||||
Square::H6 | Square::H8 | Square::G6 | Square::G7 | Square::G8
|
||||
);
|
||||
assert_eq!(king_moves(Square::H8), Square::H7 | Square::G7 | Square::G8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moves_middle() {
|
||||
assert_eq!(
|
||||
king_moves(Square::D4),
|
||||
Square::C3
|
||||
| Square::C4
|
||||
| Square::C5
|
||||
| Square::D3
|
||||
| Square::D5
|
||||
| Square::E3
|
||||
| Square::E4
|
||||
| Square::E5
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::D5),
|
||||
Square::C4
|
||||
| Square::C5
|
||||
| Square::C6
|
||||
| Square::D4
|
||||
| Square::D6
|
||||
| Square::E4
|
||||
| Square::E5
|
||||
| Square::E6
|
||||
);
|
||||
assert_eq!(
|
||||
king_moves(Square::E5),
|
||||
Square::D4
|
||||
| Square::D5
|
||||
| Square::D6
|
||||
| Square::E4
|
||||
| Square::E6
|
||||
| Square::F4
|
||||
| Square::F5
|
||||
| Square::F6
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
use crate::board::{Bitboard, Direction, Square};
|
||||
|
||||
/// Compute a knight's movement.
|
||||
pub fn knight_moves(square: Square) -> Bitboard {
|
||||
let board = square.into_bitboard();
|
||||
|
||||
Direction::iter_knight()
|
||||
.map(|dir| dir.move_board(board))
|
||||
.fold(Bitboard::EMPTY, |lhs, rhs| lhs | rhs)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn moves_first_rank() {
|
||||
assert_eq!(knight_moves(Square::A1), Square::B3 | Square::C2);
|
||||
assert_eq!(
|
||||
knight_moves(Square::B1),
|
||||
Square::A3 | Square::C3 | Square::D2
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::C1),
|
||||
Square::A2 | Square::B3 | Square::D3 | Square::E2
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::D1),
|
||||
Square::B2 | Square::C3 | Square::E3 | Square::F2
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::E1),
|
||||
Square::C2 | Square::D3 | Square::F3 | Square::G2
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::F1),
|
||||
Square::D2 | Square::E3 | Square::G3 | Square::H2
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::G1),
|
||||
Square::E2 | Square::F3 | Square::H3
|
||||
);
|
||||
assert_eq!(knight_moves(Square::H1), Square::F2 | Square::G3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moves_last_rank() {
|
||||
assert_eq!(knight_moves(Square::A8), Square::B6 | Square::C7);
|
||||
assert_eq!(
|
||||
knight_moves(Square::B8),
|
||||
Square::A6 | Square::C6 | Square::D7
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::C8),
|
||||
Square::A7 | Square::B6 | Square::D6 | Square::E7
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::D8),
|
||||
Square::B7 | Square::C6 | Square::E6 | Square::F7
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::E8),
|
||||
Square::C7 | Square::D6 | Square::F6 | Square::G7
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::F8),
|
||||
Square::D7 | Square::E6 | Square::G6 | Square::H7
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::G8),
|
||||
Square::E7 | Square::F6 | Square::H6
|
||||
);
|
||||
assert_eq!(knight_moves(Square::H8), Square::F7 | Square::G6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moves_first_file() {
|
||||
assert_eq!(knight_moves(Square::A1), Square::B3 | Square::C2);
|
||||
assert_eq!(
|
||||
knight_moves(Square::A2),
|
||||
Square::B4 | Square::C1 | Square::C3
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::A3),
|
||||
Square::B1 | Square::B5 | Square::C2 | Square::C4
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::A4),
|
||||
Square::B2 | Square::B6 | Square::C3 | Square::C5
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::A5),
|
||||
Square::B3 | Square::B7 | Square::C4 | Square::C6
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::A6),
|
||||
Square::B4 | Square::B8 | Square::C5 | Square::C7
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::A7),
|
||||
Square::B5 | Square::C6 | Square::C8
|
||||
);
|
||||
assert_eq!(knight_moves(Square::A8), Square::B6 | Square::C7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moves_last_file() {
|
||||
assert_eq!(knight_moves(Square::H1), Square::G3 | Square::F2);
|
||||
assert_eq!(
|
||||
knight_moves(Square::H2),
|
||||
Square::G4 | Square::F1 | Square::F3
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::H3),
|
||||
Square::G1 | Square::G5 | Square::F2 | Square::F4
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::H4),
|
||||
Square::G2 | Square::G6 | Square::F3 | Square::F5
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::H5),
|
||||
Square::G3 | Square::G7 | Square::F4 | Square::F6
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::H6),
|
||||
Square::G4 | Square::G8 | Square::F5 | Square::F7
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::H7),
|
||||
Square::G5 | Square::F6 | Square::F8
|
||||
);
|
||||
assert_eq!(knight_moves(Square::H8), Square::G6 | Square::F7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moves_middle() {
|
||||
assert_eq!(
|
||||
knight_moves(Square::D4),
|
||||
Square::B3
|
||||
| Square::B5
|
||||
| Square::C2
|
||||
| Square::C6
|
||||
| Square::E2
|
||||
| Square::E6
|
||||
| Square::F3
|
||||
| Square::F5
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::D5),
|
||||
Square::B4
|
||||
| Square::B6
|
||||
| Square::C3
|
||||
| Square::C7
|
||||
| Square::E3
|
||||
| Square::E7
|
||||
| Square::F4
|
||||
| Square::F6
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::E4),
|
||||
Square::C3
|
||||
| Square::C5
|
||||
| Square::D2
|
||||
| Square::D6
|
||||
| Square::F2
|
||||
| Square::F6
|
||||
| Square::G3
|
||||
| Square::G5
|
||||
);
|
||||
assert_eq!(
|
||||
knight_moves(Square::E5),
|
||||
Square::C4
|
||||
| Square::C6
|
||||
| Square::D3
|
||||
| Square::D7
|
||||
| Square::F3
|
||||
| Square::F7
|
||||
| Square::G4
|
||||
| Square::G6
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
pub mod bishop;
|
||||
pub use bishop::*;
|
||||
|
||||
pub mod king;
|
||||
pub use king::*;
|
||||
|
||||
pub mod knight;
|
||||
pub use knight::*;
|
||||
|
||||
pub mod pawn;
|
||||
pub use pawn::*;
|
||||
|
||||
pub mod rook;
|
||||
pub use rook::*;
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
use crate::board::{Bitboard, Color, Direction, Rank, Square};
|
||||
|
||||
/// Compute a pawn's movement given its color, and a set of blockers that cannot be moved past.
|
||||
pub fn pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard {
|
||||
if (square.rank() == Rank::First) || (square.rank() == Rank::Eighth) {
|
||||
return Bitboard::EMPTY;
|
||||
}
|
||||
|
||||
let dir = color.forward_direction();
|
||||
|
||||
let first_push = dir.move_board(square.into_bitboard());
|
||||
let second_push = if square.rank() == color.second_rank() {
|
||||
Square::new(square.file(), color.fourth_rank()).into_bitboard()
|
||||
} else {
|
||||
Bitboard::EMPTY
|
||||
};
|
||||
|
||||
if (first_push & blockers).is_empty() {
|
||||
first_push | second_push
|
||||
} else {
|
||||
Bitboard::EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the set of squares a pawn can capture, given its color.
|
||||
pub fn pawn_captures(color: Color, square: Square) -> Bitboard {
|
||||
if (square.rank() == Rank::First) || (square.rank() == Rank::Eighth) {
|
||||
return Bitboard::EMPTY;
|
||||
}
|
||||
|
||||
let dir = color.forward_direction();
|
||||
|
||||
let advanced = dir.move_board(square.into_bitboard());
|
||||
|
||||
let attack_west = Direction::West.move_board(advanced);
|
||||
let attack_east = Direction::East.move_board(advanced);
|
||||
|
||||
attack_west | attack_east
|
||||
}
|
||||
|
||||
/// Computes the set of squares that can capture this one *en-passant*.
|
||||
#[allow(unused)]
|
||||
pub fn en_passant_origins(square: Square) -> Bitboard {
|
||||
let board = square.into_bitboard();
|
||||
|
||||
let origin_west = Direction::West.move_board(board);
|
||||
let origin_east = Direction::East.move_board(board);
|
||||
|
||||
origin_west | origin_east
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn moves() {
|
||||
assert_eq!(
|
||||
pawn_moves(Color::White, Square::A2, Bitboard::EMPTY),
|
||||
Square::A3 | Square::A4
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_moves(Color::Black, Square::A7, Bitboard::EMPTY),
|
||||
Square::A5 | Square::A6
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_moves(Color::Black, Square::A2, Bitboard::EMPTY),
|
||||
Square::A1.into_bitboard()
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_moves(Color::White, Square::A7, Bitboard::EMPTY),
|
||||
Square::A8.into_bitboard()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn captures() {
|
||||
assert_eq!(
|
||||
pawn_captures(Color::White, Square::A2),
|
||||
Square::B3.into_bitboard()
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_captures(Color::White, Square::B2),
|
||||
Square::A3 | Square::C3
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_captures(Color::White, Square::H2),
|
||||
Square::G3.into_bitboard()
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_captures(Color::Black, Square::A2),
|
||||
Square::B1.into_bitboard()
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_captures(Color::Black, Square::B2),
|
||||
Square::A1 | Square::C1
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_captures(Color::Black, Square::H2),
|
||||
Square::G1.into_bitboard()
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_captures(Color::White, Square::A7),
|
||||
Square::B8.into_bitboard()
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_captures(Color::White, Square::B7),
|
||||
Square::A8 | Square::C8
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_captures(Color::Black, Square::H7),
|
||||
Square::G6.into_bitboard()
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_captures(Color::Black, Square::A7),
|
||||
Square::B6.into_bitboard()
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_captures(Color::Black, Square::B7),
|
||||
Square::A6 | Square::C6
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_captures(Color::Black, Square::H7),
|
||||
Square::G6.into_bitboard()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn en_passant() {
|
||||
assert_eq!(en_passant_origins(Square::A4), Square::B4.into_bitboard());
|
||||
assert_eq!(en_passant_origins(Square::A5), Square::B5.into_bitboard());
|
||||
assert_eq!(en_passant_origins(Square::B4), Square::A4 | Square::C4);
|
||||
assert_eq!(en_passant_origins(Square::B5), Square::A5 | Square::C5);
|
||||
assert_eq!(en_passant_origins(Square::H4), Square::G4.into_bitboard());
|
||||
assert_eq!(en_passant_origins(Square::H5), Square::G5.into_bitboard());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
use crate::board::{Bitboard, Direction, Square};
|
||||
|
||||
/// Compute a rook's movement given a set of blockers that cannot be moved past.
|
||||
pub fn rook_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
Direction::iter_rook()
|
||||
.map(|dir| dir.slide_board_with_blockers(square.into_bitboard(), blockers))
|
||||
.fold(Bitboard::EMPTY, |lhs, rhs| lhs | rhs)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::board::{File, Rank};
|
||||
|
||||
#[test]
|
||||
fn moves_lower_left_square() {
|
||||
assert_eq!(
|
||||
rook_moves(Square::A1, Bitboard::EMPTY),
|
||||
(File::A.into_bitboard() | Rank::First.into_bitboard()) - Square::A1
|
||||
);
|
||||
assert_eq!(
|
||||
rook_moves(Square::A1, Bitboard::ALL),
|
||||
Square::A2 | Square::B1
|
||||
);
|
||||
assert_eq!(
|
||||
rook_moves(Square::A1, Rank::First.into_bitboard()),
|
||||
(File::A.into_bitboard() | Square::B1) - Square::A1
|
||||
);
|
||||
assert_eq!(
|
||||
rook_moves(Square::A1, File::A.into_bitboard()),
|
||||
(Rank::First.into_bitboard() | Square::A2) - Square::A1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moves_middle() {
|
||||
assert_eq!(
|
||||
rook_moves(Square::D4, Bitboard::EMPTY),
|
||||
(File::D.into_bitboard() | Rank::Fourth.into_bitboard()) - Square::D4
|
||||
);
|
||||
assert_eq!(
|
||||
rook_moves(Square::D4, Bitboard::ALL),
|
||||
Square::C4 | Square::D3 | Square::D5 | Square::E4
|
||||
);
|
||||
assert_eq!(
|
||||
rook_moves(Square::D4, Rank::Fourth.into_bitboard()),
|
||||
(File::D.into_bitboard() | Square::C4 | Square::E4) - Square::D4
|
||||
);
|
||||
assert_eq!(
|
||||
rook_moves(Square::D4, File::D.into_bitboard()),
|
||||
(Rank::Fourth.into_bitboard() | Square::D3 | Square::D5) - Square::D4
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
use crate::board::{Bitboard, Square};
|
||||
use crate::movegen::naive::{bishop_moves, rook_moves};
|
||||
|
||||
use super::mask::{generate_bishop_mask, generate_rook_mask};
|
||||
use super::Magic;
|
||||
|
||||
/// A trait to represent RNG for u64 values.
|
||||
pub(crate) trait RandGen {
|
||||
fn gen(&mut self) -> u64;
|
||||
}
|
||||
|
||||
type MagicGenerationType = (Vec<Magic>, Vec<Bitboard>);
|
||||
|
||||
pub fn generate_bishop_magics(rng: &mut dyn RandGen) -> MagicGenerationType {
|
||||
generate_magics(rng, generate_bishop_mask, bishop_moves)
|
||||
}
|
||||
|
||||
pub fn generate_rook_magics(rng: &mut dyn RandGen) -> MagicGenerationType {
|
||||
generate_magics(rng, generate_rook_mask, rook_moves)
|
||||
}
|
||||
|
||||
fn generate_magics(
|
||||
rng: &mut dyn RandGen,
|
||||
mask_fn: impl Fn(Square) -> Bitboard,
|
||||
moves_fn: impl Fn(Square, Bitboard) -> Bitboard,
|
||||
) -> MagicGenerationType {
|
||||
let mut magics = Vec::new();
|
||||
let mut boards = Vec::new();
|
||||
|
||||
for square in Square::iter() {
|
||||
let mask = mask_fn(square);
|
||||
|
||||
let occupancy_to_moves: Vec<_> = mask
|
||||
.iter_power_set()
|
||||
.map(|occupancy| (occupancy, moves_fn(square, occupancy)))
|
||||
.collect();
|
||||
|
||||
'candidate_search: loop {
|
||||
let mut candidate = Magic {
|
||||
magic: magic_candidate(rng),
|
||||
offset: 0,
|
||||
mask,
|
||||
shift: (64 - mask.count()) as u8,
|
||||
};
|
||||
let mut candidate_moves = vec![Bitboard::EMPTY; occupancy_to_moves.len()];
|
||||
|
||||
for (occupancy, moves) in occupancy_to_moves.iter().cloned() {
|
||||
let index = candidate.get_index(occupancy);
|
||||
// Non-constructive collision, try with another candidate
|
||||
if !candidate_moves[index].is_empty() && candidate_moves[index] != moves {
|
||||
continue 'candidate_search;
|
||||
}
|
||||
candidate_moves[index] = moves;
|
||||
}
|
||||
|
||||
// We have filled all candidate boards, record the correct offset and add the moves
|
||||
candidate.offset = boards.len();
|
||||
magics.push(candidate);
|
||||
boards.append(&mut candidate_moves);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(magics, boards)
|
||||
}
|
||||
|
||||
fn magic_candidate(rng: &mut dyn RandGen) -> u64 {
|
||||
// Few bits makes for better candidates
|
||||
rng.gen() & rng.gen() & rng.gen()
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
use crate::board::{Bitboard, File, Rank, Square};
|
||||
use crate::movegen::naive::{bishop::bishop_moves, rook::rook_moves};
|
||||
|
||||
/// Compute the relevancy mask for a bishop on a given [Square].
|
||||
pub fn generate_bishop_mask(square: Square) -> Bitboard {
|
||||
let rays = bishop_moves(square, Bitboard::EMPTY);
|
||||
|
||||
let mask = File::A.into_bitboard()
|
||||
| File::H.into_bitboard()
|
||||
| Rank::First.into_bitboard()
|
||||
| Rank::Eighth.into_bitboard();
|
||||
|
||||
rays - mask
|
||||
}
|
||||
|
||||
/// Compute the relevancy mask for a rook on a given [Square].
|
||||
pub fn generate_rook_mask(square: Square) -> Bitboard {
|
||||
let rays = rook_moves(square, Bitboard::EMPTY);
|
||||
|
||||
let mask = {
|
||||
let mut mask = Bitboard::EMPTY;
|
||||
if square.file() != File::A {
|
||||
mask |= File::A.into_bitboard()
|
||||
};
|
||||
if square.file() != File::H {
|
||||
mask |= File::H.into_bitboard()
|
||||
};
|
||||
if square.rank() != Rank::First {
|
||||
mask |= Rank::First.into_bitboard()
|
||||
};
|
||||
if square.rank() != Rank::Eighth {
|
||||
mask |= Rank::Eighth.into_bitboard()
|
||||
};
|
||||
mask
|
||||
};
|
||||
|
||||
rays - mask
|
||||
}
|
||||
|
|
@ -1,283 +0,0 @@
|
|||
mod generation;
|
||||
pub(super) use generation::*;
|
||||
mod mask;
|
||||
|
||||
use crate::board::{Bitboard, Square};
|
||||
|
||||
/// A type representing the magic board indexing a given [crate::board::Square].
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct Magic {
|
||||
/// Magic number.
|
||||
pub(self) magic: u64,
|
||||
/// Base offset into the magic square table.
|
||||
pub(self) offset: usize,
|
||||
/// Mask to apply to the blocker board before applying the magic.
|
||||
pub(self) mask: Bitboard,
|
||||
/// Length of the resulting mask after applying the magic.
|
||||
pub(self) shift: u8,
|
||||
}
|
||||
|
||||
impl Magic {
|
||||
/// Compute the index into the magics database for this set of `blockers`.
|
||||
pub fn get_index(&self, blockers: Bitboard) -> usize {
|
||||
let relevant_occupancy = (blockers & self.mask).0;
|
||||
let base_index = ((relevant_occupancy.wrapping_mul(self.magic)) >> self.shift) as usize;
|
||||
base_index + self.offset
|
||||
}
|
||||
}
|
||||
|
||||
/// A type encapsulating a database of [Magic] bitboard moves.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct MagicMoves {
|
||||
magics: Vec<Magic>,
|
||||
moves: Vec<Bitboard>,
|
||||
}
|
||||
|
||||
impl MagicMoves {
|
||||
/// Initialize a new [MagicMoves] given a matching list of [Magic] and its corresponding moves
|
||||
/// as a [Bitboard].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This should only be called with values generated by [crate::movegen::wizardry::generation].
|
||||
pub unsafe fn new(magics: Vec<Magic>, moves: Vec<Bitboard>) -> Self {
|
||||
Self { magics, moves }
|
||||
}
|
||||
|
||||
/// Get the set of valid moves for a piece standing on a [Square], given a set of blockers.
|
||||
pub fn query(&self, square: Square, blockers: Bitboard) -> Bitboard {
|
||||
// SAFETY: indices are in range by construction
|
||||
unsafe {
|
||||
let index = self
|
||||
.magics
|
||||
.get_unchecked(square.index())
|
||||
.get_index(blockers);
|
||||
*self.moves.get_unchecked(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region:sourcegen
|
||||
/// A set of magic numbers for bishop move generation.
|
||||
pub(crate) const BISHOP_SEED: [u64; 64] = [
|
||||
4908958787341189172,
|
||||
1157496606860279808,
|
||||
289395876198088778,
|
||||
649648646467355137,
|
||||
19162426089930848,
|
||||
564067194896448,
|
||||
18586170375029026,
|
||||
9185354800693760,
|
||||
72172012436987968,
|
||||
317226351607872,
|
||||
2597178509285688384,
|
||||
1162205282238464,
|
||||
144154788211329152,
|
||||
172197832046936160,
|
||||
4625762105940000802,
|
||||
1477217245166903296,
|
||||
2251937789583872,
|
||||
289373902621379585,
|
||||
4616200855845409024,
|
||||
2251909637357568,
|
||||
3532510975437640064,
|
||||
563517968228352,
|
||||
562953309660434,
|
||||
1196005458310201856,
|
||||
2350914225914520576,
|
||||
2287018679861376,
|
||||
13836188353273790593,
|
||||
11267795163676832,
|
||||
297519119119499264,
|
||||
18588344158519552,
|
||||
10453428171813953792,
|
||||
72128237668534272,
|
||||
1298164929055953920,
|
||||
865575144395900952,
|
||||
9293076573325312,
|
||||
108104018148197376,
|
||||
578503662094123152,
|
||||
4665870505495102224,
|
||||
6066493872259301520,
|
||||
285877477613857,
|
||||
2328941618281318466,
|
||||
721165292771739652,
|
||||
4899973577790523400,
|
||||
75050392749184,
|
||||
2305878200632215680,
|
||||
11530099074925593616,
|
||||
290561512873919880,
|
||||
18652187227888000,
|
||||
3379933716168704,
|
||||
9223409493537718272,
|
||||
22273835729926,
|
||||
1152921524003672064,
|
||||
4647812741240848385,
|
||||
1244225087719112712,
|
||||
7367907171013001728,
|
||||
9263922034316951570,
|
||||
300758214358598160,
|
||||
4611686331973636096,
|
||||
2377900605806479360,
|
||||
6958097192913601024,
|
||||
864691130877743617,
|
||||
703824948904066,
|
||||
612700674899317536,
|
||||
180742128018784384,
|
||||
];
|
||||
|
||||
/// A set of magic numbers for rook move generation.
|
||||
pub(crate) const ROOK_SEED: [u64; 64] = [
|
||||
2341871943948451840,
|
||||
18015635528220736,
|
||||
72066665545773824,
|
||||
1188959097794342912,
|
||||
12141713393631625314,
|
||||
720649693658353672,
|
||||
36029896538981888,
|
||||
36033359356363520,
|
||||
140746619355268,
|
||||
1158339898446446661,
|
||||
36591886560003650,
|
||||
578853633228023808,
|
||||
2392554490300416,
|
||||
140814806160384,
|
||||
180706952366596608,
|
||||
10696087878779396,
|
||||
1153260703948210820,
|
||||
310748649170673678,
|
||||
36311372044308544,
|
||||
9223444604757615104,
|
||||
1267187285230592,
|
||||
282574622818306,
|
||||
18722484274726152,
|
||||
2271591090110593,
|
||||
1153063519847989248,
|
||||
10168327557107712,
|
||||
4507998211276833,
|
||||
1153203035420233728,
|
||||
4631961017139660032,
|
||||
2454499182462107776,
|
||||
289367288355753288,
|
||||
18015815850820609,
|
||||
9268726066908758912,
|
||||
11547264697673728000,
|
||||
2314929519368081536,
|
||||
140943655192577,
|
||||
20266215511427202,
|
||||
180706969441535248,
|
||||
1302683805944911874,
|
||||
11534000122299940994,
|
||||
22676602724843520,
|
||||
4639271120198041668,
|
||||
1302104069046927376,
|
||||
9184220895313928,
|
||||
4612249105954373649,
|
||||
562984581726212,
|
||||
2312678200579457040,
|
||||
4647736876550193157,
|
||||
3170604524138139776,
|
||||
4684447574787096704,
|
||||
20283792725901696,
|
||||
1152992019380963840,
|
||||
117383863558471808,
|
||||
1153488854922068096,
|
||||
17596884583424,
|
||||
90074759127192064,
|
||||
4900502436426416706,
|
||||
4573968656793901,
|
||||
1161084564408385,
|
||||
1657887889314811910,
|
||||
4614501455660058690,
|
||||
4612530729109422081,
|
||||
642458506527236,
|
||||
1116704154754,
|
||||
];
|
||||
// endregion:sourcegen
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
// A simple XOR-shift RNG implementation.
|
||||
struct SimpleRng(u64);
|
||||
|
||||
impl SimpleRng {
|
||||
pub fn new() -> Self {
|
||||
Self(4) // https://xkcd.com/221/
|
||||
}
|
||||
}
|
||||
|
||||
impl RandGen for SimpleRng {
|
||||
fn gen(&mut self) -> u64 {
|
||||
self.0 ^= self.0 >> 12;
|
||||
self.0 ^= self.0 << 25;
|
||||
self.0 ^= self.0 >> 27;
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rng() {
|
||||
let mut rng = SimpleRng::new();
|
||||
|
||||
assert_eq!(rng.gen(), 134217733);
|
||||
assert_eq!(rng.gen(), 4504699139039237);
|
||||
assert_eq!(rng.gen(), 13512173405898766);
|
||||
assert_eq!(rng.gen(), 9225626310854853124);
|
||||
assert_eq!(rng.gen(), 29836777971867270);
|
||||
}
|
||||
|
||||
fn split_twice<'a>(
|
||||
text: &'a str,
|
||||
start_marker: &str,
|
||||
end_marker: &str,
|
||||
) -> Option<(&'a str, &'a str, &'a str)> {
|
||||
let (prefix, rest) = text.split_once(start_marker)?;
|
||||
let (mid, suffix) = rest.split_once(end_marker)?;
|
||||
Some((prefix, mid, suffix))
|
||||
}
|
||||
|
||||
fn array_string(piece_type: &str, values: &[Magic]) -> String {
|
||||
let mut res = format!(
|
||||
"/// A set of magic numbers for {} move generation.\n",
|
||||
piece_type
|
||||
);
|
||||
res.push_str(&format!(
|
||||
"pub(crate) const {}_SEED: [u64; 64] = [\n",
|
||||
piece_type.to_uppercase()
|
||||
));
|
||||
for magic in values {
|
||||
res.push_str(&format!(" {},\n", magic.magic));
|
||||
}
|
||||
res.push_str("];\n");
|
||||
res
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "slow"]
|
||||
// Regenerates the magic bitboard numbers.
|
||||
fn regen_magic_seeds() {
|
||||
// We only care about the magics, the moves can be recomputed at runtime ~cheaply.
|
||||
let (bishop_magics, _) = generate_bishop_magics(&mut SimpleRng::new());
|
||||
let (rook_magics, _) = generate_rook_magics(&mut SimpleRng::new());
|
||||
|
||||
let original_text = std::fs::read_to_string(file!()).unwrap();
|
||||
|
||||
let bishop_array = array_string("bishop", &bishop_magics[..]);
|
||||
let rook_array = array_string("rook", &rook_magics[..]);
|
||||
|
||||
let new_text = {
|
||||
let start_marker = "// region:sourcegen\n";
|
||||
let end_marker = "// endregion:sourcegen\n";
|
||||
let (prefix, _, suffix) =
|
||||
split_twice(&original_text, start_marker, end_marker).unwrap();
|
||||
format!("{prefix}{start_marker}{bishop_array}\n{rook_array}{end_marker}{suffix}")
|
||||
};
|
||||
|
||||
if new_text != original_text {
|
||||
std::fs::write(file!(), new_text).unwrap();
|
||||
panic!("source was not up-to-date")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,9 +15,12 @@
|
|||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! static_assert {
|
||||
($($tt:tt)*) => {
|
||||
($condition:expr) => {
|
||||
// Based on the latest one in `rustc`'s one before it was [removed].
|
||||
//
|
||||
// [removed]: https://github.com/rust-lang/rust/commit/c2dad1c6b9f9636198d7c561b47a2974f5103f6d
|
||||
#[allow(dead_code)]
|
||||
const _: () = assert!($($tt)*);
|
||||
const _: () = [()][!($condition) as usize];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,17 @@
|
|||
import enum
|
||||
|
||||
import gdb
|
||||
import gdb.printing
|
||||
|
||||
|
||||
class Square(object):
|
||||
"""
|
||||
Python representation of a 'seer::board::square::Square' raw value.
|
||||
Wrapper around GDB's representation of a 'seer::board::square::Square' in
|
||||
memory.
|
||||
"""
|
||||
|
||||
FILES = list(map(lambda n: chr(ord("A") + n), range(8)))
|
||||
FILES = list(map(lambda n: chr(ord('A') + n), range(8)))
|
||||
RANKS = list(map(lambda n: str(n + 1), range(8)))
|
||||
|
||||
def __init__(self, val):
|
||||
self._val = val
|
||||
|
||||
@classmethod
|
||||
def from_file_rank(cls, file, rank):
|
||||
return cls(file * 8 + rank)
|
||||
|
||||
def __str__(self):
|
||||
return self.FILES[self.file] + self.RANKS[self.rank]
|
||||
|
||||
|
|
@ -30,10 +23,10 @@ class Square(object):
|
|||
def file(self):
|
||||
return int(self._val) // 8
|
||||
|
||||
|
||||
class Bitboard(object):
|
||||
"""
|
||||
Python representation of a 'seer::board::bitboard::Bitboard' raw value.
|
||||
Wrapper around GDB's representation of a 'seer::board::bitboard::Bitboard'
|
||||
in memory.
|
||||
"""
|
||||
|
||||
def __init__(self, val):
|
||||
|
|
@ -42,260 +35,14 @@ class Bitboard(object):
|
|||
def __str__(self):
|
||||
return "[" + ", ".join(map(str, self.squares)) + "]"
|
||||
|
||||
def at(self, square):
|
||||
return bool(self._val & (1 << square._val))
|
||||
|
||||
@property
|
||||
def squares(self):
|
||||
n = self._val
|
||||
n = int(self._val["__0"])
|
||||
while n:
|
||||
b = n & (~n + 1)
|
||||
b = n & (~n+1)
|
||||
yield Square(b.bit_length() - 1)
|
||||
n ^= b
|
||||
|
||||
|
||||
class CastleRights(enum.IntEnum):
|
||||
"""
|
||||
Python representation of a 'seer::board::castle_rights::CastleRights' raw value.
|
||||
"""
|
||||
|
||||
# Should be kept in sync with the enum in `color.rs`
|
||||
NO_SIDE = 0
|
||||
KING_SIDE = 1
|
||||
QUEEN_SIDE = 2
|
||||
BOTH_SIDES = 3
|
||||
|
||||
def __str__(self):
|
||||
return self.name.title().replace("_", "")
|
||||
|
||||
|
||||
class Color(enum.IntEnum):
|
||||
"""
|
||||
Python representation of a 'seer::board::color::Color' raw value.
|
||||
"""
|
||||
|
||||
# Should be kept in sync with the enum in `color.rs`
|
||||
WHITE = 0
|
||||
BLACK = 1
|
||||
|
||||
def __str__(self):
|
||||
return self.name.title()
|
||||
|
||||
|
||||
class File(enum.IntEnum):
|
||||
"""
|
||||
Python representation of a 'seer::board::file::File' raw value.
|
||||
"""
|
||||
|
||||
# Should be kept in sync with the enum in `file.rs`
|
||||
A = 0
|
||||
B = 1
|
||||
C = 2
|
||||
D = 3
|
||||
E = 4
|
||||
F = 5
|
||||
G = 6
|
||||
H = 7
|
||||
|
||||
def __str__(self):
|
||||
return self.name.title()
|
||||
|
||||
|
||||
class Rank(enum.IntEnum):
|
||||
"""
|
||||
Python representation of a 'seer::board::rank::Rank' raw value.
|
||||
"""
|
||||
|
||||
# Should be kept in sync with the enum in `rank.rs`
|
||||
First = 0
|
||||
Second = 1
|
||||
Third = 2
|
||||
Fourth = 3
|
||||
Fifth = 4
|
||||
Sixth = 5
|
||||
Seventh = 6
|
||||
Eighth = 7
|
||||
|
||||
def __str__(self):
|
||||
return self.name.title()
|
||||
|
||||
|
||||
class Piece(enum.IntEnum):
|
||||
"""
|
||||
Python representation of a 'seer::board::piece::Piece' raw value.
|
||||
"""
|
||||
|
||||
# Should be kept in sync with the enum in `piece.rs`
|
||||
KING = 0
|
||||
QUEEN = 1
|
||||
ROOK = 2
|
||||
BISHOP = 3
|
||||
KNIGHT = 4
|
||||
PAWN = 5
|
||||
|
||||
def __str__(self):
|
||||
return self.name.title()
|
||||
|
||||
|
||||
class Move(object):
|
||||
"""
|
||||
Wrapper around GDB's representation of a 'seer::board::move::Move'
|
||||
in memory.
|
||||
"""
|
||||
|
||||
# Should be kept in sync with the values in `move.rs`
|
||||
PIECE_SHIFT = 0
|
||||
PIECE_MASK = 0b111
|
||||
START_SHIFT = 3
|
||||
START_MASK = 0b11_1111
|
||||
DESTINATION_SHIFT = 9
|
||||
DESTINATION_MASK = 0b11_1111
|
||||
CAPTURE_SHIFT = 15
|
||||
CAPTURE_MASK = 0b111
|
||||
PROMOTION_SHIFT = 18
|
||||
PROMOTION_MASK = 0b111
|
||||
EN_PASSANT_SHIFT = 21
|
||||
EN_PASSANT_MASK = 0b1
|
||||
DOUBLE_STEP_SHIFT = 22
|
||||
DOUBLE_STEP_MASK = 0b1
|
||||
CASTLING_SHIFT = 23
|
||||
CASTLING_MASK = 0b1
|
||||
|
||||
def __init__(self, val):
|
||||
self._val = val
|
||||
|
||||
@property
|
||||
def piece(self):
|
||||
return Piece(self._val >> self.PIECE_SHIFT & self.PIECE_MASK)
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
return Square(self._val >> self.START_SHIFT & self.START_MASK)
|
||||
|
||||
@property
|
||||
def destination(self):
|
||||
return Square(self._val >> self.DESTINATION_SHIFT & self.DESTINATION_MASK)
|
||||
|
||||
@property
|
||||
def capture(self):
|
||||
index = self._val >> self.CAPTURE_SHIFT & self.CAPTURE_MASK
|
||||
if index == 7:
|
||||
return None
|
||||
return Piece(index)
|
||||
|
||||
@property
|
||||
def promotion(self):
|
||||
index = self._val >> self.PROMOTION_SHIFT & self.PROMOTION_MASK
|
||||
if index == 7:
|
||||
return None
|
||||
return Piece(index)
|
||||
|
||||
@property
|
||||
def en_passant(self):
|
||||
return bool(self._val >> self.EN_PASSANT_SHIFT & self.EN_PASSANT_MASK)
|
||||
|
||||
@property
|
||||
def double_step(self):
|
||||
return bool(self._val >> self.DOUBLE_STEP_SHIFT & self.DOUBLE_STEP_MASK)
|
||||
|
||||
@property
|
||||
def castling(self):
|
||||
return bool(self._val >> self.CASTLING_SHIFT & self.CASTLING_MASK)
|
||||
|
||||
def __str__(self):
|
||||
KEYS = [
|
||||
"piece",
|
||||
"start",
|
||||
"destination",
|
||||
"capture",
|
||||
"promotion",
|
||||
"en_passant",
|
||||
"double_step",
|
||||
"castling",
|
||||
]
|
||||
print_opt = lambda val: "(None)" if val is None else str(val)
|
||||
indent = lambda s: " " + s
|
||||
|
||||
values = [key + ": " + print_opt(getattr(self, key)) + ",\n" for key in KEYS]
|
||||
return "Move{\n" + "".join(map(indent, values)) + "}"
|
||||
|
||||
|
||||
class ChessBoard(object):
|
||||
"""
|
||||
Wrapper around GDB's representation of a 'seer::board::chess_board::ChessBoard'
|
||||
in memory.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
piece_occupancy,
|
||||
color_occupancy,
|
||||
castle_rights,
|
||||
half_move_clock,
|
||||
total_plies,
|
||||
side,
|
||||
):
|
||||
self._piece_occupancy = list(map(Bitboard, piece_occupancy))
|
||||
self._color_occupancy = list(map(Bitboard, color_occupancy))
|
||||
self._castle_rights = list(map(CastleRights, castle_rights))
|
||||
self._half_move_clock = int(half_move_clock)
|
||||
self._total_plies = int(total_plies)
|
||||
self._side = Color(side)
|
||||
|
||||
@classmethod
|
||||
def from_gdb(cls, val):
|
||||
return cls(
|
||||
[int(val["piece_occupancy"][p]["__0"]) for p in Piece],
|
||||
[int(val["color_occupancy"][c]["__0"]) for c in Color],
|
||||
[int(val["castle_rights"][c]) for c in Color],
|
||||
# FIXME: find out how to check for Some/None in val["en_passant"],
|
||||
int(val["half_move_clock"]),
|
||||
int(val["total_plies"]),
|
||||
Color(int(val["side"])),
|
||||
)
|
||||
|
||||
def at(self, square):
|
||||
for piece in Piece:
|
||||
if not self._piece_occupancy[piece].at(square):
|
||||
continue
|
||||
for color in Color:
|
||||
if not self._color_occupancy[color].at(square):
|
||||
continue
|
||||
return (piece, color)
|
||||
return None
|
||||
|
||||
def pretty_str(self):
|
||||
def pretty_piece(piece, color):
|
||||
return [
|
||||
("♚", "♔"),
|
||||
("♛", "♕"),
|
||||
("♜", "♖"),
|
||||
("♝", "♗"),
|
||||
("♞", "♘"),
|
||||
("♟", "♙"),
|
||||
][piece][color]
|
||||
|
||||
board = [
|
||||
[self.at(Square.from_file_rank(file, rank)) for file in File]
|
||||
for rank in Rank
|
||||
]
|
||||
|
||||
res = []
|
||||
res.append(" A B C D E F G H ")
|
||||
for n, line in reversed(list(enumerate(board, start=1))):
|
||||
strings = [str(n) + " "]
|
||||
strings.extend(" " if p is None else pretty_piece(*p) for p in line)
|
||||
strings.append(" " + str(n))
|
||||
res.append("|".join(strings))
|
||||
res.append(" A B C D E F G H ")
|
||||
res += [
|
||||
"Half-move clock: " + str(self._half_move_clock),
|
||||
"Total plies: " + str(self._total_plies),
|
||||
"Side to play: " + str(self._side),
|
||||
]
|
||||
return "\n".join(res)
|
||||
|
||||
|
||||
class SquarePrinter(object):
|
||||
"Print a seer::board::square::Square"
|
||||
|
||||
|
|
@ -305,110 +52,27 @@ class SquarePrinter(object):
|
|||
def to_string(self):
|
||||
return str(self._val)
|
||||
|
||||
def display_hint(self):
|
||||
return 'string'
|
||||
|
||||
class BitboardPrinter(object):
|
||||
"Print a seer::board::bitboard::Bitboard"
|
||||
|
||||
def __init__(self, val):
|
||||
self._val = Bitboard(int(val["__0"]))
|
||||
self._val = Bitboard(val)
|
||||
|
||||
def to_string(self):
|
||||
return "Bitboard{" + str(self._val)[1:-1] + "}"
|
||||
|
||||
|
||||
class CastleRightsPrinter(object):
|
||||
"Print a seer::board::castle_rights::CastleRights"
|
||||
|
||||
def __init__(self, val):
|
||||
self._val = CastleRights(int(val))
|
||||
|
||||
def to_string(self):
|
||||
return str(self._val)
|
||||
|
||||
|
||||
class ColorPrinter(object):
|
||||
"Print a seer::board::color::Color"
|
||||
|
||||
def __init__(self, val):
|
||||
self._val = Color(int(val))
|
||||
|
||||
def to_string(self):
|
||||
return str(self._val)
|
||||
|
||||
|
||||
class FilePrinter(object):
|
||||
"Print a seer::board::file::File"
|
||||
|
||||
def __init__(self, val):
|
||||
self._val = File(int(val))
|
||||
|
||||
def to_string(self):
|
||||
return str(self._val)
|
||||
|
||||
|
||||
class RankPrinter(object):
|
||||
"Print a seer::board::rank::Rank"
|
||||
|
||||
def __init__(self, val):
|
||||
self._val = Rank(int(val))
|
||||
|
||||
def to_string(self):
|
||||
return str(self._val)
|
||||
|
||||
|
||||
class PiecePrinter(object):
|
||||
"Print a seer::board::piece::Piece"
|
||||
|
||||
def __init__(self, val):
|
||||
self._val = Piece(int(val))
|
||||
|
||||
def to_string(self):
|
||||
return str(self._val)
|
||||
|
||||
|
||||
class MovePrinter(object):
|
||||
"Print a seer::board::move::Move"
|
||||
|
||||
def __init__(self, val):
|
||||
self._val = Move(int(val["__0"]))
|
||||
|
||||
def to_string(self):
|
||||
return str(self._val)
|
||||
|
||||
|
||||
class PrintBoard(gdb.Command):
|
||||
"""
|
||||
Pretty-print a 'seer::board::chess_board::ChessBoard' as a 2D textual chess board.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(PrintBoard, self).__init__(
|
||||
"print-board", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
|
||||
)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
board = ChessBoard.from_gdb(gdb.parse_and_eval(arg))
|
||||
print(board.pretty_str())
|
||||
|
||||
def display_hint(self):
|
||||
return 'string'
|
||||
|
||||
def build_pretty_printer():
|
||||
pp = gdb.printing.RegexpCollectionPrettyPrinter('seer')
|
||||
|
||||
pp.add_printer('Square', '^seer::board::square::Square$', SquarePrinter)
|
||||
pp.add_printer('Bitboard', '^seer::board::bitboard::Bitboard$', BitboardPrinter)
|
||||
pp.add_printer('CastleRights', '^seer::board::castle_rights::CastleRights$', CastleRightsPrinter)
|
||||
pp.add_printer('Color', '^seer::board::color::Color$', ColorPrinter)
|
||||
pp.add_printer('File', '^seer::board::file::File$', FilePrinter)
|
||||
pp.add_printer('Rank', '^seer::board::rank::Rank$', RankPrinter)
|
||||
pp.add_printer('Piece', '^seer::board::piece::Piece$', ColorPrinter)
|
||||
pp.add_printer('Move', '^seer::board::move::Move$', MovePrinter)
|
||||
pp.add_printer('BigNum', '^seer::board::square::Square$', SquarePrinter)
|
||||
pp.add_printer('BigNum', '^seer::board::bitboard::Bitboard$', BitboardPrinter)
|
||||
|
||||
return pp
|
||||
|
||||
|
||||
def register_commands():
|
||||
PrintBoard()
|
||||
|
||||
|
||||
gdb.printing.register_pretty_printer(gdb.current_objfile(), build_pretty_printer(), True)
|
||||
register_commands()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue