diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..7037529 --- /dev/null +++ b/.drone.yml @@ -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 +... diff --git a/.envrc b/.envrc deleted file mode 100644 index de77fcb..0000000 --- a/.envrc +++ /dev/null @@ -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 diff --git a/.gitignore b/.gitignore index 5f360ff..c2f669f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.woodpecker/check.yml b/.woodpecker/check.yml deleted file mode 100644 index 9135f7b..0000000 --- a/.woodpecker/check.yml +++ /dev/null @@ -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 diff --git a/flake.lock b/flake.lock index 18cea66..2601b96 100644 --- a/flake.lock +++ b/flake.lock @@ -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" } } diff --git a/flake.nix b/flake.nix index ca06a36..22a05e4 100644 --- a/flake.nix +++ b/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; - }; - }); + }); } diff --git a/src/board/bitboard/error.rs b/src/board/bitboard/error.rs deleted file mode 100644 index c631482..0000000 --- a/src/board/bitboard/error.rs +++ /dev/null @@ -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 {} diff --git a/src/board/bitboard/iterator.rs b/src/board/bitboard/iterator.rs index 7c01a9a..fcd644c 100644 --- a/src/board/bitboard/iterator.rs +++ b/src/board/bitboard/iterator.rs @@ -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; diff --git a/src/board/bitboard/mod.rs b/src/board/bitboard/mod.rs index b0ec90a..8b716be 100644 --- a/src/board/bitboard/mod.rs +++ b/src/board/bitboard/mod.rs @@ -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 { - 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 for Bitboard { - type Error = IntoSquareError; - - fn try_into(self) -> Result { - 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 for Bitboard { } } -/// Treat bitboard as a set of squares, shift each square's index left by the amount given. -impl std::ops::ShlAssign 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 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 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 for Bitboard { type Output = Bitboard; @@ -185,22 +134,6 @@ impl std::ops::BitOr for Bitboard { } } -/// Treat each bitboard as a set of squares, keep squares that are in either sets. -impl std::ops::BitOrAssign 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 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 for Bitboard { type Output = Bitboard; @@ -211,7 +144,7 @@ impl std::ops::BitAnd 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 for Bitboard { type Output = Bitboard; @@ -221,22 +154,6 @@ impl std::ops::BitAnd for Bitboard { } } -/// Treat each bitboard as a set of squares, keep squares that are in both sets. -impl std::ops::BitAndAssign 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 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 for Bitboard { type Output = Bitboard; @@ -247,7 +164,7 @@ impl std::ops::BitXor 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 for Bitboard { type Output = Bitboard; @@ -257,22 +174,6 @@ impl std::ops::BitXor for Bitboard { } } -/// Treat each bitboard as a set of squares, keep squares that are in exactly one of either set. -impl std::ops::BitXorAssign 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 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 for Bitboard { type Output = Bitboard; @@ -283,7 +184,7 @@ impl std::ops::Sub 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 for Bitboard { type Output = Bitboard; @@ -293,28 +194,10 @@ impl std::ops::Sub for Bitboard { } } -/// Treat each bitboard as a set of squares, and substract one set from another. -impl std::ops::SubAssign 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 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![Bitboard::EMPTY] - ) - } - - #[test] - fn iter_power_set_one_square() { - for square in Square::iter() { - assert_eq!( - square - .into_bitboard() - .iter_power_set() - .collect::>(), - [Bitboard::EMPTY, square.into_bitboard()] - .into_iter() - .collect::>() - ) - } - } - - #[test] - fn iter_power_set_two_squares() { - assert_eq!( - (Square::A1 | Square::H8) - .iter_power_set() - .collect::>(), - [ - Bitboard::EMPTY, - Square::A1.into_bitboard(), - Square::H8.into_bitboard(), - Square::A1 | Square::H8 - ] - .into_iter() - .collect::>() - ) - } - - #[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::>(), - (0..(1 << 6)).map(Bitboard).collect::>() - ) - } - - #[test] - fn iter_power_set_eight_squares_length() { - assert_eq!( - File::A - .into_bitboard() - .iter_power_set() - .collect::>() - .len(), - 1 << 8 - ); - assert_eq!( - Rank::First - .into_bitboard() - .iter_power_set() - .collect::>() - .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::::try_into(Bitboard::EMPTY), - Err(IntoSquareError::EmptyBoard) - ); - assert_eq!( - TryInto::::try_into(Square::A1 | Square::A2), - Err(IntoSquareError::TooManySquares) - ) - } } diff --git a/src/board/bitboard/superset.rs b/src/board/bitboard/superset.rs deleted file mode 100644 index 1a82ca1..0000000 --- a/src/board/bitboard/superset.rs +++ /dev/null @@ -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 { - 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) { - let size = 1 << self.board.count(); - (size, Some(size)) - } -} - -impl ExactSizeIterator for BitboardPowerSetIterator {} - -impl std::iter::FusedIterator for BitboardPowerSetIterator {} diff --git a/src/board/castle_rights.rs b/src/board/castle_rights.rs index b34d952..0e44e03 100644 --- a/src/board/castle_rights.rs +++ b/src/board/castle_rights.rs @@ -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 { diff --git a/src/board/chess_board/builder.rs b/src/board/chess_board/builder.rs deleted file mode 100644 index 8221d92..0000000 --- a/src/board/chess_board/builder.rs +++ /dev/null @@ -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, - 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 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 for ChessBoardBuilder { - fn index_mut(&mut self, square: Square) -> &mut Self::Output { - &mut self.pieces[square.index()] - } -} - -impl TryFrom for ChessBoard { - type Error = InvalidError; - - fn try_from(builder: ChessBoardBuilder) -> Result { - 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()) - } -} diff --git a/src/board/chess_board/error.rs b/src/board/chess_board/error.rs deleted file mode 100644 index e6ef030..0000000 --- a/src/board/chess_board/error.rs +++ /dev/null @@ -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 {} diff --git a/src/board/chess_board/mod.rs b/src/board/chess_board/mod.rs deleted file mode 100644 index 2a98537..0000000 --- a/src/board/chess_board/mod.rs +++ /dev/null @@ -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, - /// 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, - 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 { - 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::::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::::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::::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::::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::::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::::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::::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::::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::::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::::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::::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() - ); - } -} diff --git a/src/board/color.rs b/src/board/color.rs index 66b21b3..d71df67 100644 --- a/src/board/color.rs +++ b/src/board/color.rs @@ -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 { - 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)] diff --git a/src/board/direction.rs b/src/board/direction.rs index 40c8d69..135f5f4 100644 --- a/src/board/direction.rs +++ b/src/board/direction.rs @@ -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 - ); - } } diff --git a/src/board/file.rs b/src/board/file.rs index 1475e9a..1a64929 100644 --- a/src/board/file.rs +++ b/src/board/file.rs @@ -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 { + pub fn iter() -> impl Iterator { 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) diff --git a/src/board/mod.rs b/src/board/mod.rs index 0e34331..ad91192 100644 --- a/src/board/mod.rs +++ b/src/board/mod.rs @@ -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::*; diff --git a/src/board/move.rs b/src/board/move.rs deleted file mode 100644 index c7a6980..0000000 --- a/src/board/move.rs +++ /dev/null @@ -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, - pub promotion: Option, - pub en_passant: bool, - pub double_step: bool, - pub castling: bool, -} - -impl From 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, - promotion: Option, - 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 { - 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 { - 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()); - } -} diff --git a/src/board/piece.rs b/src/board/piece.rs deleted file mode 100644 index 58f989a..0000000 --- a/src/board/piece.rs +++ /dev/null @@ -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 { - 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); - } -} diff --git a/src/board/rank.rs b/src/board/rank.rs index f448df5..a3c783a 100644 --- a/src/board/rank.rs +++ b/src/board/rank.rs @@ -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 { + pub fn iter() -> impl Iterator { 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) diff --git a/src/board/square.rs b/src/board/square.rs index 958c3c9..e8588eb 100644 --- a/src/board/square.rs +++ b/src/board/square.rs @@ -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 { + pub fn iter() -> impl Iterator { 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 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 for Square { #[inline(always)] fn shr(self, rhs: usize) -> Self::Output { - #[allow(clippy::suspicious_arithmetic_impl)] Square::from_index(self as usize - rhs) } } diff --git a/src/fen.rs b/src/fen.rs deleted file mode 100644 index 3096c95..0000000 --- a/src/fen.rs +++ /dev/null @@ -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; -} - -/// 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 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 { - 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 { - 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 { - type Err = FenError; - - fn from_fen(s: &str) -> Result { - 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 { - 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 { - 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::::from_fen(en_passant_square)? { - builder.with_en_passant(square); - }; - - let half_move_clock = half_move_clock - .parse::() - .map_err(|_| FenError::InvalidFen)?; - builder.with_half_move_clock(half_move_clock); - - let full_move_counter = full_move_counter - .parse::() - .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 - ); - } -} diff --git a/src/lib.rs b/src/lib.rs index 82467ad..3593172 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,2 @@ pub mod board; -pub mod fen; -pub mod movegen; pub mod utils; diff --git a/src/movegen/mod.rs b/src/movegen/mod.rs deleted file mode 100644 index f9ce658..0000000 --- a/src/movegen/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Naive move generation -mod naive; - -// Magic bitboard generation -mod wizardry; - -// Magic bitboard definitions -mod moves; -pub use moves::*; diff --git a/src/movegen/moves.rs b/src/movegen/moves.rs deleted file mode 100644 index 9840083..0000000 --- a/src/movegen/moves.rs +++ /dev/null @@ -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 = 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 = 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) -} diff --git a/src/movegen/naive/bishop.rs b/src/movegen/naive/bishop.rs deleted file mode 100644 index 7a2c97f..0000000 --- a/src/movegen/naive/bishop.rs +++ /dev/null @@ -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 - ); - } -} diff --git a/src/movegen/naive/king.rs b/src/movegen/naive/king.rs deleted file mode 100644 index fdbedb7..0000000 --- a/src/movegen/naive/king.rs +++ /dev/null @@ -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 - ); - } -} diff --git a/src/movegen/naive/knight.rs b/src/movegen/naive/knight.rs deleted file mode 100644 index 28ad7f2..0000000 --- a/src/movegen/naive/knight.rs +++ /dev/null @@ -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 - ); - } -} diff --git a/src/movegen/naive/mod.rs b/src/movegen/naive/mod.rs deleted file mode 100644 index 1c64606..0000000 --- a/src/movegen/naive/mod.rs +++ /dev/null @@ -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::*; diff --git a/src/movegen/naive/pawn.rs b/src/movegen/naive/pawn.rs deleted file mode 100644 index bde5215..0000000 --- a/src/movegen/naive/pawn.rs +++ /dev/null @@ -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()); - } -} diff --git a/src/movegen/naive/rook.rs b/src/movegen/naive/rook.rs deleted file mode 100644 index e61f5ec..0000000 --- a/src/movegen/naive/rook.rs +++ /dev/null @@ -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 - ); - } -} diff --git a/src/movegen/wizardry/generation.rs b/src/movegen/wizardry/generation.rs deleted file mode 100644 index 0322977..0000000 --- a/src/movegen/wizardry/generation.rs +++ /dev/null @@ -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, Vec); - -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() -} diff --git a/src/movegen/wizardry/mask.rs b/src/movegen/wizardry/mask.rs deleted file mode 100644 index 865c986..0000000 --- a/src/movegen/wizardry/mask.rs +++ /dev/null @@ -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 -} diff --git a/src/movegen/wizardry/mod.rs b/src/movegen/wizardry/mod.rs deleted file mode 100644 index 00645d8..0000000 --- a/src/movegen/wizardry/mod.rs +++ /dev/null @@ -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, - moves: Vec, -} - -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, moves: Vec) -> 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") - } - } -} diff --git a/src/utils/static_assert.rs b/src/utils/static_assert.rs index 2c7a9a8..81b7a86 100644 --- a/src/utils/static_assert.rs +++ b/src/utils/static_assert.rs @@ -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]; }; } diff --git a/utils/gdb/seer_pretty_printers.py b/utils/gdb/seer_pretty_printers.py index 0481d8d..48a2e94 100644 --- a/utils/gdb/seer_pretty_printers.py +++ b/utils/gdb/seer_pretty_printers.py @@ -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()