├── .gitignore ├── Cross.toml ├── Cargo.toml ├── ci ├── before_deploy.ps1 ├── script.sh ├── before_deploy.sh └── install.sh ├── fathom ├── LICENSE ├── src │ ├── inline-wrapper.c │ ├── tbconfig.h │ ├── stdendian.h │ └── tbprobe.h └── README.md ├── Cargo.lock ├── README.md ├── src ├── history.rs ├── main.rs ├── rand.rs ├── types.rs ├── repetitions.rs ├── bench.rs ├── time.rs ├── fathom.rs ├── hash.rs ├── movepick.rs ├── uci.rs ├── magic.rs ├── tt.rs ├── search_controller.rs ├── movegen.rs └── bitboard.rs ├── .travis.yml └── appveyor.yml /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [build.env] 2 | passthrough = [ 3 | "RUST_BACKTRACE", 4 | "RUST_LOG", 5 | "TRAVIS", 6 | "RUSTFLAGS", 7 | ] 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asymptote" 3 | version = "0.8.0" 4 | authors = ["Maximilian Lupke "] 5 | license = "GPL-3.0-only" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | arrayvec = "0.7" 10 | crossbeam-utils = "0.8" 11 | libc = { version = "0.2", optional = true } 12 | 13 | [build-dependencies] 14 | cc = { version = "1.0", optional = true } 15 | 16 | [profile.release] 17 | lto = true 18 | debug = true 19 | codegen-units = 1 20 | 21 | [features] 22 | default = ["fathom"] 23 | 24 | tune = [] 25 | fathom = ["cc", "libc"] 26 | pext = [] 27 | -------------------------------------------------------------------------------- /ci/before_deploy.ps1: -------------------------------------------------------------------------------- 1 | # This script takes care of packaging the build artifacts that will go in the 2 | # release zipfile 3 | 4 | $SRC_DIR = $PWD.Path 5 | $STAGE = [System.Guid]::NewGuid().ToString() 6 | 7 | Set-Location $ENV:Temp 8 | New-Item -Type Directory -Name $STAGE 9 | Set-Location $STAGE 10 | 11 | $ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:NAME).zip" 12 | 13 | # TODO Update this to package the right artifacts 14 | Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\asymptote.exe" '.\' 15 | 16 | 7z a "$ZIP" * 17 | 18 | Push-AppveyorArtifact "$ZIP" 19 | 20 | Remove-Item *.* -Force 21 | Set-Location .. 22 | Remove-Item $STAGE 23 | Set-Location $SRC_DIR 24 | -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | # This script takes care of testing your crate 2 | 3 | set -ex 4 | 5 | # TODO This is the "test phase", tweak it as you see fit 6 | main() { 7 | RUSTFLAGS="-C target-feature=$FEATURES" cross build --target $TARGET 8 | RUSTFLAGS="-C target-feature=$FEATURES" cross build --target $TARGET --release 9 | 10 | if [ ! -z $DISABLE_TESTS ]; then 11 | return 12 | fi 13 | 14 | RUSTFLAGS="-C target-feature=$FEATURES" cross test --target $TARGET 15 | RUSTFLAGS="-C target-feature=$FEATURES" cross test --target $TARGET --release 16 | 17 | # cross run --target $TARGET 18 | # cross run --target $TARGET --release 19 | } 20 | 21 | # we don't run the "test phase" when doing deploys 22 | if [ -z $TRAVIS_TAG ]; then 23 | main 24 | fi 25 | -------------------------------------------------------------------------------- /ci/before_deploy.sh: -------------------------------------------------------------------------------- 1 | # This script takes care of building your crate and packaging it for release 2 | 3 | set -ex 4 | 5 | main() { 6 | local src=$(pwd) \ 7 | stage= 8 | 9 | case $TRAVIS_OS_NAME in 10 | linux) 11 | stage=$(mktemp -d) 12 | ;; 13 | osx) 14 | stage=$(mktemp -d -t tmp) 15 | ;; 16 | esac 17 | 18 | test -f Cargo.lock || cargo generate-lockfile 19 | 20 | # TODO Update this to build the artifacts that matter to you 21 | cross rustc --target $TARGET --release -- -C lto -C target-feature=$FEATURES -C target-cpu=$CPU 22 | 23 | # TODO Update this to package the right artifacts 24 | cp target/$TARGET/release/asymptote $stage/ 25 | 26 | cd $stage 27 | tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$NAME.tar.gz * 28 | cd $src 29 | 30 | rm -rf $stage 31 | } 32 | 33 | main 34 | -------------------------------------------------------------------------------- /fathom/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 basil00 4 | Modifications Copyright (c) 2016-2019 by Jon Dart 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /fathom/src/inline-wrapper.c: -------------------------------------------------------------------------------- 1 | #include "tbprobe.h" 2 | 3 | unsigned tb_probe_wdl_wrapper( 4 | uint64_t _white, 5 | uint64_t _black, 6 | uint64_t _kings, 7 | uint64_t _queens, 8 | uint64_t _rooks, 9 | uint64_t _bishops, 10 | uint64_t _knights, 11 | uint64_t _pawns, 12 | unsigned _rule50, 13 | unsigned _castling, 14 | unsigned _ep, 15 | bool _turn) 16 | { 17 | return tb_probe_wdl( 18 | _white, 19 | _black, 20 | _kings, 21 | _queens, 22 | _rooks, 23 | _bishops, 24 | _knights, 25 | _pawns, 26 | _rule50, 27 | _castling, 28 | _ep, 29 | _turn 30 | ); 31 | } 32 | 33 | unsigned tb_probe_root_wrapper( 34 | uint64_t _white, 35 | uint64_t _black, 36 | uint64_t _kings, 37 | uint64_t _queens, 38 | uint64_t _rooks, 39 | uint64_t _bishops, 40 | uint64_t _knights, 41 | uint64_t _pawns, 42 | unsigned _rule50, 43 | unsigned _castling, 44 | unsigned _ep, 45 | bool _turn) 46 | { 47 | return tb_probe_root( 48 | _white, 49 | _black, 50 | _kings, 51 | _queens, 52 | _rooks, 53 | _bishops, 54 | _knights, 55 | _pawns, 56 | _rule50, 57 | _castling, 58 | _ep, 59 | _turn, 60 | 0 61 | ); 62 | } -------------------------------------------------------------------------------- /ci/install.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | main() { 4 | local target= 5 | if [ $TRAVIS_OS_NAME = linux ]; then 6 | target=x86_64-unknown-linux-musl 7 | sort=sort 8 | else 9 | target=x86_64-apple-darwin 10 | sort=gsort # for `sort --sort-version`, from brew's coreutils. 11 | fi 12 | 13 | # Builds for iOS are done on OSX, but require the specific target to be 14 | # installed. 15 | case $TARGET in 16 | aarch64-apple-ios) 17 | rustup target install aarch64-apple-ios 18 | ;; 19 | armv7-apple-ios) 20 | rustup target install armv7-apple-ios 21 | ;; 22 | armv7s-apple-ios) 23 | rustup target install armv7s-apple-ios 24 | ;; 25 | i386-apple-ios) 26 | rustup target install i386-apple-ios 27 | ;; 28 | x86_64-apple-ios) 29 | rustup target install x86_64-apple-ios 30 | ;; 31 | esac 32 | 33 | # This fetches latest stable release 34 | local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \ 35 | | cut -d/ -f3 \ 36 | | grep -E '^v[0.1.0-9.]+$' \ 37 | | $sort --version-sort \ 38 | | tail -n1) 39 | curl -LSfs https://japaric.github.io/trust/install.sh | \ 40 | sh -s -- \ 41 | --force \ 42 | --git japaric/cross \ 43 | --tag $tag \ 44 | --target $target 45 | } 46 | 47 | main 48 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "arrayvec" 7 | version = "0.7.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" 10 | 11 | [[package]] 12 | name = "asymptote" 13 | version = "0.8.0" 14 | dependencies = [ 15 | "arrayvec", 16 | "cc", 17 | "crossbeam-utils", 18 | "libc", 19 | ] 20 | 21 | [[package]] 22 | name = "cc" 23 | version = "1.0.72" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 26 | 27 | [[package]] 28 | name = "cfg-if" 29 | version = "1.0.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 32 | 33 | [[package]] 34 | name = "crossbeam-utils" 35 | version = "0.8.6" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" 38 | dependencies = [ 39 | "cfg-if", 40 | "lazy_static", 41 | ] 42 | 43 | [[package]] 44 | name = "lazy_static" 45 | version = "1.4.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 48 | 49 | [[package]] 50 | name = "libc" 51 | version = "0.2.116" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asymptote 2 | Asymptote is a UCI (Universal Chess Interface) chess engine. Currently it does not implement the complete UCI specification, e.g. pondering is currently not supported. 3 | 4 | Asymptote does not include a graphical user interface. You need a UCI-compliant GUI like Cutechess or Arena to play against Asymptote. 5 | 6 | The UCI options currently supported (as reported by the `uci` command) are: 7 | ``` 8 | > uci 9 | < id name Asymptote 0.6.3 10 | < id author Maximilian Lupke 11 | < option name Hash type spin default 1 min 0 max 16384 12 | < option name Threads type spin default 1 min 1 max 64 13 | < option name ShowPVBoard type check default false 14 | < option name MoveOverhead type spin default 10 min 0 15 | < uciok 16 | ``` 17 | 18 | Options in general are case-insensitive. 19 | * `Hash`: size of the transposition table in megabytes. If it's not a power of two, it will be rounded down to the nearest power of two, i.e. 1000 -> 512. 20 | * `Threads`: number of cpu's to use. Whether there is any benefit in using logical (hyperthreading) rather than physical cores is unclear. 21 | * `ShowPvBoard`: if set to `true`, Asymptote will print the board at the end of the current pv (principal variation) each time the pv is updated. 22 | 23 | ## Rust version 24 | Asymptote is developed on the Rust stable channel. There is not guaranteed minimum working version, except the latest stable release. 25 | 26 | ## Rating 27 | Several versions of Asymptote have been tested by computer chess engine testers. 28 | 29 | | Version | CCRL 40/4 | CCRL 40/40 | 30 | | :------ | --------: | ---------: | 31 | | v0.6 | 2857 | 2816 | 32 | | v0.5 | 2652 | 2653 | 33 | | v0.4.2 | 2598 | 2582 | 34 | | v0.3 | 2488 | 2502 | 35 | | v0.2.0 | 2314 | 2314 | 36 | | v0.1.8 | 2176 | 2179 | 37 | 38 | (last updated July 11, 2019) 39 | 40 | Always up-to-date information can be found at the respective websites: 41 | * [CCRL 40/4](http://ccrl.chessdom.com/ccrl/404/) 42 | * [CCRL 40/40](http://ccrl.chessdom.com/ccrl/4040/) 43 | 44 | Thanks to every tester! 45 | -------------------------------------------------------------------------------- /src/history.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | use crate::movegen::*; 18 | use crate::search::*; 19 | use crate::types::SquareMap; 20 | 21 | const DECAY: i32 = 512; 22 | 23 | #[derive(Clone, Default)] 24 | pub struct History { 25 | piece_to: [SquareMap>; 2], 26 | } 27 | 28 | impl History { 29 | pub fn get_score(&self, white: bool, mov: Move) -> i16 { 30 | self.piece_to[white as usize][mov.from][mov.to] 31 | } 32 | 33 | fn change_score(&mut self, white: bool, depth: Depth, mov: Move) { 34 | // keep the sign of depth, but square the absolute value 35 | let change = depth * depth.abs(); 36 | let change = change.clamp(-400, 400); 37 | 38 | let entry = &mut self.piece_to[white as usize][mov.from][mov.to]; 39 | let decay = *entry as i32 * change.abs() as i32 / DECAY; 40 | *entry += 32 * change - decay as i16; 41 | } 42 | 43 | pub fn increase_score(&mut self, white: bool, mov: Move, depth: Depth) { 44 | let d = depth / INC_PLY; 45 | self.change_score(white, d, mov); 46 | } 47 | 48 | pub fn decrease_score(&mut self, white: bool, moves: &[Option], depth: Depth) { 49 | let d = depth / INC_PLY; 50 | 51 | for mov in moves { 52 | let mov = mov.unwrap(); 53 | self.change_score(white, -d, mov); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | #![warn(clippy::option_map_unwrap_or)] 18 | #![warn(clippy::option_map_unwrap_or_else)] 19 | #![warn(clippy::result_map_unwrap_or_else)] 20 | #![warn(clippy::single_match_else)] 21 | #![warn(clippy::unseparated_literal_suffix)] 22 | #![warn(clippy::used_underscore_binding)] 23 | #![warn(clippy::clone_on_ref_ptr)] 24 | #![warn(clippy::multiple_inherent_impl)] 25 | 26 | mod bench; 27 | mod bitboard; 28 | mod eval; 29 | #[cfg(feature = "fathom")] 30 | mod fathom; 31 | mod hash; 32 | mod history; 33 | mod magic; 34 | mod movegen; 35 | mod movepick; 36 | pub mod position; 37 | mod rand; 38 | mod repetitions; 39 | mod search; 40 | mod search_controller; 41 | mod time; 42 | mod tt; 43 | #[cfg(feature = "tune")] 44 | mod tune; 45 | mod types; 46 | mod uci; 47 | 48 | use crate::bench::run_benchmark; 49 | use crate::magic::initialize_magics; 50 | use crate::uci::*; 51 | 52 | fn main() { 53 | initialize_magics(); 54 | if std::env::args().nth(1) == Some(String::from("bench")) { 55 | run_benchmark( 56 | std::env::args() 57 | .nth(2) 58 | .and_then(|depth| depth.parse::().ok()) 59 | .unwrap_or(12), 60 | std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), 61 | ); 62 | return; 63 | } 64 | 65 | let mut uci = UCI::new(); 66 | uci.run(); 67 | } 68 | -------------------------------------------------------------------------------- /src/rand.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | pub struct Xoshiro { 19 | state: [u64; 4], 20 | } 21 | 22 | impl Xoshiro { 23 | pub fn new(state: [u64; 4]) -> Self { 24 | Xoshiro { state } 25 | } 26 | 27 | pub fn gen(&mut self) -> u64 { 28 | let result = self.state[1].wrapping_mul(5).rotate_left(7).wrapping_mul(9); 29 | let t = self.state[1] << 17; 30 | 31 | self.state[2] ^= self.state[0]; 32 | self.state[3] ^= self.state[1]; 33 | self.state[1] ^= self.state[2]; 34 | self.state[0] ^= self.state[3]; 35 | 36 | self.state[2] ^= t; 37 | self.state[3] = self.state[3].rotate_left(45); 38 | 39 | result 40 | } 41 | 42 | pub fn gen_sparse(&mut self) -> u64 { 43 | if N == 0 { 44 | return 0; 45 | } 46 | 47 | let mut result = self.gen(); 48 | for _ in 1..N { 49 | result &= self.gen(); 50 | } 51 | 52 | result 53 | } 54 | 55 | pub fn fill(&mut self, v: &mut T) { 56 | v.fill(self) 57 | } 58 | } 59 | 60 | pub trait Fill { 61 | fn fill(&mut self, rand: &mut Xoshiro); 62 | } 63 | 64 | impl Fill for u64 { 65 | fn fill(&mut self, rand: &mut Xoshiro) { 66 | *self = rand.gen(); 67 | } 68 | } 69 | 70 | impl Fill for [T; N] { 71 | fn fill(&mut self, rand: &mut Xoshiro) { 72 | self.iter_mut().for_each(|v| v.fill(rand)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: rust 3 | services: docker 4 | sudo: required 5 | env: 6 | global: 7 | - CRATE_NAME=asymptote 8 | - CPU= 9 | - FEATURES= 10 | matrix: 11 | include: 12 | - env: TARGET=x86_64-unknown-linux-gnu FEATURES=-popcnt NAME=linux-64bit 13 | - env: TARGET=x86_64-unknown-linux-gnu FEATURES=+popcnt NAME=linux-64bit-popcount 14 | - env: TARGET=x86_64-unknown-linux-gnu CPU=broadwell NAME=linux-64bit-broadwell 15 | - env: TARGET=x86_64-apple-darwin FEATURES=-popcnt NAME=osx-64bit 16 | os: osx 17 | - env: TARGET=x86_64-apple-darwin FEATURES=+popcnt NAME=osx-64bit-popcount 18 | os: osx 19 | - env: TARGET=x86_64-unknown-linux-gnu FEATURES=-popcnt NAME=linux-64bit-nightly 20 | rust: nightly 21 | - env: TARGET=x86_64-unknown-linux-gnu FEATURES=+popcnt NAME=linux-64bit-popcount-nightly 22 | rust: nightly 23 | before_install: 24 | - set -e 25 | - rustup self update 26 | install: 27 | - sh ci/install.sh 28 | - source ~/.cargo/env || true 29 | script: 30 | - bash ci/script.sh 31 | after_script: set +e 32 | before_deploy: 33 | - sh ci/before_deploy.sh 34 | deploy: 35 | api_key: 36 | secure: pSVYc9d155XgELoi1MidxD7fE73ddNoYhwAEYCIWNjcTQbzn1Ppq9Hl/fGY6radvAOwmJmi3ynJzPknAbvMpJthbTI+53kH8vjOve234UeHkWZp5YLqXWTyXtwH5zxrIg/wqWT5oJNRfUGgPAiSg5lZfkYiekp6gXDwFeCBI8bbtsiT2NuGPI6NcJbz0ypr+zTBgGQaopomIDM/jh++CaVweQlVQB0qcGM9mT3sjxHLgSMzFh57Ozy4Rl8b5onheDQLIjLT30uccokdFCG7Zvzj3F0izenikiPyLnAYCVz6HWQlnuAw6hOwpNBu8Rc/gX/4b3odBHL3fZmAMUg81lJ+4OqChPXnNzRcFMXi9zJ12yLoZh865vCqslf6GYVOF3Rmmf6cZSMVpTwf6N9IMwdWzra6GhKcknHaAY+n33ibV1m8NYH6R95VS6iUNpSbd4K12jOJXis7YFQqxAt6gs6qL7zOsYH0i4maAWkSaRW7NxmpJb/RWomMQO224T5qjsTi1/7OnbMQwUdFu1f5E1rUp8z354Pl7wHlsHrIy8Ykh08t0T6Sj+iJuXg9WbkhIvoGNbUOIR7qAZimdmdX4TSvAqcELsNGm+x6EijvlovFWt6gN3LdyJTFYeXbZbGRkTVXMLVrhNT8b33N2Z13hUPxSNxTjU+8Sf0O78XESNV8= 37 | file_glob: true 38 | file: "$CRATE_NAME-$TRAVIS_TAG-$NAME.*" 39 | on: 40 | condition: "$TRAVIS_RUST_VERSION = stable" 41 | tags: true 42 | provider: releases 43 | skip_cleanup: true 44 | cache: cargo 45 | before_cache: 46 | - chmod -R a+r $HOME/.cargo 47 | branches: 48 | only: 49 | - "/^v\\d+\\.\\d+\\.\\d+.*$/" 50 | - "/^v\\d+\\.\\d+.*$/" 51 | - master 52 | notifications: 53 | email: 54 | on_success: never 55 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | use std::mem; 18 | 19 | use crate::bitboard::Square; 20 | use crate::rand::{Fill, Xoshiro}; 21 | 22 | pub struct SquareMap { 23 | data: [T; 64], 24 | } 25 | 26 | impl SquareMap { 27 | pub const fn from_array(data: [T; 64]) -> SquareMap { 28 | SquareMap { data } 29 | } 30 | } 31 | 32 | impl Copy for SquareMap {} 33 | 34 | impl Clone for SquareMap { 35 | fn clone(&self) -> Self { 36 | SquareMap { 37 | data: self.data.clone(), 38 | } 39 | } 40 | } 41 | 42 | impl Fill for SquareMap 43 | where 44 | T: Fill, 45 | { 46 | fn fill(&mut self, rng: &mut Xoshiro) { 47 | self.data.fill(rng) 48 | } 49 | } 50 | 51 | impl Default for SquareMap { 52 | fn default() -> Self { 53 | SquareMap { 54 | data: [Default::default(); 64], 55 | } 56 | } 57 | } 58 | 59 | impl From<[T; 64]> for SquareMap { 60 | fn from(data: [T; 64]) -> SquareMap { 61 | SquareMap { data } 62 | } 63 | } 64 | 65 | impl std::ops::Index for SquareMap { 66 | type Output = T; 67 | 68 | fn index(&self, sq: Square) -> &Self::Output { 69 | unsafe { 70 | let i = mem::transmute::(sq); 71 | self.data.get_unchecked(i as usize) 72 | } 73 | } 74 | } 75 | 76 | impl std::ops::IndexMut for SquareMap { 77 | fn index_mut(&mut self, sq: Square) -> &mut Self::Output { 78 | unsafe { 79 | let i = mem::transmute::(sq); 80 | self.data.get_unchecked_mut(i as usize) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/repetitions.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | use crate::hash::Hash; 18 | use crate::search::Ply; 19 | 20 | #[derive(Clone)] 21 | pub struct Repetitions { 22 | past_positions: Vec>, 23 | index: usize, 24 | } 25 | 26 | impl Repetitions { 27 | pub fn new(capacity: usize) -> Self { 28 | let mut past_positions = Vec::with_capacity(capacity); 29 | for _ in 0..capacity { 30 | past_positions.push(Vec::with_capacity(100)); 31 | } 32 | 33 | Repetitions { 34 | past_positions, 35 | index: 0, 36 | } 37 | } 38 | 39 | pub fn clear(&mut self) { 40 | self.past_positions.iter_mut().for_each(Vec::clear); 41 | self.index = 0; 42 | } 43 | 44 | pub fn irreversible_move(&mut self) { 45 | self.index += 1; 46 | if self.index >= self.past_positions.len() { 47 | self.past_positions.push(Vec::with_capacity(100)); 48 | } 49 | } 50 | 51 | pub fn push_position(&mut self, hash: Hash) { 52 | self.past_positions[self.index].push(hash); 53 | } 54 | 55 | pub fn pop_position(&mut self) { 56 | self.past_positions[self.index].pop(); 57 | if self.past_positions[self.index].is_empty() { 58 | self.index -= 1; 59 | } 60 | } 61 | 62 | pub fn has_repeated(&self, ply: Ply) -> bool { 63 | let current = self.past_positions[self.index].last().unwrap(); 64 | let repeated_since_root = self.past_positions[self.index] 65 | .iter() 66 | .rev() 67 | .take(ply as usize + 1) 68 | .step_by(2) 69 | .skip(1) 70 | .any(|h| h == current); 71 | 72 | if repeated_since_root { 73 | return true; 74 | } 75 | 76 | let repeated_twice_before_root = self.past_positions[self.index] 77 | .iter() 78 | .rev() 79 | .skip(ply as usize) 80 | .skip(ply as usize % 2) // skip once more if not root side-to-move 81 | .step_by(2) 82 | .filter(|&h| h == current) 83 | .nth(1) 84 | .is_some(); 85 | 86 | repeated_twice_before_root 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Based on the "trust" template v0.1.2 2 | # https://github.com/japaric/trust/tree/v0.1.2 3 | 4 | environment: 5 | global: 6 | # TODO This is the Rust channel that build jobs will use by default but can be 7 | # overridden on a case by case basis down below 8 | RUST_VERSION: stable 9 | 10 | # TODO Update this to match the name of your project. 11 | CRATE_NAME: asymptote 12 | FEATURES: '' 13 | CPU: '' 14 | 15 | # TODO These are all the build jobs. Adjust as necessary. Comment out what you 16 | # don't need 17 | matrix: 18 | # MinGW 19 | - TARGET: x86_64-pc-windows-gnu 20 | FEATURES: -popcnt 21 | NAME: windows-gnu-64bit 22 | - TARGET: x86_64-pc-windows-gnu 23 | FEATURES: +popcnt 24 | NAME: windows-gnu-64bit-popcount 25 | 26 | # MSVC 27 | - TARGET: x86_64-pc-windows-msvc 28 | FEATURES: -popcnt 29 | NAME: windows-msvc-64bit 30 | - TARGET: x86_64-pc-windows-msvc 31 | FEATURES: +popcnt 32 | NAME: windows-msvc-64bit-popcount 33 | - TARGET: x86_64-pc-windows-msvc 34 | CPU: broadwell 35 | NAME: windows-msvc-64bit-broadwell 36 | 37 | # Testing other channels 38 | - TARGET: x86_64-pc-windows-gnu 39 | RUST_VERSION: nightly 40 | # The following line should not be necessary as nightly builds don't get 41 | # deployed, but just to make sure. 42 | NAME: windows-gnu-64bit-nightly 43 | - TARGET: x86_64-pc-windows-msvc 44 | RUST_VERSION: nightly 45 | # The following line should not be necessary as nightly builds don't get 46 | # deployed, but just to make sure. 47 | NAME: windows-msvc-64bit-nightly 48 | 49 | install: 50 | - ps: >- 51 | If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') { 52 | $Env:PATH += ';C:\msys64\mingw64\bin' 53 | } 54 | - curl -sSf -o rustup-init.exe https://win.rustup.rs/ 55 | - rustup-init.exe -y --default-host %TARGET% --default-toolchain %RUST_VERSION% 56 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 57 | - set RUSTFLAGS=-C target-feature=%FEATURES% -C target-cpu=%CPU% 58 | - rustc -Vv 59 | - cargo -V 60 | 61 | # TODO This is the "test phase", tweak it as you see fit 62 | test_script: 63 | # we don't run the "test phase" when doing deploys 64 | - if [%APPVEYOR_REPO_TAG%]==[false] ( 65 | cargo build --target %TARGET% && 66 | cargo build --target %TARGET% --release && 67 | cargo test --target %TARGET% && 68 | cargo test --target %TARGET% --release 69 | ) 70 | 71 | before_deploy: 72 | # TODO Update this to build the artifacts that matter to you 73 | - cargo rustc --target %TARGET% --release -- -C lto -C target-feature=%FEATURES% -C target-cpu=%CPU% 74 | - ps: ci\before_deploy.ps1 75 | 76 | deploy: 77 | artifact: /.*\.zip/ 78 | # TODO update `auth_token.secure` 79 | # - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new 80 | # - Encrypt it. Go to https://ci.appveyor.com/tools/encrypt 81 | # - Paste the output down here 82 | auth_token: 83 | secure: O6KD5XQDiJ/RUauyhmUVSlmJ0la1Ps+08aRl13AtFrJS/Vi72oTygOiisp1QCHsX 84 | description: '' 85 | on: 86 | # TODO Here you can pick which targets will generate binary releases 87 | # In this example, there are some targets that are tested using the stable 88 | # and nightly channels. This condition makes sure there is only one release 89 | # for such targets and that's generated using the stable channel 90 | RUST_VERSION: stable 91 | appveyor_repo_tag: true 92 | provider: GitHub 93 | 94 | cache: 95 | - C:\Users\appveyor\.cargo\registry 96 | - target 97 | 98 | branches: 99 | only: 100 | # Release tags 101 | - /^v\d+\.\d+\.\d+.*$/ 102 | - /^v\d+\.\d+.*$/ 103 | - master 104 | 105 | notifications: 106 | - provider: Email 107 | on_build_success: false 108 | 109 | # Building is done in the test phase, so we disable Appveyor's build phase. 110 | build: false 111 | -------------------------------------------------------------------------------- /src/bench.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | // Taken from Ethereal 19 | const BENCH_POSITIONS: [&str; 50] = [ 20 | "r3k2r/2pb1ppp/2pp1q2/p7/1nP1B3/1P2P3/P2N1PPP/R2QK2R w KQkq a6 0 14", 21 | "4rrk1/2p1b1p1/p1p3q1/4p3/2P2n1p/1P1NR2P/PB3PP1/3R1QK1 b - - 2 24", 22 | "r3qbrk/6p1/2b2pPp/p3pP1Q/PpPpP2P/3P1B2/2PB3K/R5R1 w - - 16 42", 23 | "6k1/1R3p2/6p1/2Bp3p/3P2q1/P7/1P2rQ1K/5R2 b - - 4 44", 24 | "8/8/1p2k1p1/3p3p/1p1P1P1P/1P2PK2/8/8 w - - 3 54", 25 | "7r/2p3k1/1p1p1qp1/1P1Bp3/p1P2r1P/P7/4R3/Q4RK1 w - - 0 36", 26 | "r1bq1rk1/pp2b1pp/n1pp1n2/3P1p2/2P1p3/2N1P2N/PP2BPPP/R1BQ1RK1 b - - 2 10", 27 | "3r3k/2r4p/1p1b3q/p4P2/P2Pp3/1B2P3/3BQ1RP/6K1 w - - 3 87", 28 | "2r4r/1p4k1/1Pnp4/3Qb1pq/8/4BpPp/5P2/2RR1BK1 w - - 0 42", 29 | "4q1bk/6b1/7p/p1p4p/PNPpP2P/KN4P1/3Q4/4R3 b - - 0 37", 30 | "2q3r1/1r2pk2/pp3pp1/2pP3p/P1Pb1BbP/1P4Q1/R3NPP1/4R1K1 w - - 2 34", 31 | "1r2r2k/1b4q1/pp5p/2pPp1p1/P3Pn2/1P1B1Q1P/2R3P1/4BR1K b - - 1 37", 32 | "r3kbbr/pp1n1p1P/3ppnp1/q5N1/1P1pP3/P1N1B3/2P1QP2/R3KB1R b KQkq b3 0 17", 33 | "8/6pk/2b1Rp2/3r4/1R1B2PP/P5K1/8/2r5 b - - 16 42", 34 | "1r4k1/4ppb1/2n1b1qp/pB4p1/1n1BP1P1/7P/2PNQPK1/3RN3 w - - 8 29", 35 | "8/p2B4/PkP5/4p1pK/4Pb1p/5P2/8/8 w - - 29 68", 36 | "3r4/ppq1ppkp/4bnp1/2pN4/2P1P3/1P4P1/PQ3PBP/R4K2 b - - 2 20", 37 | "5rr1/4n2k/4q2P/P1P2n2/3B1p2/4pP2/2N1P3/1RR1K2Q w - - 1 49", 38 | "1r5k/2pq2p1/3p3p/p1pP4/4QP2/PP1R3P/6PK/8 w - - 1 51", 39 | "q5k1/5ppp/1r3bn1/1B6/P1N2P2/BQ2P1P1/5K1P/8 b - - 2 34", 40 | "r1b2k1r/5n2/p4q2/1ppn1Pp1/3pp1p1/NP2P3/P1PPBK2/1RQN2R1 w - - 0 22", 41 | "r1bqk2r/pppp1ppp/5n2/4b3/4P3/P1N5/1PP2PPP/R1BQKB1R w KQkq - 0 5", 42 | "r1bqr1k1/pp1p1ppp/2p5/8/3N1Q2/P2BB3/1PP2PPP/R3K2n b Q - 1 12", 43 | "r1bq2k1/p4r1p/1pp2pp1/3p4/1P1B3Q/P2B1N2/2P3PP/4R1K1 b - - 2 19", 44 | "r4qk1/6r1/1p4p1/2ppBbN1/1p5Q/P7/2P3PP/5RK1 w - - 2 25", 45 | "r7/6k1/1p6/2pp1p2/7Q/8/p1P2K1P/8 w - - 0 32", 46 | "r3k2r/ppp1pp1p/2nqb1pn/3p4/4P3/2PP4/PP1NBPPP/R2QK1NR w KQkq - 1 5", 47 | "3r1rk1/1pp1pn1p/p1n1q1p1/3p4/Q3P3/2P5/PP1NBPPP/4RRK1 w - - 0 12", 48 | "5rk1/1pp1pn1p/p3Brp1/8/1n6/5N2/PP3PPP/2R2RK1 w - - 2 20", 49 | "8/1p2pk1p/p1p1r1p1/3n4/8/5R2/PP3PPP/4R1K1 b - - 3 27", 50 | "8/4pk2/1p1r2p1/p1p4p/Pn5P/3R4/1P3PP1/4RK2 w - - 1 33", 51 | "8/5k2/1pnrp1p1/p1p4p/P6P/4R1PK/1P3P2/4R3 b - - 1 38", 52 | "8/8/1p1kp1p1/p1pr1n1p/P6P/1R4P1/1P3PK1/1R6 b - - 15 45", 53 | "8/8/1p1k2p1/p1prp2p/P2n3P/6P1/1P1R1PK1/4R3 b - - 5 49", 54 | "8/8/1p4p1/p1p2k1p/P2npP1P/4K1P1/1P6/3R4 w - - 6 54", 55 | "8/8/1p4p1/p1p2k1p/P2n1P1P/4K1P1/1P6/6R1 b - - 6 59", 56 | "8/5k2/1p4p1/p1pK3p/P2n1P1P/6P1/1P6/4R3 b - - 14 63", 57 | "8/1R6/1p1K1kp1/p6p/P1p2P1P/6P1/1Pn5/8 w - - 0 67", 58 | "1rb1rn1k/p3q1bp/2p3p1/2p1p3/2P1P2N/PP1RQNP1/1B3P2/4R1K1 b - - 4 23", 59 | "4rrk1/pp1n1pp1/q5p1/P1pP4/2n3P1/7P/1P3PB1/R1BQ1RK1 w - - 3 22", 60 | "r2qr1k1/pb1nbppp/1pn1p3/2ppP3/3P4/2PB1NN1/PP3PPP/R1BQR1K1 w - - 4 12", 61 | "2r2k2/8/4P1R1/1p6/8/P4K1N/7b/2B5 b - - 0 55", 62 | "6k1/5pp1/8/2bKP2P/2P5/p4PNb/B7/8 b - - 1 44", 63 | "2rqr1k1/1p3p1p/p2p2p1/P1nPb3/2B1P3/5P2/1PQ2NPP/R1R4K w - - 3 25", 64 | "r1b2rk1/p1q1ppbp/6p1/2Q5/8/4BP2/PPP3PP/2KR1B1R b - - 2 14", 65 | "6r1/5k2/p1b1r2p/1pB1p1p1/1Pp3PP/2P1R1K1/2P2P2/3R4 w - - 1 36", 66 | "rnbqkb1r/pppppppp/5n2/8/2PP4/8/PP2PPPP/RNBQKBNR b KQkq c3 0 2", 67 | "2rr2k1/1p4bp/p1q1p1p1/4Pp1n/2PB4/1PN3P1/P3Q2P/2RR2K1 w - f6 0 20", 68 | "3br1k1/p1pn3p/1p3n2/5pNq/2P1p3/1PN3PP/P2Q1PB1/4R1K1 w - - 0 23", 69 | "2r2b2/5p2/5k2/p1r1pP2/P2pB3/1P3P2/K1P3R1/7R w - - 23 93", 70 | ]; 71 | 72 | use std::sync; 73 | use std::time; 74 | 75 | use crate::position::Position; 76 | use crate::search::Ply; 77 | use crate::search_controller::SearchController; 78 | use crate::time::TimeControl; 79 | 80 | pub fn run_benchmark(ply: Ply, abort: sync::Arc) { 81 | let tc = TimeControl::FixedDepth(ply); 82 | 83 | let start = time::Instant::now(); 84 | let mut nodes = 0; 85 | for (i, &fen) in BENCH_POSITIONS.iter().enumerate() { 86 | println!("Position {:>2}: {}", i + 1, fen); 87 | let pos = Position::from(fen); 88 | let mut search = SearchController::new(pos, sync::Arc::clone(&abort)); 89 | search.resize_tt(17); // 8 MB hash table 90 | search.set_time_control(tc); 91 | search.get_best_move(); 92 | nodes += search.get_node_count(); 93 | } 94 | let duration = time::Instant::now() - start; 95 | let ms = 1000 * duration.as_secs() + u64::from(duration.subsec_millis()); 96 | println!("Nodes: {}", nodes); 97 | println!("Time: {} ms", ms); 98 | println!("NPS: {}", 1000 * nodes / ms); 99 | } 100 | -------------------------------------------------------------------------------- /fathom/README.md: -------------------------------------------------------------------------------- 1 | Fathom 2 | ====== 3 | 4 | Fathom is a stand-alone Syzygy tablebase probing tool. The aims of Fathom 5 | are: 6 | 7 | * To make it easy to integrate the Syzygy tablebases into existing chess 8 | engines; 9 | * To make it easy to create stand-alone applications that use the Syzygy 10 | tablebases; 11 | 12 | Fathom is compilable under either C99 or C++ and supports a variety of 13 | platforms, including at least Windows, Linux, and MacOS. 14 | 15 | Tool 16 | ---- 17 | 18 | Fathom includes a stand-alone command-line syzygy probing tool `fathom`. To 19 | probe a position, simply run the command: 20 | 21 | fathom --path= "FEN-string" 22 | 23 | The tool will print out a PGN representation of the probe result, including: 24 | 25 | * Result: "1-0" (white wins), "1/2-1/2" (draw), or "0-1" (black wins) 26 | * The Win-Draw-Loss (WDL) value for the next move: "Win", "Draw", "Loss", 27 | "CursedWin" (win but 50-move draw) or "BlessedLoss" (loss but 50-move draw) 28 | * The Distance-To-Zero (DTZ) value (in plys) for the next move 29 | * WinningMoves: The list of all winning moves 30 | * DrawingMoves: The list of all drawing moves 31 | * LosingMoves: The list of all losing moves 32 | * A pseudo "principal variation" of Syzygy vs. Syzygy for the input position. 33 | 34 | For more information, run the following command: 35 | 36 | fathom --help 37 | 38 | Programming API 39 | --------------- 40 | 41 | Fathom provides a simple API. Following are the main function calls: 42 | 43 | * `tb_init` initializes the tablebases. 44 | * `tb_free` releases any resources allocated by Fathom. 45 | * `tb_probe_wdl` probes the Win-Draw-Loss (WDL) table for a given position. 46 | * `tb_probe_root` probes the Distance-To-Zero (DTZ) table for the given 47 | position. It returns a recommended move, and also a list of unsigned 48 | integers, each one encoding a possible move and its DTZ and WDL values. 49 | * `tb_probe_root_dtz` probes the Distance-To-Zero (DTZ) at the root position. 50 | It returns a score and a rank for each possible move. 51 | * `tb_probe_root_wdl` probes the Win-Draw-Loss (WDL) at the root position. 52 | it returns a score and a rank for each possible move. 53 | 54 | Fathom does not require the callee to provide any additional functionality 55 | (e.g. move generation). A simple set of chess-related functions including move 56 | generation is provided in file tbchess.c. However, chess engines can opt to 57 | replace some of this functionality for better performance (see below). 58 | 59 | Chess Engines 60 | ------------- 61 | 62 | Chess engines can use `tb_probe_wdl` to get the WDL value during search. 63 | This function is thread safe (unless TB_NO_THREADS is set). The various 64 | "probe_root" functions are intended for probing only at the root node 65 | and are not thread-safe. 66 | 67 | Chess engines can opt for a tighter integration of Fathom by configuring 68 | `tbconfig.h`. Specifically, the chess engines can define `TB_*_ATTACKS` 69 | macros that replace the default attack functions with the engine's own definitions, 70 | avoiding duplication of functionality. 71 | 72 | History and Credits 73 | ------------------- 74 | 75 | The Syzygy tablebases were created by Ronald de Man. This original version of Fathom 76 | (https://github.com/basil00/Fathom) combined probing code from Ronald de Man, originally written for 77 | Stockfish, with chess-related functions and other support code from Basil Falcinelli. 78 | This repository was originaly a fork of that codebase, with additional modifications 79 | by Jon Dart. 80 | 81 | However, the current release of Fathom is not derived directly from the probing code 82 | written for Stockfish, but from tbprobe.c, which is a component of the Cfish chess engine 83 | (https://github.com/syzygy1/Cfish), a Stockfish derivative. tbprobe.c was written 84 | by Ronald de Man and released for unrestricted distribution and use. 85 | 86 | Fathom replaces the Cfish board representation and move generation code used in tbprobe.c 87 | with simpler code from the original Fathom source by Basil. The code has been reorganized 88 | so that tbchess.c contains all move generation and most chess-related typedefs and 89 | functions, while tbprobe.c contains all the tablebase probing code. The code replacement and 90 | reorganization was done by Jon Dart. 91 | 92 | License 93 | ------- 94 | 95 | (C) 2013-2015 Ronald de Man (original code) 96 | (C) 2015 basil (new modifications) 97 | (C) 2016-2019 Jon Dart (additional modifications) 98 | 99 | This version of Fathom is released under the MIT License: 100 | 101 | Permission is hereby granted, free of charge, to any person obtaining a copy of 102 | this software and associated documentation files (the "Software"), to deal in 103 | the Software without restriction, including without limitation the rights to 104 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 105 | of the Software, and to permit persons to whom the Software is furnished to do 106 | so, subject to the following conditions: 107 | 108 | The above copyright notice and this permission notice shall be included in all 109 | copies or substantial portions of the Software. 110 | 111 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 112 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 113 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 114 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 115 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 116 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 117 | SOFTWARE. 118 | 119 | -------------------------------------------------------------------------------- /fathom/src/tbconfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * tbconfig.h 3 | * (C) 2015 basil, all rights reserved, 4 | * Modifications Copyright 2016-2017 Jon Dart 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef TBCONFIG_H 26 | #define TBCONFIG_H 27 | 28 | /****************************************************************************/ 29 | /* BUILD CONFIG: */ 30 | /****************************************************************************/ 31 | 32 | /* 33 | * Define TB_CUSTOM_POP_COUNT to override the internal popcount 34 | * implementation. To do this supply a macro or function definition 35 | * here: 36 | */ 37 | /* #define TB_CUSTOM_POP_COUNT(x) */ 38 | 39 | /* 40 | * Define TB_CUSTOM_LSB to override the internal lsb 41 | * implementation. To do this supply a macro or function definition 42 | * here: 43 | */ 44 | /* #define TB_CUSTOM_LSB(x) */ 45 | 46 | /* 47 | * Define TB_NO_STDINT if you do not want to use or it is not 48 | * available. 49 | */ 50 | /* #define TB_NO_STDINT */ 51 | 52 | /* 53 | * Define TB_NO_STDBOOL if you do not want to use or it is not 54 | * available or unnecessary (e.g. C++). 55 | */ 56 | /* #define TB_NO_STDBOOL */ 57 | 58 | /* 59 | * Define TB_NO_THREADS if your program is not multi-threaded. 60 | */ 61 | /* #define TB_NO_THREADS */ 62 | 63 | /* 64 | * Define TB_NO_HELPER_API if you do not need the helper API. 65 | */ 66 | /* #define TB_NO_HELPER_API */ 67 | 68 | /* 69 | * Define TB_NO_HW_POP_COUNT if there is no hardware popcount instruction. 70 | * 71 | * Note: if defined, TB_CUSTOM_POP_COUNT is always used in preference 72 | * to any built-in popcount functions. 73 | * 74 | * If no custom popcount function is defined, and if the following 75 | * define is not set, the code will attempt to use an available hardware 76 | * popcnt (currently supported on x86_64 architecture only) and otherwise 77 | * will fall back to a software implementation. 78 | */ 79 | /* #define TB_NO_HW_POP_COUNT */ 80 | 81 | /***************************************************************************/ 82 | /* SCORING CONSTANTS */ 83 | /***************************************************************************/ 84 | /* 85 | * Fathom can produce scores for tablebase moves. These depend on the 86 | * value of a pawn, and the magnitude of mate scores. The following 87 | * constants are representative values but will likely need 88 | * modification to adapt to an engine's own internal score values. 89 | */ 90 | #define TB_VALUE_PAWN 100 /* value of pawn in endgame */ 91 | #define TB_VALUE_MATE 32000 92 | #define TB_VALUE_INFINITE 32767 /* value above all normal score values */ 93 | #define TB_VALUE_DRAW 0 94 | #define TB_MAX_MATE_PLY 255 95 | 96 | /***************************************************************************/ 97 | /* ENGINE INTEGRATION CONFIG */ 98 | /***************************************************************************/ 99 | 100 | /* 101 | * If you are integrating tbprobe into an engine, you can replace some of 102 | * tbprobe's built-in functionality with that already provided by the engine. 103 | * This is OPTIONAL. If no definition are provided then tbprobe will use its 104 | * own internal defaults. That said, for engines it is generally a good idea 105 | * to avoid redundancy. 106 | */ 107 | 108 | /* 109 | * Define TB_KING_ATTACKS(square) to return the king attacks bitboard for a 110 | * king at `square'. 111 | */ 112 | /* #define TB_KING_ATTACKS(square) */ 113 | 114 | /* 115 | * Define TB_KNIGHT_ATTACKS(square) to return the knight attacks bitboard for 116 | * a knight at `square'. 117 | */ 118 | /* #define TB_KNIGHT_ATTACKS(square) */ 119 | 120 | /* 121 | * Define TB_ROOK_ATTACKS(square, occ) to return the rook attacks bitboard 122 | * for a rook at `square' assuming the given `occ' occupancy bitboard. 123 | */ 124 | /* #define TB_ROOK_ATTACKS(square, occ) */ 125 | 126 | /* 127 | * Define TB_BISHOP_ATTACKS(square, occ) to return the bishop attacks bitboard 128 | * for a bishop at `square' assuming the given `occ' occupancy bitboard. 129 | */ 130 | /* #define TB_BISHOP_ATTACKS(square, occ) */ 131 | 132 | /* 133 | * Define TB_QUEEN_ATTACKS(square, occ) to return the queen attacks bitboard 134 | * for a queen at `square' assuming the given `occ' occupancy bitboard. 135 | * NOTE: If no definition is provided then tbprobe will use: 136 | * TB_ROOK_ATTACKS(square, occ) | TB_BISHOP_ATTACKS(square, occ) 137 | */ 138 | /* #define TB_QUEEN_ATTACKS(square, occ) */ 139 | 140 | /* 141 | * Define TB_PAWN_ATTACKS(square, color) to return the pawn attacks bitboard 142 | * for a `color' pawn at `square'. 143 | * NOTE: This definition must work for pawns on ranks 1 and 8. For example, 144 | * a white pawn on e1 attacks d2 and f2. A black pawn on e1 attacks 145 | * nothing. Etc. 146 | * NOTE: This definition must not include en passant captures. 147 | */ 148 | /* #define TB_PAWN_ATTACKS(square, color) */ 149 | 150 | #endif 151 | -------------------------------------------------------------------------------- /src/time.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | use std::cmp; 19 | use std::sync; 20 | use std::time; 21 | 22 | use crate::eval::Score; 23 | use crate::position::Position; 24 | use crate::search::{Ply, MAX_PLY}; 25 | 26 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 27 | pub enum TimeControl { 28 | Infinite, 29 | FixedMillis(u64), 30 | FixedDepth(Ply), 31 | FixedNodes(u64), 32 | Variable { 33 | wtime: u64, 34 | btime: u64, 35 | winc: Option, 36 | binc: Option, 37 | movestogo: Option, 38 | }, 39 | } 40 | 41 | #[derive(Clone)] 42 | pub struct TimeManager { 43 | started_at: time::Instant, 44 | control: TimeControl, 45 | searching_for_white: bool, 46 | pub abort: sync::Arc, 47 | force_stop: bool, 48 | move_overhead: u64, 49 | 50 | dynamic: DynamicTimeManagement, 51 | 52 | times_checked: u64, 53 | } 54 | 55 | #[derive(Copy, Clone, Default)] 56 | struct DynamicTimeManagement { 57 | maximum: u64, 58 | initial: u64, 59 | target: u64, 60 | } 61 | 62 | impl TimeManager { 63 | pub fn new( 64 | position: &Position, 65 | control: TimeControl, 66 | move_overhead: u64, 67 | abort: sync::Arc, 68 | ) -> TimeManager { 69 | let mut tm = TimeManager { 70 | started_at: time::Instant::now(), 71 | control, 72 | searching_for_white: position.white_to_move, 73 | abort, 74 | force_stop: false, 75 | move_overhead, 76 | dynamic: DynamicTimeManagement::default(), 77 | times_checked: 0, 78 | }; 79 | 80 | tm.update(position, control); 81 | tm 82 | } 83 | 84 | pub fn update(&mut self, position: &Position, control: TimeControl) { 85 | self.force_stop = false; 86 | self.started_at = time::Instant::now(); 87 | self.control = control; 88 | self.searching_for_white = position.white_to_move; 89 | self.times_checked = 0; 90 | self.abort.store(false, sync::atomic::Ordering::SeqCst); 91 | 92 | if let TimeControl::Variable { 93 | wtime, 94 | btime, 95 | winc, 96 | binc, 97 | movestogo, 98 | } = control 99 | { 100 | let time = if self.searching_for_white { 101 | wtime 102 | } else { 103 | btime 104 | }; 105 | let inc = if self.searching_for_white { winc } else { binc }.unwrap_or(0); 106 | 107 | let initial = cmp::min(time, time / movestogo.unwrap_or(40) + inc); 108 | let target = initial; 109 | let maximum = target + (time - target) / 4; 110 | 111 | self.dynamic.initial = initial; 112 | self.dynamic.target = target; 113 | self.dynamic.maximum = maximum; 114 | } 115 | } 116 | 117 | pub fn elapsed_millis(&self) -> u64 { 118 | let duration = time::Instant::now() - self.started_at; 119 | duration.as_millis() as u64 120 | } 121 | 122 | pub fn check_for_stop(&mut self) { 123 | if self.abort.load(sync::atomic::Ordering::Relaxed) { 124 | self.force_stop = true; 125 | } 126 | } 127 | 128 | pub fn start_another_iteration(&mut self, ply: Ply) -> bool { 129 | self.check_for_stop(); 130 | if ply == MAX_PLY || self.force_stop { 131 | return false; 132 | } 133 | 134 | let start_another = match self.control { 135 | TimeControl::Infinite => true, 136 | TimeControl::FixedMillis(millis) => { 137 | let elapsed = self.elapsed_millis(); 138 | elapsed + self.move_overhead <= millis 139 | } 140 | TimeControl::FixedDepth(stop_depth) => ply <= stop_depth, 141 | TimeControl::FixedNodes(_) => true, // handled by should_stop 142 | TimeControl::Variable { .. } => { 143 | let elapsed = self.elapsed_millis(); 144 | elapsed + self.move_overhead <= self.dynamic.target / 2 145 | } 146 | }; 147 | 148 | if !start_another { 149 | self.abort.store(true, sync::atomic::Ordering::Relaxed); 150 | } 151 | start_another 152 | } 153 | 154 | pub fn should_stop(&mut self) -> bool { 155 | self.times_checked += 1; 156 | if self.times_checked & 0x7F == 0 { 157 | self.check_for_stop(); 158 | } 159 | 160 | if self.force_stop { 161 | return true; 162 | } 163 | 164 | let stop = match self.control { 165 | TimeControl::Infinite => false, 166 | TimeControl::FixedMillis(millis) => { 167 | if self.times_checked & 0x7F == 0 { 168 | let elapsed = self.elapsed_millis(); 169 | elapsed + self.move_overhead > millis 170 | } else { 171 | false 172 | } 173 | } 174 | TimeControl::FixedDepth(_) => false, // handled by start_another_iteration 175 | TimeControl::FixedNodes(nodes) => self.times_checked >= nodes, 176 | TimeControl::Variable { .. } => { 177 | if self.times_checked & 0x7F == 0 { 178 | let elapsed = self.elapsed_millis(); 179 | elapsed + self.move_overhead >= self.dynamic.maximum 180 | } else { 181 | false 182 | } 183 | } 184 | }; 185 | 186 | if stop { 187 | self.abort.store(true, sync::atomic::Ordering::Relaxed); 188 | } 189 | stop 190 | } 191 | 192 | pub fn fail_low(&mut self, diff: Score) { 193 | if diff > -25 { 194 | return; 195 | } 196 | 197 | if diff > -75 { 198 | self.dynamic.target = cmp::min(self.dynamic.maximum, self.dynamic.target * 5 / 4); 199 | } 200 | 201 | self.dynamic.target = cmp::min(self.dynamic.maximum, self.dynamic.target * 3 / 2); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/fathom.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | use std::{ 19 | convert::{TryFrom, TryInto}, 20 | ffi::CString, 21 | path::Path, 22 | }; 23 | 24 | // pub const CASTLE_WHITE_KINGSIDE: u8 = 0x1; 25 | // pub const CASTLE_WHITE_QUEENSIDE: u8 = 0x2; 26 | // pub const CASTLE_BLACK_KINGSIDE: u8 = 0x4; 27 | // pub const CASTLE_BLACK_QUEENSIDE: u8 = 0x8; 28 | 29 | #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] 30 | pub struct BoardState { 31 | pub white: u64, 32 | pub black: u64, 33 | pub kings: u64, 34 | pub queens: u64, 35 | pub rooks: u64, 36 | pub bishops: u64, 37 | pub knights: u64, 38 | pub pawns: u64, 39 | pub halfmove_clock: u32, 40 | pub castling: u32, 41 | pub en_passant: u32, 42 | pub white_to_move: bool, 43 | } 44 | 45 | #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] 46 | pub enum Wdl { 47 | Loss, 48 | BlessedLoss, 49 | Draw, 50 | CursedWin, 51 | Win, 52 | } 53 | 54 | const TB_LOSS: u32 = 0; 55 | const TB_BLESSED_LOSS: u32 = 1; 56 | const TB_DRAW: u32 = 2; 57 | const TB_CURSED_WIN: u32 = 3; 58 | const TB_WIN: u32 = 4; 59 | const TB_FAILED: u32 = 0xFFFFFFFF; 60 | 61 | impl TryFrom for Wdl { 62 | type Error = (); 63 | fn try_from(value: u32) -> Result { 64 | match value { 65 | TB_LOSS => Ok(Wdl::Loss), 66 | TB_BLESSED_LOSS => Ok(Wdl::BlessedLoss), 67 | TB_DRAW => Ok(Wdl::Draw), 68 | TB_CURSED_WIN => Ok(Wdl::CursedWin), 69 | TB_WIN => Ok(Wdl::Win), 70 | _ => Err(()), 71 | } 72 | } 73 | } 74 | 75 | #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] 76 | pub enum PromotionPiece { 77 | Knight, 78 | Bishop, 79 | Rook, 80 | Queen, 81 | } 82 | 83 | impl TryFrom for PromotionPiece { 84 | type Error = (); 85 | fn try_from(value: u32) -> Result { 86 | match value { 87 | TB_PROMOTES_KNIGHT => Ok(PromotionPiece::Knight), 88 | TB_PROMOTES_BISHOP => Ok(PromotionPiece::Bishop), 89 | TB_PROMOTES_ROOK => Ok(PromotionPiece::Rook), 90 | TB_PROMOTES_QUEEN => Ok(PromotionPiece::Queen), 91 | _ => Err(()), 92 | } 93 | } 94 | } 95 | 96 | #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] 97 | pub struct Move { 98 | pub from: u32, 99 | pub to: u32, 100 | pub promotes: Option, 101 | pub en_passant: bool, 102 | } 103 | 104 | pub struct ProbeResult { 105 | pub wdl: Wdl, 106 | pub dtz: u32, 107 | pub best_move: Move, 108 | } 109 | 110 | pub unsafe fn init>(path: P) -> bool { 111 | let pathref = path.as_ref(); 112 | let pathstr = match pathref.to_str() { 113 | Some(s) => s, 114 | None => return false, 115 | }; 116 | let c_string = match CString::new(pathstr) { 117 | Ok(s) => s, 118 | Err(_) => return false, 119 | }; 120 | 121 | return c::tb_init(c_string.as_ptr()); 122 | } 123 | 124 | pub unsafe fn max_pieces() -> usize { 125 | c::TB_LARGEST as usize 126 | } 127 | 128 | pub unsafe fn probe_wdl(board: &BoardState) -> Option { 129 | let result = c::tb_probe_wdl_wrapper( 130 | board.white, 131 | board.black, 132 | board.kings, 133 | board.queens, 134 | board.rooks, 135 | board.bishops, 136 | board.knights, 137 | board.pawns, 138 | board.halfmove_clock, 139 | board.castling, 140 | board.en_passant, 141 | board.white_to_move as u8, 142 | ); 143 | 144 | result.try_into().ok() 145 | } 146 | 147 | // const TB_PROMOTES_NONE: u32 = 0; 148 | const TB_PROMOTES_QUEEN: u32 = 1; 149 | const TB_PROMOTES_ROOK: u32 = 2; 150 | const TB_PROMOTES_BISHOP: u32 = 3; 151 | const TB_PROMOTES_KNIGHT: u32 = 4; 152 | 153 | const TB_RESULT_WDL_MASK: u32 = 0x0000000F; 154 | const TB_RESULT_TO_MASK: u32 = 0x000003F0; 155 | const TB_RESULT_FROM_MASK: u32 = 0x0000FC00; 156 | const TB_RESULT_PROMOTES_MASK: u32 = 0x00070000; 157 | const TB_RESULT_EP_MASK: u32 = 0x00080000; 158 | const TB_RESULT_DTZ_MASK: u32 = 0xFFF00000; 159 | const TB_RESULT_WDL_SHIFT: u32 = 0; 160 | const TB_RESULT_TO_SHIFT: u32 = 4; 161 | const TB_RESULT_FROM_SHIFT: u32 = 10; 162 | const TB_RESULT_PROMOTES_SHIFT: u32 = 16; 163 | const TB_RESULT_EP_SHIFT: u32 = 19; 164 | const TB_RESULT_DTZ_SHIFT: u32 = 20; 165 | 166 | fn tb_get_wdl(res: u32) -> Option { 167 | ((res & TB_RESULT_WDL_MASK) >> TB_RESULT_WDL_SHIFT) 168 | .try_into() 169 | .ok() 170 | } 171 | 172 | fn tb_get_to(res: u32) -> u32 { 173 | (res & TB_RESULT_TO_MASK) >> TB_RESULT_TO_SHIFT 174 | } 175 | 176 | fn tb_get_from(res: u32) -> u32 { 177 | (res & TB_RESULT_FROM_MASK) >> TB_RESULT_FROM_SHIFT 178 | } 179 | 180 | fn tb_get_promotes(res: u32) -> Option { 181 | ((res & TB_RESULT_PROMOTES_MASK) >> TB_RESULT_PROMOTES_SHIFT) 182 | .try_into() 183 | .ok() 184 | } 185 | 186 | fn tb_get_ep(res: u32) -> bool { 187 | (res & TB_RESULT_EP_MASK) >> TB_RESULT_EP_SHIFT > 0 188 | } 189 | 190 | fn tb_get_dtz(res: u32) -> u32 { 191 | (res & TB_RESULT_DTZ_MASK) >> TB_RESULT_DTZ_SHIFT 192 | } 193 | 194 | pub unsafe fn probe_root(board: &BoardState) -> Option { 195 | let result = c::tb_probe_root_wrapper( 196 | board.white, 197 | board.black, 198 | board.kings, 199 | board.queens, 200 | board.rooks, 201 | board.bishops, 202 | board.knights, 203 | board.pawns, 204 | board.halfmove_clock, 205 | board.castling, 206 | board.en_passant, 207 | board.white_to_move as u8, 208 | ); 209 | 210 | if result == TB_FAILED { 211 | return None; 212 | } 213 | 214 | let wdl = tb_get_wdl(result)?; 215 | let dtz = tb_get_dtz(result); 216 | let best_move = Move { 217 | from: tb_get_from(result), 218 | to: tb_get_to(result), 219 | promotes: tb_get_promotes(result), 220 | en_passant: tb_get_ep(result), 221 | }; 222 | 223 | Some(ProbeResult { 224 | best_move: best_move, 225 | wdl, 226 | dtz, 227 | }) 228 | } 229 | 230 | mod c { 231 | extern "C" { 232 | pub static TB_LARGEST: u32; 233 | pub fn tb_init(filename: *const i8) -> bool; 234 | pub fn tb_probe_wdl_wrapper( 235 | white: u64, 236 | black: u64, 237 | kings: u64, 238 | queens: u64, 239 | rooks: u64, 240 | bishops: u64, 241 | knights: u64, 242 | pawns: u64, 243 | rule50: u32, 244 | castling: u32, 245 | ep: u32, 246 | turn: u8, 247 | ) -> u32; 248 | 249 | pub fn tb_probe_root_wrapper( 250 | white: u64, 251 | black: u64, 252 | kings: u64, 253 | queens: u64, 254 | rooks: u64, 255 | bishops: u64, 256 | knights: u64, 257 | pawns: u64, 258 | rule50: u32, 259 | castling: u32, 260 | ep: u32, 261 | turn: u8, 262 | ) -> u32; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/hash.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | use crate::bitboard::*; 18 | use crate::movegen::*; 19 | use crate::position::*; 20 | use crate::rand::Xoshiro; 21 | use crate::types::SquareMap; 22 | 23 | pub type Hash = u64; 24 | 25 | #[derive(Clone)] 26 | pub struct Hasher { 27 | color: SquareMap, 28 | hashes: [SquareMap; 6], 29 | white_to_move: Hash, 30 | en_passant: [Hash; 8], 31 | castle: [Hash; 16], 32 | singular: Hash, 33 | 34 | hash: Hash, 35 | pawn_hash: Hash, 36 | } 37 | 38 | impl Hasher { 39 | pub fn new() -> Self { 40 | let mut rng = Xoshiro::new([ 41 | 0x1123581321345589, 42 | 0x2357111317192329, 43 | 0x1248163265128256, 44 | 0x0123456789ABCDEF, 45 | ]); 46 | let mut hasher = Hasher { 47 | color: SquareMap::default(), 48 | hashes: [SquareMap::default(); 6], 49 | white_to_move: 0, 50 | en_passant: [0; 8], 51 | castle: [0; 16], 52 | singular: 0, 53 | 54 | hash: 0, 55 | pawn_hash: 0, 56 | }; 57 | 58 | rng.fill(&mut hasher.color); 59 | rng.fill(&mut hasher.hashes[0]); 60 | rng.fill(&mut hasher.hashes[1]); 61 | rng.fill(&mut hasher.hashes[2]); 62 | rng.fill(&mut hasher.hashes[3]); 63 | rng.fill(&mut hasher.hashes[4]); 64 | rng.fill(&mut hasher.hashes[5]); 65 | hasher.white_to_move = rng.gen(); 66 | rng.fill(&mut hasher.en_passant); 67 | rng.fill(&mut hasher.castle); 68 | hasher.singular = rng.gen(); 69 | 70 | hasher.from_position(&STARTING_POSITION); 71 | 72 | hasher 73 | } 74 | 75 | pub fn get_hash(&self) -> Hash { 76 | self.hash 77 | } 78 | 79 | pub fn get_pawn_hash(&self) -> Hash { 80 | self.pawn_hash 81 | } 82 | 83 | pub fn set(&mut self, hash: Hash, pawn_hash: Hash) { 84 | self.hash = hash; 85 | self.pawn_hash = pawn_hash; 86 | } 87 | 88 | pub fn toggle_singular(&mut self, mov: Move) { 89 | self.hash ^= self.singular; 90 | let from: u8 = mov.from.into(); 91 | let to: u8 = mov.to.into(); 92 | self.hash ^= from as Hash; 93 | self.hash ^= (to as Hash) << 8; 94 | } 95 | 96 | pub fn from_position(&mut self, pos: &Position) { 97 | self.hash = 0; 98 | if pos.white_to_move { 99 | self.hash ^= self.white_to_move; 100 | } 101 | 102 | if pos.details.en_passant != 255 { 103 | self.hash ^= self.en_passant[pos.details.en_passant as usize]; 104 | } 105 | 106 | self.hash ^= self.castle[pos.details.castling as usize]; 107 | 108 | for sq in pos.white_pieces().squares() { 109 | self.hash ^= self.color[sq]; 110 | } 111 | 112 | for sq in pos.pawns().squares() { 113 | self.hash ^= self.hashes[Piece::Pawn.index()][sq]; 114 | self.pawn_hash ^= self.hashes[Piece::Pawn.index()][sq]; 115 | } 116 | 117 | for sq in pos.knights().squares() { 118 | self.hash ^= self.hashes[Piece::Knight.index()][sq]; 119 | } 120 | 121 | for sq in pos.bishops().squares() { 122 | self.hash ^= self.hashes[Piece::Bishop.index()][sq]; 123 | } 124 | 125 | for sq in pos.rooks().squares() { 126 | self.hash ^= self.hashes[Piece::Rook.index()][sq]; 127 | } 128 | 129 | for sq in pos.queens().squares() { 130 | self.hash ^= self.hashes[Piece::Queen.index()][sq]; 131 | } 132 | 133 | for sq in pos.kings().squares() { 134 | self.hash ^= self.hashes[Piece::King.index()][sq]; 135 | } 136 | } 137 | 138 | pub fn make_move(&mut self, pos: &Position, mov: Move) { 139 | let them = pos.them(pos.white_to_move); 140 | let rank2 = if pos.white_to_move { 1 } else { 6 }; 141 | let rank4 = if pos.white_to_move { 3 } else { 4 }; 142 | 143 | if pos.details.en_passant != 255 { 144 | self.hash ^= self.en_passant[pos.details.en_passant as usize]; 145 | } 146 | 147 | if mov.piece == Piece::Pawn 148 | && mov.from.rank() == rank2 149 | && mov.to.rank() == rank4 150 | && ((them & pos.pawns()).left(1) | (them & pos.pawns()).right(1)) & mov.to 151 | { 152 | self.hash ^= self.en_passant[mov.from.file() as usize]; 153 | } 154 | 155 | // Update Pawn Hash 156 | if mov.captured == Some(Piece::Pawn) { 157 | if mov.en_passant { 158 | self.pawn_hash ^= 159 | self.hashes[Piece::Pawn.index()][mov.to.backward(pos.white_to_move, 1)]; 160 | if !pos.white_to_move { 161 | self.pawn_hash ^= self.color[mov.to.backward(pos.white_to_move, 1)]; 162 | } 163 | } else { 164 | self.pawn_hash ^= self.hashes[Piece::Pawn.index()][mov.to]; 165 | 166 | if !pos.white_to_move { 167 | self.pawn_hash ^= self.color[mov.to]; 168 | } 169 | } 170 | } 171 | 172 | if mov.piece == Piece::Pawn { 173 | self.pawn_hash ^= self.hashes[Piece::Pawn.index()][mov.from]; 174 | if pos.white_to_move { 175 | self.pawn_hash ^= self.color[mov.from]; 176 | } 177 | 178 | if mov.promoted.is_none() { 179 | self.pawn_hash ^= self.hashes[Piece::Pawn.index()][mov.to]; 180 | if pos.white_to_move { 181 | self.pawn_hash ^= self.color[mov.to]; 182 | } 183 | } 184 | } 185 | 186 | let mut castling = pos.details.castling; 187 | self.hash ^= self.castle[castling as usize]; 188 | 189 | self.hash ^= self.hashes[mov.piece.index()][mov.from]; 190 | 191 | if let Some(piece) = mov.captured { 192 | if mov.en_passant { 193 | self.hash ^= 194 | self.hashes[Piece::Pawn.index()][mov.to.backward(pos.white_to_move, 1)]; 195 | if !pos.white_to_move { 196 | self.hash ^= self.color[mov.to.backward(pos.white_to_move, 1)]; 197 | } 198 | } else { 199 | self.hash ^= self.hashes[piece.index()][mov.to]; 200 | if !pos.white_to_move { 201 | self.hash ^= self.color[mov.to]; 202 | } 203 | } 204 | } 205 | 206 | if let Some(piece) = mov.promoted { 207 | self.hash ^= self.hashes[piece.index()][mov.to]; 208 | } else { 209 | self.hash ^= self.hashes[mov.piece.index()][mov.to]; 210 | } 211 | 212 | if mov.piece == Piece::King { 213 | if mov.is_kingside_castle() { 214 | // castle kingside 215 | self.hash ^= self.hashes[Piece::Rook.index()][mov.to.right(1)]; 216 | self.hash ^= self.hashes[Piece::Rook.index()][mov.to.left(1)]; 217 | if pos.white_to_move { 218 | self.hash ^= self.color[mov.to.right(1)]; 219 | self.hash ^= self.color[mov.to.left(1)]; 220 | } 221 | } else if mov.is_queenside_castle() { 222 | // castle queenside 223 | self.hash ^= self.hashes[Piece::Rook.index()][mov.to.left(2)]; 224 | self.hash ^= self.hashes[Piece::Rook.index()][mov.to.right(1)]; 225 | if pos.white_to_move { 226 | self.hash ^= self.color[mov.to.left(2)]; 227 | self.hash ^= self.color[mov.to.right(1)]; 228 | } 229 | } 230 | 231 | if pos.white_to_move { 232 | castling &= CASTLE_BLACK_KSIDE | CASTLE_BLACK_QSIDE; 233 | } else { 234 | castling &= CASTLE_WHITE_KSIDE | CASTLE_WHITE_QSIDE; 235 | } 236 | } 237 | 238 | if mov.from == SQUARE_A1 || mov.to == SQUARE_A1 { 239 | castling &= !CASTLE_WHITE_QSIDE; 240 | } 241 | 242 | if mov.from == SQUARE_H1 || mov.to == SQUARE_H1 { 243 | castling &= !CASTLE_WHITE_KSIDE; 244 | } 245 | 246 | if mov.from == SQUARE_A8 || mov.to == SQUARE_A8 { 247 | castling &= !CASTLE_BLACK_QSIDE; 248 | } 249 | 250 | if mov.from == SQUARE_H8 || mov.to == SQUARE_H8 { 251 | castling &= !CASTLE_BLACK_KSIDE; 252 | } 253 | 254 | if pos.white_to_move { 255 | self.hash ^= self.color[mov.to]; 256 | self.hash ^= self.color[mov.from]; 257 | } 258 | 259 | self.hash ^= self.castle[castling as usize]; 260 | self.hash ^= self.white_to_move; 261 | } 262 | 263 | pub fn make_nullmove(&mut self, pos: &Position) { 264 | self.hash ^= self.white_to_move; 265 | if pos.details.en_passant != 255 { 266 | self.hash ^= self.en_passant[pos.details.en_passant as usize]; 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /fathom/src/stdendian.h: -------------------------------------------------------------------------------- 1 | #ifndef _STDENDIAN_H_ 2 | #define _STDENDIAN_H_ 3 | /* from https://gist.github.com/michaeljclark/3b4fd912f6fa8bb598b3 */ 4 | /* modified to use functions not macros for bswap */ 5 | /* and added a fix for Cygwin */ 6 | /* 7 | * stdendian.h 8 | * 9 | * This header defines the following endian macros as defined here: 10 | * http://austingroupbugs.net/view.php?id=162 11 | * 12 | * BYTE_ORDER this macro shall have a value equal to one 13 | * of the *_ENDIAN macros in this header. 14 | * LITTLE_ENDIAN if BYTE_ORDER == LITTLE_ENDIAN, the host 15 | * byte order is from least significant to 16 | * most significant. 17 | * BIG_ENDIAN if BYTE_ORDER == BIG_ENDIAN, the host byte 18 | * order is from most significant to least 19 | * significant. 20 | * 21 | * The following are defined as macros: 22 | * 23 | * uint16_t bswap16(uint16_t x); 24 | * uint32_t bswap32(uint32_t x); 25 | * uint64_t bswap64(uint64_t x); 26 | 27 | * uint16_t htobe16(uint16_t x); 28 | * uint16_t htole16(uint16_t x); 29 | * uint16_t be16toh(uint16_t x); 30 | * uint16_t le16toh(uint16_t x); 31 | * 32 | * uint32_t htobe32(uint32_t x); 33 | * uint32_t htole32(uint32_t x); 34 | * uint32_t be32toh(uint32_t x); 35 | * uint32_t le32toh(uint32_t x); 36 | * 37 | * uint64_t htobe64(uint64_t x); 38 | * uint64_t htole64(uint64_t x); 39 | * uint64_t be64toh(uint64_t x); 40 | * uint64_t le64toh(uint64_t x); 41 | * 42 | * The header defines the following macro for OpenCL compatibility 43 | * https://www.khronos.org/registry/cl/sdk/2.0/docs/man/xhtml/preprocessorDirectives.html 44 | * 45 | * __ENDIAN_LITTLE__ if BYTE_ORDER == LITTLE_ENDIAN then this 46 | * macro is present for OpenCL compatibility 47 | * 48 | * The implementation provides a uniform interface to endian macros using only 49 | * system headers on recent Linux, Darwin, FreeBSD, Solaris and Windows systems. 50 | * 51 | * This approach is intended to avoid the need for preflight configure scripts. 52 | * An alternative approach would be to test compiler CPU architecture marcros. 53 | * 54 | * This header has had *limited* testing on recent C11/C++11 compilers and is 55 | * based on the austin bug tracker interface, manpages, and headers present in 56 | * Linux, FreeBSD, Windows, Solaris and Darwin. 57 | * 58 | * The header uses __builtin_bswapXX intrinsic with GCC/Clang (__GNUC__) on 59 | * platforms that do not provide bswap16, bswap32, bswap64 (Darwin) 60 | * 61 | * Public Domain. 62 | */ 63 | 64 | /* requires C11 or C++11 */ 65 | #if defined (__cplusplus) 66 | #include 67 | #elif !defined (__OPENCL_VERSION__) 68 | #include 69 | #endif 70 | 71 | /* Linux / GLIBC */ 72 | #if defined(__linux__) || defined(__GLIBC__) || defined(__CYGWIN__) 73 | #include 74 | #include 75 | #define __ENDIAN_DEFINED 1 76 | #define __BSWAP_DEFINED 1 77 | #define __HOSTSWAP_DEFINED 1 78 | // NDK defines _BYTE_ORDER etc 79 | #ifndef _BYTE_ORDER 80 | #define _BYTE_ORDER __BYTE_ORDER 81 | #define _LITTLE_ENDIAN __LITTLE_ENDIAN 82 | #define _BIG_ENDIAN __BIG_ENDIAN 83 | #endif 84 | #define bswap16(x) bswap_16(x) 85 | #define bswap32(x) bswap_32(x) 86 | #define bswap64(x) bswap_64(x) 87 | #endif /* __linux__ || __GLIBC__ */ 88 | 89 | /* BSD */ 90 | #if defined(__FreeBSD__) || defined(__NetBSD__) || \ 91 | defined(__DragonFly__) || defined(__OpenBSD__) 92 | #include 93 | #define __ENDIAN_DEFINED 1 94 | #define __BSWAP_DEFINED 1 95 | #define __HOSTSWAP_DEFINED 1 96 | #endif /* BSD */ 97 | 98 | /* Solaris */ 99 | #if defined (sun) 100 | #include 101 | /* sun headers don't set a value for _LITTLE_ENDIAN or _BIG_ENDIAN */ 102 | #if defined(_LITTLE_ENDIAN) 103 | #undef _LITTLE_ENDIAN 104 | #define _LITTLE_ENDIAN 1234 105 | #define _BIG_ENDIAN 4321 106 | #define _BYTE_ORDER _LITTLE_ENDIAN 107 | #elif defined(_BIG_ENDIAN) 108 | #undef _BIG_ENDIAN 109 | #define _LITTLE_ENDIAN 1234 110 | #define _BIG_ENDIAN 4321 111 | #define _BYTE_ORDER _BIG_ENDIAN 112 | #endif 113 | #define __ENDIAN_DEFINED 1 114 | #endif /* sun */ 115 | 116 | /* Windows */ 117 | #if defined(_WIN32) || defined(_MSC_VER) 118 | /* assumes all Microsoft targets are little endian */ 119 | #define _LITTLE_ENDIAN 1234 120 | #define _BIG_ENDIAN 4321 121 | #define _BYTE_ORDER _LITTLE_ENDIAN 122 | #define __ENDIAN_DEFINED 1 123 | #endif /* _MSC_VER */ 124 | 125 | /* OS X */ 126 | #if defined(__APPLE__) 127 | #include 128 | #define _BYTE_ORDER BYTE_ORDER 129 | #define _LITTLE_ENDIAN LITTLE_ENDIAN 130 | #define _BIG_ENDIAN BIG_ENDIAN 131 | #define __ENDIAN_DEFINED 1 132 | #endif /* __APPLE__ */ 133 | 134 | /* OpenCL */ 135 | #if defined (__OPENCL_VERSION__) 136 | #define _LITTLE_ENDIAN 1234 137 | #define __BIG_ENDIAN 4321 138 | #if defined (__ENDIAN_LITTLE__) 139 | #define _BYTE_ORDER _LITTLE_ENDIAN 140 | #else 141 | #define _BYTE_ORDER _BIG_ENDIAN 142 | #endif 143 | #define bswap16(x) as_ushort(as_uchar2(ushort(x)).s1s0) 144 | #define bswap32(x) as_uint(as_uchar4(uint(x)).s3s2s1s0) 145 | #define bswap64(x) as_ulong(as_uchar8(ulong(x)).s7s6s5s4s3s2s1s0) 146 | #define __ENDIAN_DEFINED 1 147 | #define __BSWAP_DEFINED 1 148 | #endif 149 | 150 | /* Unknown */ 151 | #if !__ENDIAN_DEFINED 152 | #error Could not determine CPU byte order 153 | #endif 154 | 155 | /* POSIX - http://austingroupbugs.net/view.php?id=162 */ 156 | #ifndef BYTE_ORDER 157 | #define BYTE_ORDER _BYTE_ORDER 158 | #endif 159 | #ifndef LITTLE_ENDIAN 160 | #define LITTLE_ENDIAN _LITTLE_ENDIAN 161 | #endif 162 | #ifndef BIG_ENDIAN 163 | #define BIG_ENDIAN _BIG_ENDIAN 164 | #endif 165 | 166 | /* OpenCL compatibility - define __ENDIAN_LITTLE__ on little endian systems */ 167 | #if _BYTE_ORDER == _LITTLE_ENDIAN 168 | #if !defined (__ENDIAN_LITTLE__) 169 | #define __ENDIAN_LITTLE__ 1 170 | #endif 171 | #endif 172 | 173 | /* Byte swap macros */ 174 | #if !__BSWAP_DEFINED 175 | 176 | #ifndef bswap16 177 | /* handle missing __builtin_bswap16 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52624 */ 178 | #if defined __GNUC__ 179 | #define bswap16(x) __builtin_bswap16(x) 180 | #else 181 | inline uint16_t bswap16(uint16_t x) { 182 | return (uint16_t)((((uint16_t) (x) & 0xff00) >> 8) | \ 183 | (((uint16_t) (x) & 0x00ff) << 8)); 184 | } 185 | #endif 186 | #endif 187 | 188 | #ifndef bswap32 189 | #if defined __GNUC__ 190 | #define bswap32(x) __builtin_bswap32(x) 191 | #else 192 | inline uint32_t bswap32(uint32_t x) { 193 | return (( x & 0xff000000) >> 24) | \ 194 | (( x & 0x00ff0000) >> 8) | \ 195 | (( x & 0x0000ff00) << 8) | \ 196 | (( x & 0x000000ff) << 24); 197 | } 198 | #endif 199 | #endif 200 | 201 | #ifndef bswap64 202 | #if defined __GNUC__ 203 | #define bswap64(x) __builtin_bswap64(x) 204 | #else 205 | inline uint64_t bswap64(uint64_t x) { 206 | return (( x & 0xff00000000000000ull) >> 56) | \ 207 | (( x & 0x00ff000000000000ull) >> 40) | \ 208 | (( x & 0x0000ff0000000000ull) >> 24) | \ 209 | (( x & 0x000000ff00000000ull) >> 8) | \ 210 | (( x & 0x00000000ff000000ull) << 8) | \ 211 | (( x & 0x0000000000ff0000ull) << 24) | \ 212 | (( x & 0x000000000000ff00ull) << 40) | \ 213 | (( x & 0x00000000000000ffull) << 56); 214 | } 215 | #endif 216 | #endif 217 | 218 | #endif 219 | 220 | /* Host swap macros */ 221 | #ifndef __HOSTSWAP_DEFINED 222 | #if __BYTE_ORDER == __LITTLE_ENDIAN 223 | #define htobe16(x) bswap16((x)) 224 | #define htole16(x) ((uint16_t)(x)) 225 | #define be16toh(x) bswap16((x)) 226 | #define le16toh(x) ((uint16_t)(x)) 227 | 228 | #define htobe32(x) bswap32((x)) 229 | #define htole32(x) ((uint32_t((x)) 230 | #define be32toh(x) bswap32((x)) 231 | #define le32toh(x) ((uint32_t)(x)) 232 | 233 | #define htobe64(x) bswap64((x)) 234 | #define htole64(x) ((uint64_t)(x)) 235 | #define be64toh(x) bswap64((x)) 236 | #define le64toh(x) ((uint64_t)(x)) 237 | #elif __BYTE_ORDER == __BIG_ENDIAN 238 | #define htobe16(x) ((uint16_t)(x)) 239 | #define htole16(x) bswap16((x)) 240 | #define be16toh(x) ((uint16_t)(x)) 241 | #define le16toh(x) bswap16((x)) 242 | 243 | #define htobe32(x) ((uint32_t)(x)) 244 | #define htole32(x) bswap32((x)) 245 | #define be32toh(x) ((uint32_t)(x)) 246 | #define le64toh(x) bswap64((x)) 247 | 248 | #define htobe64(x) ((uint64_t)(x)) 249 | #define htole64(x) bswap64((x)) 250 | #define be64toh(x) ((uint64_t)(x)) 251 | #define le32toh(x) bswap32((x)) 252 | #endif 253 | #endif 254 | 255 | /* 256 | 257 | #include 258 | #include 259 | 260 | int main() 261 | { 262 | 263 | #if BYTE_ORDER == LITTLE_ENDIAN 264 | printf("little endian\n"); 265 | #endif 266 | 267 | #if BYTE_ORDER == BIG_ENDIAN 268 | printf("big endian\n"); 269 | #endif 270 | 271 | printf("bswap16(%04x) %04x\n", 0xf0e0, bswap16(0xf0e0)); 272 | printf("htobe16(%04x) %04x\n", 0xf0e0, htobe16(0xf0e0)); 273 | printf("htole16(%04x) %04x\n", 0xf0e0, htole16(0xf0e0)); 274 | 275 | printf("bswap32(%08x) %08x\n", 0xf0e0d0c0, bswap32(0xf0e0d0c0)); 276 | printf("htobe32(%08x) %08x\n", 0xf0e0d0c0, htobe32(0xf0e0d0c0)); 277 | printf("htole32(%08x) %08x\n", 0xf0e0d0c0, htole32(0xf0e0d0c0)); 278 | 279 | printf("bswap64(%016llx) %016llx\n", 0xf0e0d0c0b0a09080ULL, bswap64(0xf0e0d0c0b0a09080ULL)); 280 | printf("htobe64(%016llx) %016llx\n", 0xf0e0d0c0b0a09080ULL, htobe64(0xf0e0d0c0b0a09080ULL)); 281 | printf("htole64(%016llx) %016llx\n", 0xf0e0d0c0b0a09080ULL, htole64(0xf0e0d0c0b0a09080ULL)); 282 | } 283 | 284 | */ 285 | #endif 286 | -------------------------------------------------------------------------------- /src/movepick.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | use crate::history::*; 18 | use crate::movegen::*; 19 | use crate::position::*; 20 | use crate::types::SquareMap; 21 | 22 | pub struct MovePicker<'a> { 23 | ttmove: Option, 24 | excluded: ShortMoveList, 25 | stage: usize, 26 | stages: &'a [Stage], 27 | moves: MoveList, 28 | scores: ScoreList, 29 | bad_moves: MoveList, 30 | bad_scores: ScoreList, 31 | index: usize, 32 | killers: [Option; 2], 33 | skip_quiets: bool, 34 | previous_move: Option, 35 | } 36 | 37 | #[derive(Clone)] 38 | enum Stage { 39 | TTMove, 40 | GenerateGoodCaptures, 41 | GoodCaptures, 42 | GenerateKillers, 43 | Killers, 44 | GenerateQuietMoves, 45 | QuietMoves, 46 | GenerateBadCaptures, 47 | BadCaptures, 48 | } 49 | 50 | const ALPHA_BETA_STAGES: &[Stage] = &[ 51 | Stage::TTMove, 52 | Stage::GenerateGoodCaptures, 53 | Stage::GoodCaptures, 54 | Stage::GenerateKillers, 55 | Stage::Killers, 56 | Stage::GenerateQuietMoves, 57 | Stage::QuietMoves, 58 | Stage::GenerateBadCaptures, 59 | Stage::BadCaptures, 60 | ]; 61 | 62 | const QUIESCENCE_STAGES: &[Stage] = &[Stage::GenerateGoodCaptures, Stage::GoodCaptures]; 63 | 64 | const QUIESCENCE_CHECK_STAGES: &[Stage] = &[ 65 | Stage::GenerateGoodCaptures, 66 | Stage::GoodCaptures, 67 | Stage::GenerateBadCaptures, 68 | Stage::BadCaptures, 69 | Stage::GenerateQuietMoves, 70 | Stage::QuietMoves, 71 | ]; 72 | 73 | #[derive(Copy, Clone, PartialEq, Eq)] 74 | pub enum MoveType { 75 | TTMove, 76 | GoodCapture, 77 | Killer, 78 | Quiet, 79 | BadCapture, 80 | } 81 | 82 | impl<'a> MovePicker<'a> { 83 | pub fn new( 84 | ttmove: Option, 85 | killers: [Option; 2], 86 | previous_move: Option, 87 | ) -> Self { 88 | MovePicker { 89 | ttmove, 90 | excluded: ShortMoveList::new(), 91 | stage: 0, 92 | stages: ALPHA_BETA_STAGES, 93 | moves: MoveList::new(), 94 | scores: ScoreList::new(), 95 | bad_moves: MoveList::new(), 96 | bad_scores: ScoreList::new(), 97 | index: 0, 98 | killers, 99 | skip_quiets: false, 100 | previous_move, 101 | } 102 | } 103 | 104 | pub fn qsearch(position: &Position) -> Self { 105 | let stages = if position.in_check() { 106 | QUIESCENCE_CHECK_STAGES 107 | } else { 108 | QUIESCENCE_STAGES 109 | }; 110 | 111 | MovePicker { 112 | ttmove: None, 113 | excluded: ShortMoveList::new(), 114 | stage: 0, 115 | stages, 116 | moves: MoveList::new(), 117 | scores: ScoreList::new(), 118 | bad_moves: MoveList::new(), 119 | bad_scores: ScoreList::new(), 120 | index: 0, 121 | killers: [None; 2], 122 | skip_quiets: false, 123 | previous_move: None, 124 | } 125 | } 126 | 127 | pub fn add_excluded_move(&mut self, mov: Move) { 128 | self.excluded.push(mov); 129 | } 130 | 131 | pub fn skip_quiets(&mut self, skip_quiets: bool) { 132 | self.skip_quiets = skip_quiets; 133 | } 134 | 135 | fn get_move(&mut self) -> Option { 136 | let mov = Self::get_move_from_lists(self.index, &mut self.moves, &mut self.scores); 137 | self.index += 1; 138 | mov 139 | } 140 | 141 | fn get_bad_move(&mut self) -> Option { 142 | let mov = Self::get_move_from_lists(self.index, &mut self.bad_moves, &mut self.bad_scores); 143 | self.index += 1; 144 | mov 145 | } 146 | 147 | fn get_move_from_lists( 148 | index: usize, 149 | moves: &mut MoveList, 150 | scores: &mut ScoreList, 151 | ) -> Option { 152 | // Iterator::max_by_key chooses the last maximal element, but we want 153 | // the first (for no particular reason other than that's what was done 154 | // before). Hence we reverse the iterator first. 155 | let (best_index, (_, best_move)) = scores 156 | .iter() 157 | .zip(moves.iter()) 158 | .enumerate() 159 | .skip(index) 160 | .rev() 161 | .max_by_key(|(_, (&score, _))| score)?; 162 | 163 | let best_move = *best_move; 164 | moves.swap(index, best_index); 165 | scores.swap(index, best_index); 166 | Some(best_move) 167 | } 168 | 169 | pub fn next( 170 | &mut self, 171 | position: &Position, 172 | history: &History, 173 | last_best_reply: &[[SquareMap>; 6]; 2], 174 | ) -> Option<(MoveType, Move)> { 175 | if self.stage >= self.stages.len() { 176 | return None; 177 | } 178 | 179 | match self.stages[self.stage] { 180 | Stage::TTMove => { 181 | self.stage += 1; 182 | if let Some(mov) = self.ttmove { 183 | self.excluded.push(mov); 184 | return Some((MoveType::TTMove, mov)); 185 | } 186 | self.next(position, history, last_best_reply) 187 | } 188 | Stage::GenerateGoodCaptures => { 189 | self.moves.clear(); 190 | self.scores.clear(); 191 | self.bad_moves.clear(); 192 | self.bad_scores.clear(); 193 | 194 | MoveGenerator::from(position).good_captures( 195 | &mut self.moves, 196 | &mut self.scores, 197 | &mut self.bad_moves, 198 | &mut self.bad_scores, 199 | ); 200 | self.index = 0; 201 | self.stage += 1; 202 | self.next(position, history, last_best_reply) 203 | } 204 | Stage::GoodCaptures => { 205 | if let Some(mov) = self.get_move() { 206 | if self.excluded.contains(&mov) { 207 | self.next(position, history, last_best_reply) 208 | } else { 209 | Some((MoveType::GoodCapture, mov)) 210 | } 211 | } else { 212 | self.stage += 1; 213 | self.next(position, history, last_best_reply) 214 | } 215 | } 216 | Stage::GenerateKillers => { 217 | if self.skip_quiets { 218 | self.stage += 1; 219 | return self.next(position, history, last_best_reply); 220 | } 221 | 222 | self.moves.clear(); 223 | self.scores.clear(); 224 | self.moves.extend( 225 | self.killers 226 | .iter() 227 | .flatten() 228 | .filter(|&&m| position.move_is_pseudo_legal(m)) 229 | .copied(), 230 | ); 231 | if let Some(prev_move) = self.previous_move { 232 | if prev_move.is_quiet() { 233 | self.moves.extend( 234 | last_best_reply[position.white_to_move as usize] 235 | [prev_move.piece.index()][prev_move.to] 236 | .iter() 237 | .filter(|&&m| position.move_is_pseudo_legal(m)) 238 | .copied(), 239 | ); 240 | } 241 | } 242 | self.scores.extend(self.moves.iter().map(|_| 0)); 243 | self.index = 0; 244 | self.stage += 1; 245 | self.next(position, history, last_best_reply) 246 | } 247 | Stage::Killers => { 248 | if self.skip_quiets { 249 | self.stage += 1; 250 | return self.next(position, history, last_best_reply); 251 | } 252 | 253 | if let Some(mov) = self.get_move() { 254 | if self.excluded.contains(&mov) { 255 | self.next(position, history, last_best_reply) 256 | } else { 257 | self.excluded.push(mov); 258 | Some((MoveType::Killer, mov)) 259 | } 260 | } else { 261 | self.stage += 1; 262 | self.next(position, history, last_best_reply) 263 | } 264 | } 265 | Stage::GenerateQuietMoves => { 266 | if self.skip_quiets { 267 | self.stage += 1; 268 | return self.next(position, history, last_best_reply); 269 | } 270 | 271 | self.moves.clear(); 272 | self.scores.clear(); 273 | 274 | MoveGenerator::from(position).quiet_moves(&mut self.moves); 275 | let wtm = position.white_to_move; 276 | self.scores.extend( 277 | self.moves 278 | .iter() 279 | .map(|&mov| history.get_score(wtm, mov) as i64), 280 | ); 281 | self.index = 0; 282 | self.stage += 1; 283 | self.next(position, history, last_best_reply) 284 | } 285 | Stage::QuietMoves => { 286 | if self.skip_quiets { 287 | self.stage += 1; 288 | return self.next(position, history, last_best_reply); 289 | } 290 | 291 | if let Some(mov) = self.get_move() { 292 | if self.excluded.contains(&mov) { 293 | self.next(position, history, last_best_reply) 294 | } else { 295 | Some((MoveType::Quiet, mov)) 296 | } 297 | } else { 298 | self.stage += 1; 299 | self.next(position, history, last_best_reply) 300 | } 301 | } 302 | Stage::GenerateBadCaptures => { 303 | self.index = 0; 304 | self.stage += 1; 305 | self.next(position, history, last_best_reply) 306 | } 307 | Stage::BadCaptures => { 308 | if let Some(mov) = self.get_bad_move() { 309 | if self.excluded.contains(&mov) { 310 | self.next(position, history, last_best_reply) 311 | } else { 312 | Some((MoveType::BadCapture, mov)) 313 | } 314 | } else { 315 | self.stage += 1; 316 | self.next(position, history, last_best_reply) 317 | } 318 | } 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/uci.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | use crate::bench::*; 18 | use crate::position::*; 19 | use crate::search_controller::SearchController; 20 | use crate::time::*; 21 | 22 | #[cfg(feature = "tune")] 23 | use crate::tune::*; 24 | 25 | use std::io::{self, BufRead}; 26 | use std::sync; 27 | use std::thread; 28 | 29 | pub struct UCI { 30 | _main_thread: thread::JoinHandle<()>, 31 | main_thread_tx: sync::mpsc::Sender, 32 | abort: sync::Arc, 33 | } 34 | 35 | pub enum UciCommand { 36 | Unknown(String), 37 | UciNewGame, 38 | Uci, 39 | IsReady, 40 | SetOption(String, String), 41 | Position(Position, Vec), 42 | Go(GoParams), 43 | Quit, 44 | Stop, 45 | Bench, 46 | Tune(String), 47 | ShowMoves, 48 | Debug, 49 | TT, 50 | Perft(usize), 51 | } 52 | 53 | pub struct GoParams { 54 | pub time_control: TimeControl, 55 | } 56 | 57 | impl UCI { 58 | pub fn new() -> UCI { 59 | let (main_tx, main_rx) = sync::mpsc::channel(); 60 | let abort = sync::Arc::new(sync::atomic::AtomicBool::new(false)); 61 | let builder = thread::Builder::new() 62 | .name("Main thread".into()) 63 | .stack_size(8 * 1024 * 1024); 64 | UCI { 65 | abort: sync::Arc::clone(&abort), 66 | _main_thread: builder 67 | .spawn(move || SearchController::new(STARTING_POSITION, abort).looping(main_rx)) 68 | .unwrap(), 69 | main_thread_tx: main_tx, 70 | } 71 | } 72 | 73 | pub fn run(&mut self) { 74 | let stdin = io::stdin(); 75 | let lock = stdin.lock(); 76 | for line in lock.lines() { 77 | let line = line.unwrap(); 78 | 79 | let cmd = UciCommand::from(line.as_ref()); 80 | 81 | // Some commands are handled here instead of by the search 82 | match cmd { 83 | UciCommand::Quit => return, 84 | UciCommand::Stop => { 85 | self.abort.store(true, sync::atomic::Ordering::SeqCst); 86 | } 87 | UciCommand::Uci => { 88 | self.main_thread_tx.send(UciCommand::Uci).unwrap(); 89 | } 90 | UciCommand::UciNewGame => { 91 | self.main_thread_tx.send(UciCommand::UciNewGame).unwrap(); 92 | } 93 | UciCommand::Bench => { 94 | run_benchmark(12, sync::Arc::clone(&self.abort)); 95 | } 96 | UciCommand::Tune(filename) => { 97 | tune(&filename); 98 | } 99 | cmd => { 100 | self.main_thread_tx.send(cmd).unwrap(); 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | impl<'a> From<&'a str> for UciCommand { 108 | fn from(line: &str) -> Self { 109 | if line.starts_with("ucinewgame") { 110 | UciCommand::UciNewGame 111 | } else if line.starts_with("setoption") { 112 | let mut words = line.split_whitespace(); 113 | assert!(words.next() == Some("setoption")); 114 | assert!(words.next() == Some("name")); 115 | let mut name_parts = Vec::new(); 116 | let mut value_parts = Vec::new(); 117 | 118 | // parse option name 119 | while let Some(word) = words.next() { 120 | if word == "value" { 121 | break; 122 | } else { 123 | name_parts.push(word); 124 | } 125 | } 126 | 127 | for word in words { 128 | value_parts.push(word); 129 | } 130 | 131 | let mut name = name_parts 132 | .into_iter() 133 | .fold(String::new(), |name, part| name + part); 134 | name.make_ascii_lowercase(); 135 | let value = value_parts 136 | .into_iter() 137 | .fold(String::new(), |name, part| name + part); 138 | 139 | UciCommand::SetOption(name, value) 140 | } else if line.starts_with("uci") { 141 | UciCommand::Uci 142 | } else if line.starts_with("isready") { 143 | UciCommand::IsReady 144 | } else if line.starts_with("go") { 145 | let params = GoParams::from(line); 146 | UciCommand::Go(params) 147 | } else if line.starts_with("position") { 148 | let pos; 149 | let fen = line.trim_start_matches("position "); 150 | if fen.starts_with("startpos") { 151 | pos = STARTING_POSITION; 152 | } else { 153 | pos = Position::from(fen.trim_start_matches("fen")); 154 | } 155 | 156 | let mut moves = Vec::new(); 157 | if line.contains("moves") { 158 | if let Some(moves_) = line.split_terminator("moves ").nth(1) { 159 | for mov in moves_.split_whitespace() { 160 | moves.push(String::from(mov)); 161 | } 162 | } 163 | } 164 | UciCommand::Position(pos, moves) 165 | } else if line.starts_with("quit") { 166 | UciCommand::Quit 167 | } else if line.starts_with("bench") { 168 | UciCommand::Bench 169 | } else if line.starts_with("tune") { 170 | UciCommand::Tune(line[5..].to_owned()) 171 | } else if line.starts_with("showmoves") { 172 | UciCommand::ShowMoves 173 | } else if line == "d" { 174 | UciCommand::Debug 175 | } else if line == "tt" { 176 | UciCommand::TT 177 | } else if line.starts_with("perft") { 178 | let depth = line 179 | .split_whitespace() 180 | .nth(1) 181 | .and_then(|d| d.parse().ok()) 182 | .unwrap_or(6); 183 | UciCommand::Perft(depth) 184 | } else if line == "stop" { 185 | UciCommand::Stop 186 | } else { 187 | UciCommand::Unknown(line.to_owned()) 188 | } 189 | } 190 | } 191 | 192 | impl<'a> From<&'a str> for GoParams { 193 | fn from(s: &str) -> Self { 194 | let mut result = GoParams { 195 | time_control: TimeControl::Infinite, 196 | }; 197 | 198 | let mut wtime: Option = None; 199 | let mut btime: Option = None; 200 | let mut winc: Option = None; 201 | let mut binc: Option = None; 202 | let mut movestogo: Option = None; 203 | 204 | let mut split = s.split_whitespace(); 205 | while let Some(s) = split.next() { 206 | if s == "movetime" { 207 | result.time_control = 208 | TimeControl::FixedMillis(split.next().unwrap().parse().unwrap()); 209 | return result; 210 | } else if s == "infinite" { 211 | result.time_control = TimeControl::Infinite; 212 | return result; 213 | } else if s == "nodes" { 214 | result.time_control = 215 | TimeControl::FixedNodes(split.next().unwrap().parse().unwrap()); 216 | return result; 217 | } else if s == "depth" { 218 | result.time_control = 219 | TimeControl::FixedDepth(split.next().unwrap().parse().unwrap()); 220 | return result; 221 | } else if s == "wtime" { 222 | wtime = split.next().unwrap().parse().ok(); 223 | } else if s == "btime" { 224 | btime = split.next().unwrap().parse().ok(); 225 | } else if s == "winc" { 226 | winc = split.next().unwrap().parse().ok(); 227 | } else if s == "binc" { 228 | binc = split.next().unwrap().parse().ok(); 229 | } else if s == "movestogo" { 230 | movestogo = split.next().unwrap().parse().ok(); 231 | } 232 | } 233 | 234 | GoParams { 235 | time_control: TimeControl::Variable { 236 | wtime: wtime.unwrap(), 237 | btime: btime.unwrap(), 238 | winc, 239 | binc, 240 | movestogo, 241 | }, 242 | } 243 | } 244 | } 245 | 246 | #[cfg(feature = "tune")] 247 | fn tune(path: &str) { 248 | let mut traces; 249 | if path.ends_with(".pgn") { 250 | traces = pgn_to_positions(path) 251 | .map(|(r, pos)| CompactTrace::from(Trace::from_position(r, pos))) 252 | .collect::>(); 253 | } else if path.ends_with(".fen") { 254 | traces = fens_to_positions(path) 255 | .map(|(r, pos)| CompactTrace::from(Trace::from_position(r, pos))) 256 | .collect::>(); 257 | } else if path.ends_with(".epd") { 258 | traces = epd_to_positions(path) 259 | .map(|(r, pos)| CompactTrace::from(Trace::from_position(r, pos))) 260 | .collect::>(); 261 | } else { 262 | eprintln!("Unsupported format"); 263 | return; 264 | } 265 | let mut params = Parameters::default(); 266 | println!("# positions: {:>8}", traces.len()); 267 | params.compute_optimal_k(&traces); 268 | println!("Optimized K: {:>8.6}", params.k); 269 | 270 | let initial_error = params.total_error(&traces); 271 | let mut last_printed_error = initial_error; 272 | let mut best = initial_error; 273 | println!("Error : {:>8.6}", last_printed_error); 274 | 275 | let mut f = 5.; 276 | 277 | for i in 1.. { 278 | shuffle_traces(&mut traces); 279 | params.step(&traces, f); 280 | 281 | let error = params.total_error(&traces); 282 | if i % 20 == 0 { 283 | params.print_weights(); 284 | println!( 285 | "Error : {:>8.6} ({:>8.6}%) (total {:>8.6}%)", 286 | error, 287 | 100. * (error - last_printed_error) / last_printed_error, 288 | 100. * (error - initial_error) / initial_error 289 | ); 290 | last_printed_error = error; 291 | if error > best { 292 | f /= 2.; 293 | if f < 0.000001 { 294 | break; 295 | } 296 | println!("Decreased learning rate. Now: {:.6}", f); 297 | } 298 | 299 | best = error; 300 | } 301 | } 302 | println!("Done"); 303 | let error = params.total_error(&traces); 304 | println!( 305 | "Total error: {:>8.6} ({:>8.6}%)", 306 | error, 307 | 100. * (error - initial_error) / initial_error 308 | ); 309 | } 310 | 311 | #[cfg(feature = "tune")] 312 | fn shuffle_traces(traces: &mut [CompactTrace]) { 313 | use rand::Rng; 314 | let mut rng = rand::thread_rng(); 315 | let n = traces.len(); 316 | 317 | for _ in 0..n { 318 | let a = rng.gen::() % n; 319 | let b = rng.gen::() % n; 320 | traces.swap(a, b); 321 | } 322 | } 323 | 324 | #[cfg(not(feature = "tune"))] 325 | fn tune(_path: &str) { 326 | println!("This binary was not compiled with tuning support."); 327 | } 328 | -------------------------------------------------------------------------------- /src/magic.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | use crate::bitboard::*; 18 | use crate::rand::Xoshiro; 19 | use crate::types::SquareMap; 20 | 21 | const SHIFT_MASK: u64 = 0xF8_00_00_00_00_00_00_00; 22 | 23 | pub fn initialize_magics() { 24 | let offset = initialize_bishop_attacks(0); 25 | initialize_rook_attacks(offset); 26 | } 27 | 28 | pub static mut MAGIC_TABLE: [Bitboard; 107_648] = [Bitboard(0); 107_648]; 29 | pub static mut BISHOP_ATTACKS: SquareMap = SquareMap::from_array( 30 | [Magic { 31 | magic: 0, 32 | mask: Bitboard(0), 33 | offset: 0, 34 | }; 64], 35 | ); 36 | pub static mut ROOK_ATTACKS: SquareMap = SquareMap::from_array( 37 | [Magic { 38 | magic: 0, 39 | mask: Bitboard(0), 40 | offset: 0, 41 | }; 64], 42 | ); 43 | 44 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 45 | pub struct Magic { 46 | magic: u64, 47 | mask: Bitboard, 48 | offset: u32, 49 | } 50 | 51 | impl Magic { 52 | #[cfg(all(target_arch = "x86_64", target_feature = "bmi2", feature = "pext"))] 53 | pub fn index(&self, occupied: Bitboard) -> usize { 54 | use std::arch::x86_64::_pext_u64; 55 | 56 | self.offset as usize + unsafe { _pext_u64(occupied.0, self.mask.0) as usize } 57 | } 58 | 59 | #[cfg(not(all(target_arch = "x86_64", target_feature = "bmi2", feature = "pext")))] 60 | pub fn index(&self, occupied: Bitboard) -> usize { 61 | let shift = self.magic.wrapping_shr(56) as u32; 62 | self.offset as usize 63 | + ((occupied & self.mask).0.wrapping_mul(self.magic)).wrapping_shr(shift) as usize 64 | } 65 | } 66 | 67 | fn initialize_bishop_attacks(offset: usize) -> usize { 68 | let border = FILE_A | FILE_H | RANK_1 | RANK_8; 69 | 70 | let mut rng = Xoshiro::new([ 71 | 0x1123581321345589, 72 | 0x2357111317192329, 73 | 0x1248163265128256, 74 | 0x0123456789ABCDEF, 75 | ]); 76 | 77 | let mut offset = offset; 78 | 79 | for sq in 0..64 { 80 | let from = Square::from(sq); 81 | let mask = bishop_from(from, Bitboard::from(0)) & !border; 82 | let bits = mask.popcount() as u64; 83 | let shift = 64 - bits; 84 | 85 | let mut occ = Bitboard::from(0); 86 | let mut size = 0; 87 | 88 | let mut occupancy = Vec::with_capacity(1 << size); 89 | let mut reference = Vec::with_capacity(1 << size); 90 | 91 | loop { 92 | occupancy.push(occ); 93 | reference.push(bishop_from(from, occ)); 94 | size += 1; 95 | occ = Bitboard::from(occ.0.wrapping_sub(mask.0)) & mask; 96 | if occ.is_empty() { 97 | break; 98 | } 99 | } 100 | 101 | // search for magics 102 | let mut magic = Magic { 103 | magic: rng.gen_sparse::<3>() & !SHIFT_MASK | shift.wrapping_shl(56), 104 | mask, 105 | offset: offset as u32, 106 | }; 107 | 108 | let mut last_used = Vec::with_capacity(size); 109 | for _ in 0..size { 110 | last_used.push(0); 111 | } 112 | 113 | let mut tries = 1; 114 | 115 | 'search_magic: loop { 116 | for i in 0..size { 117 | let index = magic.index(occupancy[i]); 118 | let magic_table_entry = unsafe { MAGIC_TABLE[index] }; 119 | if magic_table_entry != reference[i] && last_used[index - offset] == tries { 120 | // retry 121 | magic.magic = rng.gen_sparse::<3>() & !SHIFT_MASK | shift.wrapping_shl(56); 122 | tries += 1; 123 | continue 'search_magic; 124 | } 125 | unsafe { 126 | MAGIC_TABLE[index] = reference[i]; 127 | } 128 | last_used[index - offset] = tries; 129 | } 130 | 131 | break; 132 | } 133 | 134 | unsafe { 135 | BISHOP_ATTACKS[from] = magic; 136 | } 137 | offset += size; 138 | } 139 | 140 | offset 141 | } 142 | 143 | fn bishop_from(from: Square, blockers: Bitboard) -> Bitboard { 144 | let empty = !blockers; 145 | 146 | let mut propagators_ne = empty; 147 | let mut propagators_se = empty; 148 | let mut propagators_sw = empty; 149 | let mut propagators_nw = empty; 150 | let mut reachable_ne = Bitboard::from(0); 151 | let mut reachable_se = Bitboard::from(0); 152 | let mut reachable_sw = Bitboard::from(0); 153 | let mut reachable_nw = Bitboard::from(0); 154 | reachable_ne |= from; 155 | reachable_se |= from; 156 | reachable_sw |= from; 157 | reachable_nw |= from; 158 | 159 | reachable_ne |= reachable_ne.forward(true, 1).right(1) & propagators_ne; 160 | propagators_ne &= propagators_ne.forward(true, 1).right(1); 161 | reachable_ne |= reachable_ne.forward(true, 2).right(2) & propagators_ne; 162 | propagators_ne &= propagators_ne.forward(true, 2).right(2); 163 | reachable_ne |= reachable_ne.forward(true, 4).right(4) & propagators_ne; 164 | 165 | reachable_se |= reachable_se.backward(true, 1).right(1) & propagators_se; 166 | propagators_se &= propagators_se.backward(true, 1).right(1); 167 | reachable_se |= reachable_se.backward(true, 2).right(2) & propagators_se; 168 | propagators_se &= propagators_se.backward(true, 2).right(2); 169 | reachable_se |= reachable_se.backward(true, 4).right(4) & propagators_se; 170 | 171 | reachable_sw |= reachable_sw.backward(true, 1).left(1) & propagators_sw; 172 | propagators_sw &= propagators_sw.backward(true, 1).left(1); 173 | reachable_sw |= reachable_sw.backward(true, 2).left(2) & propagators_sw; 174 | propagators_sw &= propagators_sw.backward(true, 2).left(2); 175 | reachable_sw |= reachable_sw.backward(true, 4).left(4) & propagators_sw; 176 | 177 | reachable_nw |= reachable_nw.forward(true, 1).left(1) & propagators_nw; 178 | propagators_nw &= propagators_nw.forward(true, 1).left(1); 179 | reachable_nw |= reachable_nw.forward(true, 2).left(2) & propagators_nw; 180 | propagators_nw &= propagators_nw.forward(true, 2).left(2); 181 | reachable_nw |= reachable_nw.forward(true, 4).left(4) & propagators_nw; 182 | 183 | reachable_ne.forward(true, 1).right(1) 184 | | reachable_se.backward(true, 1).right(1) 185 | | reachable_sw.backward(true, 1).left(1) 186 | | reachable_nw.forward(true, 1).left(1) 187 | } 188 | 189 | fn initialize_rook_attacks(offset: usize) -> usize { 190 | let border_files = FILE_A | FILE_H; 191 | let border_ranks = RANK_1 | RANK_8; 192 | 193 | let mut rng = Xoshiro::new([ 194 | 0x1123581321345589, 195 | 0x2357111317192329, 196 | 0x1248163265128256, 197 | 0x0123456789ABCDEF, 198 | ]); 199 | 200 | let mut offset = offset; 201 | 202 | for sq in 0..64 { 203 | let from = Square::from(sq); 204 | let mask = !from.to_bb() 205 | & ((FILES[from.file() as usize] & !border_ranks) 206 | | (RANKS[from.rank() as usize] & !border_files)); 207 | let bits = mask.popcount() as u64; 208 | let shift = 64 - bits; 209 | 210 | let mut occ = Bitboard::from(0); 211 | let mut size = 0; 212 | 213 | let mut occupancy = Vec::with_capacity(1 << bits); 214 | let mut reference = Vec::with_capacity(1 << bits); 215 | 216 | loop { 217 | occupancy.push(occ); 218 | reference.push(rook_from(from, occ)); 219 | size += 1; 220 | occ = Bitboard::from(occ.0.wrapping_sub(mask.0)) & mask; 221 | if occ.is_empty() { 222 | break; 223 | } 224 | } 225 | 226 | // search for magics 227 | let mut magic = Magic { 228 | magic: rng.gen_sparse::<3>() & !SHIFT_MASK | shift.wrapping_shl(56), 229 | mask, 230 | offset: offset as u32, 231 | }; 232 | 233 | let mut last_used = Vec::with_capacity(size); 234 | for _ in 0..size { 235 | last_used.push(0); 236 | } 237 | 238 | let mut tries = 1; 239 | 240 | 'search_magic: loop { 241 | for i in 0..size { 242 | let index = magic.index(occupancy[i]); 243 | let magic_table_entry = unsafe { MAGIC_TABLE[index] }; 244 | if magic_table_entry != reference[i] && last_used[index - offset] == tries { 245 | // retry 246 | magic.magic = rng.gen_sparse::<3>() & !SHIFT_MASK | shift.wrapping_shl(56); 247 | tries += 1; 248 | continue 'search_magic; 249 | } 250 | unsafe { 251 | MAGIC_TABLE[index] = reference[i]; 252 | } 253 | last_used[index - offset] = tries; 254 | } 255 | 256 | break; 257 | } 258 | 259 | unsafe { 260 | ROOK_ATTACKS[from] = magic; 261 | } 262 | offset += size; 263 | } 264 | 265 | offset 266 | } 267 | 268 | fn rook_from(from: Square, blockers: Bitboard) -> Bitboard { 269 | let empty = !blockers; 270 | 271 | let mut propagators_north = empty; 272 | let mut propagators_south = empty; 273 | let mut propagators_west = empty; 274 | let mut propagators_east = empty; 275 | let mut reachable_north = Bitboard::from(0); 276 | let mut reachable_south = Bitboard::from(0); 277 | let mut reachable_west = Bitboard::from(0); 278 | let mut reachable_east = Bitboard::from(0); 279 | reachable_north |= from; 280 | reachable_south |= from; 281 | reachable_west |= from; 282 | reachable_east |= from; 283 | 284 | reachable_north |= reachable_north.forward(true, 1) & propagators_north; 285 | propagators_north &= propagators_north.forward(true, 1); 286 | reachable_north |= reachable_north.forward(true, 2) & propagators_north; 287 | propagators_north &= propagators_north.forward(true, 2); 288 | reachable_north |= reachable_north.forward(true, 4) & propagators_north; 289 | 290 | reachable_south |= reachable_south.backward(true, 1) & propagators_south; 291 | propagators_south &= propagators_south.backward(true, 1); 292 | reachable_south |= reachable_south.backward(true, 2) & propagators_south; 293 | propagators_south &= propagators_south.backward(true, 2); 294 | reachable_south |= reachable_south.backward(true, 4) & propagators_south; 295 | 296 | reachable_west |= reachable_west.left(1) & propagators_west; 297 | propagators_west &= propagators_west.left(1); 298 | reachable_west |= reachable_west.left(2) & propagators_west; 299 | propagators_west &= propagators_west.left(2); 300 | reachable_west |= reachable_west.left(4) & propagators_west; 301 | 302 | reachable_east |= reachable_east.right(1) & propagators_east; 303 | propagators_east &= propagators_east.right(1); 304 | reachable_east |= reachable_east.right(2) & propagators_east; 305 | propagators_east &= propagators_east.right(2); 306 | reachable_east |= reachable_east.right(4) & propagators_east; 307 | 308 | reachable_north.forward(true, 1) 309 | | reachable_south.backward(true, 1) 310 | | reachable_west.left(1) 311 | | reachable_east.right(1) 312 | } 313 | -------------------------------------------------------------------------------- /src/tt.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | use crate::bitboard::*; 18 | use crate::eval::*; 19 | use crate::hash::*; 20 | use crate::movegen::*; 21 | use crate::position::*; 22 | use crate::search::*; 23 | 24 | use std::cmp; 25 | use std::sync::atomic::{AtomicU64, Ordering}; 26 | 27 | pub struct TT { 28 | table: Vec, 29 | bitmask: u64, 30 | generation: u8, 31 | } 32 | 33 | impl TT { 34 | pub fn new(bits: u64) -> Self { 35 | let bitmask = (1 << bits) - 1; 36 | let mut table = Vec::with_capacity(1 << bits); 37 | for _ in 0..(1 << bits) { 38 | table.push(Bucket::default()); 39 | } 40 | 41 | TT { 42 | table, 43 | bitmask, 44 | generation: 0, 45 | } 46 | } 47 | 48 | pub fn usage(&self) -> u64 { 49 | let n = cmp::min(300, self.table.len()); 50 | let total = n * NUM_CLUSTERS; 51 | let mut usage = 0; 52 | for bucket in self.table.iter().take(n) { 53 | for atomic_entry in &bucket.0 { 54 | let entry = atomic_entry.read(); 55 | if entry.generation == self.generation { 56 | usage += 1; 57 | } 58 | } 59 | } 60 | 1000 * usage / total as u64 61 | } 62 | 63 | pub fn next_generation(&mut self) { 64 | self.generation = self.generation.wrapping_add(1); 65 | } 66 | 67 | pub fn insert( 68 | &self, 69 | hash: Hash, 70 | depth: Depth, 71 | score: TTScore, 72 | best_move: Option, 73 | bound: Bound, 74 | eval: Option, 75 | ) { 76 | let mut replace_age = None; 77 | let mut age_depth = Depth::max_value(); 78 | let mut replace_depth = None; 79 | let mut lowest_depth = Depth::max_value(); 80 | let mut replace = 0; 81 | 82 | { 83 | let entries = unsafe { &self.table.get_unchecked((hash & self.bitmask) as usize).0 }; 84 | for (i, atomic_entry) in entries.iter().enumerate() { 85 | let entry = atomic_entry.read(); 86 | if entry.key == (hash >> 32) as u32 { 87 | if bound != EXACT_BOUND && depth < entry.depth - 3 * INC_PLY { 88 | return; 89 | } 90 | 91 | replace = i; 92 | replace_age = None; 93 | replace_depth = None; 94 | break; 95 | } 96 | 97 | if self.generation != entry.generation && entry.depth < age_depth { 98 | age_depth = entry.depth; 99 | replace_age = Some(i); 100 | } 101 | 102 | if entry.depth < lowest_depth { 103 | replace_depth = Some(i); 104 | lowest_depth = entry.depth; 105 | } 106 | } 107 | } 108 | 109 | if let Some(i) = replace_age { 110 | replace = i; 111 | } else if let Some(i) = replace_depth { 112 | replace = i; 113 | } 114 | 115 | let mut flags = 0; 116 | if eval.is_some() { 117 | flags |= FLAG_HAS_SCORE; 118 | } 119 | 120 | if best_move.is_some() { 121 | flags |= FLAG_HAS_MOVE; 122 | } 123 | 124 | unsafe { 125 | self.table.get_unchecked((hash & self.bitmask) as usize).0[replace].write(&TTEntry { 126 | key: (hash >> 32) as u32, 127 | depth, 128 | score, 129 | best_move: best_move.map_or(TTMove { from: 0, to: 0 }, TTMove::from), 130 | bound, 131 | generation: self.generation, 132 | eval: eval.unwrap_or(0), 133 | flags, 134 | _pad: 0, 135 | }) 136 | }; 137 | } 138 | 139 | pub fn get(&self, hash: Hash) -> Option { 140 | for atomic_entry in unsafe { &self.table.get_unchecked((hash & self.bitmask) as usize).0 } { 141 | let mut entry = atomic_entry.read(); 142 | if entry.key == (hash >> 32) as u32 { 143 | entry.generation = self.generation; 144 | atomic_entry.write(&entry); 145 | return Some(entry); 146 | } 147 | } 148 | 149 | None 150 | } 151 | } 152 | 153 | #[repr(align(64))] 154 | pub struct Bucket([AtomicU128; NUM_CLUSTERS]); 155 | const NUM_CLUSTERS: usize = 4; 156 | 157 | impl Default for Bucket { 158 | fn default() -> Self { 159 | Bucket([ 160 | AtomicU128::default(), 161 | AtomicU128::default(), 162 | AtomicU128::default(), 163 | AtomicU128::default(), 164 | ]) 165 | } 166 | } 167 | 168 | const FLAG_HAS_SCORE: u8 = 0x1; 169 | const FLAG_HAS_MOVE: u8 = 0x2; 170 | 171 | struct AtomicU128(AtomicU64, AtomicU64); 172 | 173 | impl AtomicU128 { 174 | fn read(&self) -> TTEntry { 175 | ( 176 | self.0.load(Ordering::Relaxed), 177 | self.1.load(Ordering::Relaxed), 178 | ) 179 | .into() 180 | } 181 | 182 | fn write(&self, entry: &TTEntry) { 183 | let (a, b): (u64, u64) = entry.into(); 184 | self.0.store(a, Ordering::Relaxed); 185 | self.1.store(b, Ordering::Relaxed); 186 | } 187 | } 188 | 189 | impl Default for AtomicU128 { 190 | fn default() -> Self { 191 | AtomicU128(AtomicU64::default(), AtomicU64::default()) 192 | } 193 | } 194 | 195 | #[repr(align(16))] 196 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 197 | pub struct TTEntry { 198 | key: u32, // 4 byte 199 | pub best_move: TTMove, // 2 byte 200 | pub depth: Depth, // 2 byte 201 | pub score: TTScore, // 2 byte 202 | pub eval: Score, // 2 byte 203 | pub bound: Bound, // 1 byte 204 | generation: u8, // 1 byte 205 | flags: u8, // 1 byte 206 | _pad: u8, // 1 byte 207 | } 208 | 209 | impl TTEntry { 210 | pub fn get_eval(&self) -> Option { 211 | if self.flags & FLAG_HAS_SCORE > 0 { 212 | Some(self.eval) 213 | } else { 214 | None 215 | } 216 | } 217 | 218 | pub fn has_move(&self) -> bool { 219 | self.flags & FLAG_HAS_MOVE > 0 220 | } 221 | } 222 | 223 | impl From<(u64, u64)> for TTEntry { 224 | fn from(v: (u64, u64)) -> Self { 225 | unsafe { std::mem::transmute(v) } 226 | } 227 | } 228 | 229 | impl From<&TTEntry> for (u64, u64) { 230 | fn from(v: &TTEntry) -> Self { 231 | unsafe { std::mem::transmute(*v) } 232 | } 233 | } 234 | 235 | impl Default for TTEntry { 236 | fn default() -> Self { 237 | TTEntry { 238 | key: 0, 239 | depth: 0, 240 | score: TTScore(0), 241 | eval: 0, 242 | best_move: TTMove { from: 0, to: 0 }, 243 | bound: 0, 244 | generation: 0, 245 | flags: 0, 246 | _pad: 0, 247 | } 248 | } 249 | } 250 | 251 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 252 | pub struct TTMove { 253 | from: u8, // bits 0-5: from positions, bit 6: capture, bit 7: en passant 254 | to: u8, // bits 0-5: to position, bits 6-7 promotion piece 255 | } 256 | 257 | const SQUARE_MASK: u8 = 0b0011_1111; 258 | const CAPTURE_FLAG: u8 = 0b0100_0000; 259 | const EN_PASSANT_FLAG: u8 = 0b1000_0000; 260 | const PROMOTION_MASK: u8 = 0b1100_0000; 261 | const PROMOTION_QUEEN: u8 = 0b1100_0000; 262 | const PROMOTION_ROOK: u8 = 0b1000_0000; 263 | const PROMOTION_BISHOP: u8 = 0b0100_0000; 264 | const PROMOTION_KNIGHT: u8 = 0b0000_0000; 265 | 266 | impl TTMove { 267 | // Expands this `TTMove` to a `Move`value. 268 | pub fn expand(self, pos: &Position) -> Option { 269 | let mut result = Move { 270 | from: Square::from(self.from & SQUARE_MASK), 271 | to: Square::from(self.to & SQUARE_MASK), 272 | piece: pos.find_piece(Square::from(self.from & SQUARE_MASK))?, 273 | captured: None, 274 | promoted: None, 275 | en_passant: self.from & EN_PASSANT_FLAG > 0, 276 | }; 277 | 278 | if self.from & CAPTURE_FLAG > 0 { 279 | if result.en_passant { 280 | result.captured = Some(Piece::Pawn); 281 | } else { 282 | result.captured = pos.find_piece(result.to); 283 | result.captured?; 284 | } 285 | } 286 | 287 | if result.piece == Piece::Pawn && (result.to.rank() == 0 || result.to.rank() == 7) { 288 | match self.to & PROMOTION_MASK { 289 | PROMOTION_QUEEN => result.promoted = Some(Piece::Queen), 290 | PROMOTION_ROOK => result.promoted = Some(Piece::Rook), 291 | PROMOTION_BISHOP => result.promoted = Some(Piece::Bishop), 292 | PROMOTION_KNIGHT => result.promoted = Some(Piece::Knight), 293 | _ => {} 294 | } 295 | } 296 | 297 | Some(result) 298 | } 299 | } 300 | 301 | impl From for TTMove { 302 | fn from(mov: Move) -> TTMove { 303 | let from: u8 = mov.from.into(); 304 | let to: u8 = mov.to.into(); 305 | let mut result = TTMove { 306 | from: from & SQUARE_MASK, 307 | to: to & SQUARE_MASK, 308 | }; 309 | 310 | if mov.captured.is_some() { 311 | result.from |= CAPTURE_FLAG; 312 | } 313 | 314 | if mov.en_passant { 315 | result.from |= EN_PASSANT_FLAG; 316 | } 317 | 318 | match mov.promoted { 319 | Some(Piece::Queen) => result.to |= PROMOTION_QUEEN, 320 | Some(Piece::Rook) => result.to |= PROMOTION_ROOK, 321 | Some(Piece::Bishop) => result.to |= PROMOTION_BISHOP, 322 | Some(Piece::Knight) => result.to |= PROMOTION_KNIGHT, 323 | _ => {} 324 | } 325 | 326 | result 327 | } 328 | } 329 | 330 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 331 | pub struct TTScore(Score); 332 | 333 | impl TTScore { 334 | pub fn to_score(self, ply: Ply) -> Score { 335 | if self.0 < -MATE_SCORE + MAX_PLY { 336 | self.0 + ply 337 | } else if self.0 > MATE_SCORE - MAX_PLY { 338 | self.0 - ply 339 | } else { 340 | self.0 341 | } 342 | } 343 | 344 | pub fn from_score(score: Score, ply: Ply) -> TTScore { 345 | if score < -MATE_SCORE + MAX_PLY { 346 | TTScore(score - ply) 347 | } else if score > MATE_SCORE - MAX_PLY { 348 | TTScore(score + ply) 349 | } else { 350 | TTScore(score) 351 | } 352 | } 353 | } 354 | 355 | pub type Bound = u8; 356 | pub const LOWER_BOUND: Bound = 1; 357 | pub const UPPER_BOUND: Bound = 2; 358 | pub const EXACT_BOUND: Bound = LOWER_BOUND | UPPER_BOUND; 359 | 360 | #[cfg(test)] 361 | mod tests { 362 | use super::*; 363 | 364 | #[test] 365 | fn test_size_of_structs() { 366 | assert_eq!(::std::mem::size_of::(), 16); 367 | assert_eq!(::std::mem::size_of::(), 64); 368 | } 369 | 370 | #[test] 371 | fn test_align_of_structs() { 372 | assert_eq!(::std::mem::align_of::(), 16); 373 | assert_eq!(::std::mem::align_of::(), 64); 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /src/search_controller.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | use std::sync::atomic::AtomicBool; 18 | use std::sync::{self, Arc}; 19 | 20 | use crossbeam_utils::thread; 21 | 22 | #[cfg(feature = "fathom")] 23 | use crate::fathom; 24 | use crate::hash::Hasher; 25 | use crate::movegen::{Move, MoveGenerator, MoveList}; 26 | use crate::position::{Position, STARTING_POSITION}; 27 | use crate::repetitions::Repetitions; 28 | use crate::search::{Depth, Search, INC_PLY}; 29 | use crate::time::TimeControl; 30 | use crate::tt::{self, TT}; 31 | use crate::uci::{GoParams, UciCommand}; 32 | 33 | #[derive(Clone, Debug)] 34 | pub struct PersistentOptions { 35 | hash_bits: u64, 36 | pub show_pv_board: bool, 37 | pub threads: usize, 38 | pub move_overhead: u64, 39 | pub syzygy_directories: Vec, 40 | pub syzygy_probe_depth: Depth, 41 | } 42 | 43 | impl Default for PersistentOptions { 44 | fn default() -> Self { 45 | PersistentOptions { 46 | hash_bits: 14, 47 | show_pv_board: false, 48 | threads: 1, 49 | move_overhead: 10, 50 | syzygy_directories: Vec::new(), 51 | syzygy_probe_depth: 0, 52 | } 53 | } 54 | } 55 | 56 | pub struct SearchController { 57 | abort: Arc, 58 | node_count: u64, 59 | hasher: Hasher, 60 | options: PersistentOptions, 61 | position: Position, 62 | time_control: TimeControl, 63 | tt: TT, 64 | repetitions: Repetitions, 65 | } 66 | 67 | impl SearchController { 68 | pub fn new(position: Position, abort: Arc) -> SearchController { 69 | let mut controller = SearchController { 70 | abort, 71 | node_count: 0, 72 | hasher: Hasher::new(), 73 | options: PersistentOptions::default(), 74 | position: position.clone(), 75 | time_control: TimeControl::Infinite, 76 | tt: TT::new(14), 77 | repetitions: Repetitions::new(100), 78 | }; 79 | 80 | controller.handle_position(position, vec![]); 81 | controller 82 | } 83 | 84 | pub fn get_best_move(&mut self) -> Move { 85 | self.tt.next_generation(); 86 | 87 | let threads = self.options.threads; 88 | let mut main_thread = Search::new( 89 | Arc::clone(&self.abort), 90 | self.hasher.clone(), 91 | self.options.clone(), 92 | self.position.clone(), 93 | self.time_control, 94 | &self.tt, 95 | self.repetitions.clone(), 96 | ); 97 | 98 | let mov = thread::scope(|s| { 99 | main_thread.prepare_search(); 100 | 101 | for id in 1..threads { 102 | let mut thread = main_thread.clone(); 103 | thread.id = id; 104 | thread.set_time_control(TimeControl::Infinite); 105 | s.builder() 106 | .name(format!("Helper thread #{:>3}", id)) 107 | .stack_size(8 * 1024 * 1024) 108 | .spawn(move |_| thread.iterative_deepening()) 109 | .unwrap(); 110 | } 111 | 112 | main_thread.iterative_deepening() 113 | }) 114 | .unwrap(); 115 | 116 | self.node_count = main_thread.visited_nodes; 117 | 118 | mov 119 | } 120 | 121 | pub fn get_node_count(&self) -> u64 { 122 | self.node_count 123 | } 124 | 125 | pub fn make_move(&mut self, mov: Move) { 126 | self.hasher.make_move(&self.position, mov); 127 | self.position.make_move(mov); 128 | 129 | if self.position.details.halfmove == 0 { 130 | self.repetitions.irreversible_move(); 131 | } 132 | self.repetitions.push_position(self.hasher.get_hash()); 133 | } 134 | 135 | pub fn resize_tt(&mut self, bits: u64) { 136 | self.options.hash_bits = bits; 137 | self.tt = TT::new(self.options.hash_bits); 138 | } 139 | 140 | pub fn set_time_control(&mut self, tc: TimeControl) { 141 | self.time_control = tc; 142 | } 143 | 144 | pub fn looping(&mut self, main_rx: sync::mpsc::Receiver) { 145 | for cmd in main_rx { 146 | match cmd { 147 | UciCommand::Unknown(cmd) => eprintln!("Unknown command: {}", cmd), 148 | UciCommand::UciNewGame => self.handle_ucinewgame(), 149 | UciCommand::Uci => self.handle_uci(), 150 | UciCommand::IsReady => self.handle_isready(), 151 | UciCommand::SetOption(name, value) => self.handle_setoption(name, value), 152 | UciCommand::Position(pos, moves) => self.handle_position(pos, moves), 153 | UciCommand::Go(params) => self.handle_go(params), 154 | UciCommand::ShowMoves => self.handle_showmoves(), 155 | UciCommand::Debug => self.handle_d(), 156 | UciCommand::TT => self.handle_tt(), 157 | UciCommand::Perft(depth) => self.handle_perft(depth), 158 | _ => eprintln!("Unexpected uci command"), 159 | } 160 | } 161 | } 162 | 163 | fn handle_ucinewgame(&mut self) { 164 | self.position = STARTING_POSITION; 165 | self.hasher.from_position(&self.position); 166 | self.tt = TT::new(self.options.hash_bits); 167 | self.repetitions = Repetitions::new(100); 168 | self.repetitions.push_position(self.hasher.get_hash()); 169 | } 170 | 171 | fn handle_uci(&mut self) { 172 | println!("id name Asymptote 0.8"); 173 | println!("id author Maximilian Lupke"); 174 | println!("option name Hash type spin default 1 min 0 max 65536"); 175 | println!("option name Threads type spin default 1 min 1 max 256"); 176 | println!("option name ShowPVBoard type check default false"); 177 | println!("option name MoveOverhead type spin default 10 min 0 max 10000"); 178 | println!("option name SyzygyPath type string default "); 179 | println!("option name SyzygyProbeDepth type spin default 0 min 0 max 127"); 180 | self.handle_ucinewgame(); 181 | println!("uciok"); 182 | } 183 | 184 | fn handle_isready(&self) { 185 | println!("readyok"); 186 | } 187 | 188 | fn handle_go(&mut self, params: GoParams) { 189 | self.time_control = params.time_control; 190 | let bestmove = self.get_best_move(); 191 | println!("bestmove {}", bestmove.to_algebraic()); 192 | } 193 | 194 | fn handle_position(&mut self, pos: Position, moves: Vec) { 195 | self.position = pos; 196 | self.hasher.from_position(&self.position); 197 | 198 | self.repetitions.clear(); 199 | self.repetitions.push_position(self.hasher.get_hash()); 200 | 201 | for mov in &moves { 202 | let mov = Move::from_algebraic(&self.position, mov); 203 | self.make_move(mov); 204 | } 205 | } 206 | 207 | fn handle_setoption(&mut self, name: String, value: String) { 208 | match name.as_ref() { 209 | "hash" => { 210 | if let Ok(mb) = value.parse::() { 211 | let hash_buckets = 1024 * 1024 * mb / 64; // 64 bytes per hash bucket 212 | let power_of_two = (hash_buckets + 1).next_power_of_two() / 2; 213 | let bits = power_of_two.trailing_zeros(); 214 | self.tt = TT::new(u64::from(bits)); 215 | self.options.hash_bits = u64::from(bits); 216 | } else { 217 | eprintln!("Unable to parse value '{}' as integer", value); 218 | } 219 | } 220 | "threads" => { 221 | if let Ok(threads) = value.parse::() { 222 | self.options.threads = threads; 223 | } else { 224 | eprintln!("Unable to parse value '{}' as integer", value); 225 | } 226 | } 227 | "showpvboard" => { 228 | self.options.show_pv_board = value.eq_ignore_ascii_case("true"); 229 | } 230 | "moveoverhead" => { 231 | if let Ok(move_overhead) = value.parse::() { 232 | self.options.move_overhead = move_overhead; 233 | } else { 234 | eprintln!("Unable to parse value '{}' as integer", value); 235 | } 236 | } 237 | "syzygypath" => { 238 | #[cfg(not(feature = "fathom"))] 239 | { 240 | println!("info string Error: Not Implemented"); 241 | } 242 | 243 | #[cfg(feature = "fathom")] 244 | { 245 | if unsafe { fathom::init(value) } { 246 | println!("info string found {}-piece Syzygy Tablebases", unsafe { 247 | fathom::max_pieces() 248 | }); 249 | } else { 250 | println!("info string Error while loading Syzygy Tablebases"); 251 | } 252 | } 253 | } 254 | "syzygyprobedepth" => { 255 | if let Ok(plies) = value.parse::() { 256 | self.options.syzygy_probe_depth = plies as Depth * INC_PLY; 257 | } else { 258 | eprintln!("Unable to parse value '{}' as integer", value); 259 | } 260 | } 261 | _ => { 262 | eprintln!("Unrecognized option {}", name); 263 | } 264 | } 265 | } 266 | 267 | fn handle_showmoves(&mut self) { 268 | println!("Pseudo-legal moves"); 269 | let mut moves = MoveList::new(); 270 | MoveGenerator::from(&self.position).all_moves(&mut moves); 271 | for &mov in &moves { 272 | print!("{} ", mov.to_algebraic()); 273 | } 274 | println!("\n"); 275 | 276 | println!("Legal moves"); 277 | let mut moves = MoveList::new(); 278 | MoveGenerator::from(&self.position).all_moves(&mut moves); 279 | for &mov in &moves { 280 | if self.position.move_is_legal(mov) { 281 | print!("{} ", mov.to_algebraic()); 282 | } 283 | } 284 | println!(); 285 | } 286 | 287 | fn handle_d(&self) { 288 | self.position.print(""); 289 | 290 | #[cfg(feature = "fathom")] 291 | { 292 | let state = (&self.position).into(); 293 | if let Some(probe_result) = unsafe { fathom::probe_root(&state) } { 294 | println!("info Syzygy WDL: {:?}", probe_result.wdl); 295 | println!("info Syzygy DTZ: {:?}", probe_result.dtz); 296 | } 297 | } 298 | } 299 | 300 | fn handle_tt(&mut self) { 301 | println!("Current hash: 0x{:0>8x}", self.hasher.get_hash()); 302 | let tt = self.tt.get(self.hasher.get_hash()); 303 | if let Some(tt) = tt { 304 | if let Some(best_move) = tt.best_move.expand(&self.position) { 305 | println!("Best move: {}", best_move.to_algebraic()); 306 | print!("Score: "); 307 | if tt.bound == tt::EXACT_BOUND { 308 | println!("= {:?}", tt.score); 309 | } else if tt.bound & tt::LOWER_BOUND > 0 { 310 | println!("> {:?}", tt.score); 311 | } else { 312 | println!("< {:?}", tt.score); 313 | } 314 | println!("Depth: {} ({} plies)", tt.depth, tt.depth / INC_PLY); 315 | } else { 316 | println!("No TT entry."); 317 | } 318 | } else { 319 | println!("No TT entry."); 320 | } 321 | } 322 | 323 | fn handle_perft(&mut self, depth: usize) { 324 | let mut thread = Search::new( 325 | Arc::clone(&self.abort), 326 | self.hasher.clone(), 327 | self.options.clone(), 328 | self.position.clone(), 329 | self.time_control, 330 | &self.tt, 331 | self.repetitions.clone(), 332 | ); 333 | thread.perft(depth); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /fathom/src/tbprobe.h: -------------------------------------------------------------------------------- 1 | /* 2 | * tbprobe.h 3 | * (C) 2015 basil, all rights reserved, 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the 10 | * Software is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #ifndef TBPROBE_H 25 | #define TBPROBE_H 26 | 27 | #include 28 | 29 | #ifdef __cplusplus 30 | extern "C" 31 | { 32 | #endif 33 | 34 | #ifndef TB_NO_STDINT 35 | #include 36 | #else 37 | typedef unsigned char uint8_t; 38 | typedef unsigned short uint16_t; 39 | typedef unsigned uint32_t; 40 | typedef long long unsigned uint64_t; 41 | typedef char int8_t; 42 | typedef short int16_t; 43 | typedef int int32_t; 44 | typedef long long int64_t; 45 | #endif 46 | 47 | #ifndef TB_NO_STDBOOL 48 | #include 49 | #else 50 | #ifndef __cplusplus 51 | typedef uint8_t bool; 52 | #define true 1 53 | #define false 0 54 | #endif 55 | #endif 56 | 57 | /* 58 | * Internal definitions. Do not call these functions directly. 59 | */ 60 | extern bool tb_init_impl(const char *_path); 61 | extern unsigned tb_probe_wdl_impl( 62 | uint64_t _white, 63 | uint64_t _black, 64 | uint64_t _kings, 65 | uint64_t _queens, 66 | uint64_t _rooks, 67 | uint64_t _bishops, 68 | uint64_t _knights, 69 | uint64_t _pawns, 70 | unsigned _ep, 71 | bool _turn); 72 | extern unsigned tb_probe_root_impl( 73 | uint64_t _white, 74 | uint64_t _black, 75 | uint64_t _kings, 76 | uint64_t _queens, 77 | uint64_t _rooks, 78 | uint64_t _bishops, 79 | uint64_t _knights, 80 | uint64_t _pawns, 81 | unsigned _rule50, 82 | unsigned _ep, 83 | bool _turn, 84 | unsigned *_results); 85 | 86 | /****************************************************************************/ 87 | /* MAIN API */ 88 | /****************************************************************************/ 89 | 90 | #define TB_MAX_MOVES (192+1) 91 | #define TB_MAX_CAPTURES 64 92 | #define TB_MAX_PLY 256 93 | #define TB_CASTLING_K 0x1 /* White king-side. */ 94 | #define TB_CASTLING_Q 0x2 /* White queen-side. */ 95 | #define TB_CASTLING_k 0x4 /* Black king-side. */ 96 | #define TB_CASTLING_q 0x8 /* Black queen-side. */ 97 | 98 | #define TB_LOSS 0 /* LOSS */ 99 | #define TB_BLESSED_LOSS 1 /* LOSS but 50-move draw */ 100 | #define TB_DRAW 2 /* DRAW */ 101 | #define TB_CURSED_WIN 3 /* WIN but 50-move draw */ 102 | #define TB_WIN 4 /* WIN */ 103 | 104 | #define TB_PROMOTES_NONE 0 105 | #define TB_PROMOTES_QUEEN 1 106 | #define TB_PROMOTES_ROOK 2 107 | #define TB_PROMOTES_BISHOP 3 108 | #define TB_PROMOTES_KNIGHT 4 109 | 110 | #define TB_RESULT_WDL_MASK 0x0000000F 111 | #define TB_RESULT_TO_MASK 0x000003F0 112 | #define TB_RESULT_FROM_MASK 0x0000FC00 113 | #define TB_RESULT_PROMOTES_MASK 0x00070000 114 | #define TB_RESULT_EP_MASK 0x00080000 115 | #define TB_RESULT_DTZ_MASK 0xFFF00000 116 | #define TB_RESULT_WDL_SHIFT 0 117 | #define TB_RESULT_TO_SHIFT 4 118 | #define TB_RESULT_FROM_SHIFT 10 119 | #define TB_RESULT_PROMOTES_SHIFT 16 120 | #define TB_RESULT_EP_SHIFT 19 121 | #define TB_RESULT_DTZ_SHIFT 20 122 | 123 | #define TB_GET_WDL(_res) \ 124 | (((_res) & TB_RESULT_WDL_MASK) >> TB_RESULT_WDL_SHIFT) 125 | #define TB_GET_TO(_res) \ 126 | (((_res) & TB_RESULT_TO_MASK) >> TB_RESULT_TO_SHIFT) 127 | #define TB_GET_FROM(_res) \ 128 | (((_res) & TB_RESULT_FROM_MASK) >> TB_RESULT_FROM_SHIFT) 129 | #define TB_GET_PROMOTES(_res) \ 130 | (((_res) & TB_RESULT_PROMOTES_MASK) >> TB_RESULT_PROMOTES_SHIFT) 131 | #define TB_GET_EP(_res) \ 132 | (((_res) & TB_RESULT_EP_MASK) >> TB_RESULT_EP_SHIFT) 133 | #define TB_GET_DTZ(_res) \ 134 | (((_res) & TB_RESULT_DTZ_MASK) >> TB_RESULT_DTZ_SHIFT) 135 | 136 | #define TB_SET_WDL(_res, _wdl) \ 137 | (((_res) & ~TB_RESULT_WDL_MASK) | \ 138 | (((_wdl) << TB_RESULT_WDL_SHIFT) & TB_RESULT_WDL_MASK)) 139 | #define TB_SET_TO(_res, _to) \ 140 | (((_res) & ~TB_RESULT_TO_MASK) | \ 141 | (((_to) << TB_RESULT_TO_SHIFT) & TB_RESULT_TO_MASK)) 142 | #define TB_SET_FROM(_res, _from) \ 143 | (((_res) & ~TB_RESULT_FROM_MASK) | \ 144 | (((_from) << TB_RESULT_FROM_SHIFT) & TB_RESULT_FROM_MASK)) 145 | #define TB_SET_PROMOTES(_res, _promotes) \ 146 | (((_res) & ~TB_RESULT_PROMOTES_MASK) | \ 147 | (((_promotes) << TB_RESULT_PROMOTES_SHIFT) & TB_RESULT_PROMOTES_MASK)) 148 | #define TB_SET_EP(_res, _ep) \ 149 | (((_res) & ~TB_RESULT_EP_MASK) | \ 150 | (((_ep) << TB_RESULT_EP_SHIFT) & TB_RESULT_EP_MASK)) 151 | #define TB_SET_DTZ(_res, _dtz) \ 152 | (((_res) & ~TB_RESULT_DTZ_MASK) | \ 153 | (((_dtz) << TB_RESULT_DTZ_SHIFT) & TB_RESULT_DTZ_MASK)) 154 | 155 | #define TB_RESULT_CHECKMATE TB_SET_WDL(0, TB_WIN) 156 | #define TB_RESULT_STALEMATE TB_SET_WDL(0, TB_DRAW) 157 | #define TB_RESULT_FAILED 0xFFFFFFFF 158 | 159 | /* 160 | * The tablebase can be probed for any position where #pieces <= TB_LARGEST. 161 | */ 162 | extern unsigned TB_LARGEST; 163 | 164 | /* 165 | * Initialize the tablebase. 166 | * 167 | * PARAMETERS: 168 | * - path: 169 | * The tablebase PATH string. 170 | * 171 | * RETURN: 172 | * - true=succes, false=failed. The TB_LARGEST global will also be 173 | * initialized. If no tablebase files are found, then `true' is returned 174 | * and TB_LARGEST is set to zero. 175 | */ 176 | bool tb_init(const char *_path); 177 | 178 | /* 179 | * Free any resources allocated by tb_init 180 | */ 181 | void tb_free(void); 182 | 183 | /* 184 | * Probe the Win-Draw-Loss (WDL) table. 185 | * 186 | * PARAMETERS: 187 | * - white, black, kings, queens, rooks, bishops, knights, pawns: 188 | * The current position (bitboards). 189 | * - rule50: 190 | * The 50-move half-move clock. 191 | * - castling: 192 | * Castling rights. Set to zero if no castling is possible. 193 | * - ep: 194 | * The en passant square (if exists). Set to zero if there is no en passant 195 | * square. 196 | * - turn: 197 | * true=white, false=black 198 | * 199 | * RETURN: 200 | * - One of {TB_LOSS, TB_BLESSED_LOSS, TB_DRAW, TB_CURSED_WIN, TB_WIN}. 201 | * Otherwise returns TB_RESULT_FAILED if the probe failed. 202 | * 203 | * NOTES: 204 | * - Engines should use this function during search. 205 | * - This function is thread safe assuming TB_NO_THREADS is disabled. 206 | */ 207 | static inline unsigned tb_probe_wdl( 208 | uint64_t _white, 209 | uint64_t _black, 210 | uint64_t _kings, 211 | uint64_t _queens, 212 | uint64_t _rooks, 213 | uint64_t _bishops, 214 | uint64_t _knights, 215 | uint64_t _pawns, 216 | unsigned _rule50, 217 | unsigned _castling, 218 | unsigned _ep, 219 | bool _turn) 220 | { 221 | if (_castling != 0) 222 | return TB_RESULT_FAILED; 223 | if (_rule50 != 0) 224 | return TB_RESULT_FAILED; 225 | return tb_probe_wdl_impl(_white, _black, _kings, _queens, _rooks, 226 | _bishops, _knights, _pawns, _ep, _turn); 227 | } 228 | 229 | /* 230 | * Probe the Distance-To-Zero (DTZ) table. 231 | * 232 | * PARAMETERS: 233 | * - white, black, kings, queens, rooks, bishops, knights, pawns: 234 | * The current position (bitboards). 235 | * - rule50: 236 | * The 50-move half-move clock. 237 | * - castling: 238 | * Castling rights. Set to zero if no castling is possible. 239 | * - ep: 240 | * The en passant square (if exists). Set to zero if there is no en passant 241 | * square. 242 | * - turn: 243 | * true=white, false=black 244 | * - results (OPTIONAL): 245 | * Alternative results, one for each possible legal move. The passed array 246 | * must be TB_MAX_MOVES in size. 247 | * If alternative results are not desired then set results=NULL. 248 | * 249 | * RETURN: 250 | * - A TB_RESULT value comprising: 251 | * 1) The WDL value (TB_GET_WDL) 252 | * 2) The suggested move (TB_GET_FROM, TB_GET_TO, TB_GET_PROMOTES, TB_GET_EP) 253 | * 3) The DTZ value (TB_GET_DTZ) 254 | * The suggested move is guaranteed to preserved the WDL value. 255 | * 256 | * Otherwise: 257 | * 1) TB_RESULT_STALEMATE is returned if the position is in stalemate. 258 | * 2) TB_RESULT_CHECKMATE is returned if the position is in checkmate. 259 | * 3) TB_RESULT_FAILED is returned if the probe failed. 260 | * 261 | * If results!=NULL, then a TB_RESULT for each legal move will be generated 262 | * and stored in the results array. The results array will be terminated 263 | * by TB_RESULT_FAILED. 264 | * 265 | * NOTES: 266 | * - Engines can use this function to probe at the root. This function should 267 | * not be used during search. 268 | * - DTZ tablebases can suggest unnatural moves, especially for losing 269 | * positions. Engines may prefer to traditional search combined with WDL 270 | * move filtering using the alternative results array. 271 | * - This function is NOT thread safe. For engines this function should only 272 | * be called once at the root per search. 273 | */ 274 | static inline unsigned tb_probe_root( 275 | uint64_t _white, 276 | uint64_t _black, 277 | uint64_t _kings, 278 | uint64_t _queens, 279 | uint64_t _rooks, 280 | uint64_t _bishops, 281 | uint64_t _knights, 282 | uint64_t _pawns, 283 | unsigned _rule50, 284 | unsigned _castling, 285 | unsigned _ep, 286 | bool _turn, 287 | unsigned *_results) 288 | { 289 | if (_castling != 0) 290 | return TB_RESULT_FAILED; 291 | return tb_probe_root_impl(_white, _black, _kings, _queens, _rooks, 292 | _bishops, _knights, _pawns, _rule50, _ep, _turn, _results); 293 | } 294 | 295 | typedef uint16_t TbMove; 296 | 297 | #define TB_MOVE_FROM(move) \ 298 | (((move) >> 6) & 0x3F) 299 | #define TB_MOVE_TO(move) \ 300 | ((move) & 0x3F) 301 | #define TB_MOVE_PROMOTES(move) \ 302 | (((move) >> 12) & 0x7) 303 | 304 | struct TbRootMove { 305 | TbMove move; 306 | TbMove pv[TB_MAX_PLY]; 307 | unsigned pvSize; 308 | int32_t tbScore, tbRank; 309 | }; 310 | 311 | struct TbRootMoves { 312 | unsigned size; 313 | struct TbRootMove moves[TB_MAX_MOVES]; 314 | }; 315 | 316 | /* 317 | * Use the DTZ tables to rank and score all root moves. 318 | * INPUT: as for tb_probe_root 319 | * OUTPUT: TbRootMoves structure is filled in. This contains 320 | * an array of TbRootMove structures. 321 | * Each structure instance contains a rank, a score, and a 322 | * predicted principal variation. 323 | * RETURN VALUE: 324 | * non-zero if ok, 0 means not all probes were successful 325 | * 326 | */ 327 | int tb_probe_root_dtz( 328 | uint64_t _white, 329 | uint64_t _black, 330 | uint64_t _kings, 331 | uint64_t _queens, 332 | uint64_t _rooks, 333 | uint64_t _bishops, 334 | uint64_t _knights, 335 | uint64_t _pawns, 336 | unsigned _rule50, 337 | unsigned _castling, 338 | unsigned _ep, 339 | bool _turn, 340 | bool hasRepeated, 341 | bool useRule50, 342 | struct TbRootMoves *_results); 343 | 344 | /* 345 | // Use the WDL tables to rank and score all root moves. 346 | // This is a fallback for the case that some or all DTZ tables are missing. 347 | * INPUT: as for tb_probe_root 348 | * OUTPUT: TbRootMoves structure is filled in. This contains 349 | * an array of TbRootMove structures. 350 | * Each structure instance contains a rank, a score, and a 351 | * predicted principal variation. 352 | * RETURN VALUE: 353 | * non-zero if ok, 0 means not all probes were successful 354 | * 355 | */ 356 | int tb_probe_root_wdl(uint64_t _white, 357 | uint64_t _black, 358 | uint64_t _kings, 359 | uint64_t _queens, 360 | uint64_t _rooks, 361 | uint64_t _bishops, 362 | uint64_t _knights, 363 | uint64_t _pawns, 364 | unsigned _rule50, 365 | unsigned _castling, 366 | unsigned _ep, 367 | bool _turn, 368 | bool useRule50, 369 | struct TbRootMoves *_results); 370 | 371 | /****************************************************************************/ 372 | /* HELPER API */ 373 | /****************************************************************************/ 374 | 375 | /* 376 | * The HELPER API provides some useful additional functions. It is optional 377 | * and can be disabled by defining TB_NO_HELPER_API. Engines should disable 378 | * the HELPER API. 379 | */ 380 | 381 | #ifndef TB_NO_HELPER_API 382 | 383 | extern unsigned tb_pop_count(uint64_t _bb); 384 | extern unsigned tb_lsb(uint64_t _bb); 385 | extern uint64_t tb_pop_lsb(uint64_t _bb); 386 | extern uint64_t tb_king_attacks(unsigned _square); 387 | extern uint64_t tb_queen_attacks(unsigned _square, uint64_t _occ); 388 | extern uint64_t tb_rook_attacks(unsigned _square, uint64_t _occ); 389 | extern uint64_t tb_bishop_attacks(unsigned _square, uint64_t _occ); 390 | extern uint64_t tb_knight_attacks(unsigned _square); 391 | extern uint64_t tb_pawn_attacks(unsigned _square, bool _color); 392 | 393 | #endif 394 | 395 | #ifdef __cplusplus 396 | } 397 | #endif 398 | 399 | #endif 400 | -------------------------------------------------------------------------------- /src/movegen.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | use crate::bitboard::*; 18 | use crate::eval::*; 19 | use crate::magic::{BISHOP_ATTACKS, MAGIC_TABLE, ROOK_ATTACKS}; 20 | use crate::position::*; 21 | 22 | pub type MoveList = arrayvec::ArrayVec; 23 | pub type ShortMoveList = arrayvec::ArrayVec; 24 | pub type ScoreList = arrayvec::ArrayVec; 25 | 26 | pub fn get_bishop_attacks_from(from: Square, blockers: Bitboard) -> Bitboard { 27 | unsafe { 28 | let magic = &BISHOP_ATTACKS[from]; 29 | *MAGIC_TABLE.get_unchecked(magic.index(blockers)) 30 | } 31 | } 32 | 33 | pub fn get_rook_attacks_from(from: Square, blockers: Bitboard) -> Bitboard { 34 | unsafe { 35 | let magic = &ROOK_ATTACKS[from]; 36 | *MAGIC_TABLE.get_unchecked(magic.index(blockers)) 37 | } 38 | } 39 | 40 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] 41 | #[repr(u8)] 42 | pub enum Piece { 43 | Pawn = 0, 44 | Knight, 45 | Bishop, 46 | Rook, 47 | Queen, 48 | King, 49 | } 50 | 51 | impl Piece { 52 | pub fn index(self) -> usize { 53 | self as usize 54 | } 55 | 56 | pub fn all() -> [Piece; 6] { 57 | [ 58 | Piece::Pawn, 59 | Piece::Knight, 60 | Piece::Bishop, 61 | Piece::Rook, 62 | Piece::Queen, 63 | Piece::King, 64 | ] 65 | } 66 | 67 | pub fn value(self) -> Score { 68 | match self { 69 | Piece::Pawn => eg(PAWN_SCORE), 70 | Piece::Knight => eg(KNIGHT_SCORE), 71 | Piece::Bishop => eg(BISHOP_SCORE), 72 | Piece::Rook => eg(ROOK_SCORE), 73 | Piece::Queen => eg(QUEEN_SCORE), 74 | Piece::King => 10000, 75 | } 76 | } 77 | 78 | pub fn see_value(self) -> Score { 79 | match self { 80 | Piece::Pawn => 120, 81 | Piece::Knight => 300, 82 | Piece::Bishop => 300, 83 | Piece::Rook => 550, 84 | Piece::Queen => 1000, 85 | Piece::King => 10000, 86 | } 87 | } 88 | } 89 | 90 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 91 | pub struct Move { 92 | pub from: Square, 93 | pub to: Square, 94 | 95 | pub piece: Piece, 96 | pub captured: Option, 97 | pub promoted: Option, 98 | pub en_passant: bool, 99 | } 100 | 101 | pub struct MoveGenerator<'p> { 102 | pub position: &'p Position, 103 | } 104 | 105 | impl<'p> MoveGenerator<'p> { 106 | pub fn good_captures( 107 | &mut self, 108 | moves: &mut MoveList, 109 | scores: &mut ScoreList, 110 | bad_moves: &mut MoveList, 111 | bad_scores: &mut ScoreList, 112 | ) { 113 | let all_pieces = self.position.all_pieces; 114 | let them = self.position.them(self.position.white_to_move); 115 | 116 | if self.position.details.checkers.more_than_one() { 117 | self.king(them & all_pieces, moves); 118 | } else if self.position.details.checkers.at_least_one() { 119 | let checkers = self.position.details.checkers; 120 | let ep = if self.position.details.en_passant != 255 { 121 | if self.position.white_to_move { 122 | Square::file_rank(self.position.details.en_passant, 5).to_bb() 123 | } else { 124 | Square::file_rank(self.position.details.en_passant, 2).to_bb() 125 | } 126 | } else { 127 | Bitboard::from(0) 128 | }; 129 | let promotion_rank = if self.position.white_to_move { 130 | RANK_8 131 | } else { 132 | RANK_1 133 | }; 134 | 135 | self.pawn(checkers | promotion_rank | ep, moves); 136 | self.knight(checkers, moves); 137 | self.bishop(checkers, moves); 138 | self.rook(checkers, moves); 139 | self.queen(checkers, moves); 140 | self.king(them & all_pieces, moves); 141 | } else { 142 | let ep = if self.position.details.en_passant != 255 { 143 | if self.position.white_to_move { 144 | Square::file_rank(self.position.details.en_passant, 5).to_bb() 145 | } else { 146 | Square::file_rank(self.position.details.en_passant, 2).to_bb() 147 | } 148 | } else { 149 | Bitboard::from(0) 150 | }; 151 | let promotion_rank = if self.position.white_to_move { 152 | RANK_8 153 | } else { 154 | RANK_1 155 | }; 156 | 157 | self.pawn(them & all_pieces | promotion_rank | ep, moves); 158 | self.knight(them & all_pieces, moves); 159 | self.bishop(them & all_pieces, moves); 160 | self.rook(them & all_pieces, moves); 161 | self.queen(them & all_pieces, moves); 162 | self.king(them & all_pieces, moves); 163 | } 164 | 165 | let mut i = 0; 166 | while i < moves.len() { 167 | let mov = moves[i]; 168 | if self.position.see(mov, 0) { 169 | scores.push(mov.mvv_lva_score()); 170 | i += 1; 171 | } else { 172 | bad_scores.push(mov.mvv_lva_score()); 173 | bad_moves.push(mov); 174 | moves.swap_remove(i); 175 | } 176 | } 177 | } 178 | 179 | pub fn quiet_moves(&self, moves: &mut MoveList) { 180 | if self.position.details.checkers.more_than_one() { 181 | self.king(!self.position.all_pieces, moves); 182 | return; 183 | } 184 | 185 | let promotion_rank = if self.position.white_to_move { 186 | RANK_8 187 | } else { 188 | RANK_1 189 | }; 190 | 191 | let ep = if self.position.details.en_passant != 255 { 192 | if self.position.white_to_move { 193 | Square::file_rank(self.position.details.en_passant, 5).to_bb() 194 | } else { 195 | Square::file_rank(self.position.details.en_passant, 2).to_bb() 196 | } 197 | } else { 198 | Bitboard::from(0) 199 | }; 200 | 201 | self.pawn(!self.position.all_pieces & !promotion_rank & !ep, moves); 202 | self.knight(!self.position.all_pieces, moves); 203 | self.bishop(!self.position.all_pieces, moves); 204 | self.rook(!self.position.all_pieces, moves); 205 | self.queen(!self.position.all_pieces, moves); 206 | self.king(!self.position.all_pieces, moves); 207 | } 208 | 209 | pub fn all_moves(&self, moves: &mut MoveList) { 210 | let us = self.position.us(self.position.white_to_move); 211 | let all = !us; 212 | self.pawn(all, moves); 213 | self.knight(all, moves); 214 | self.bishop(all, moves); 215 | self.rook(all, moves); 216 | self.queen(all, moves); 217 | self.king(all, moves); 218 | } 219 | 220 | pub fn pawn(&self, targets: Bitboard, moves: &mut MoveList) { 221 | let us = self.position.us(self.position.white_to_move); 222 | let them = self.position.them(self.position.white_to_move); 223 | let promoting = if self.position.white_to_move { 7 } else { 0 }; 224 | let rank3 = if self.position.white_to_move { 225 | RANK_3 226 | } else { 227 | RANK_6 228 | }; 229 | 230 | let wtm = self.position.white_to_move; 231 | 232 | let pawns = self.position.pawns() & us; 233 | let single_step_targets = pawns.forward(wtm, 1) & !self.position.all_pieces & targets; 234 | let double_step_targets = (pawns.forward(wtm, 1) & !self.position.all_pieces & rank3) 235 | .forward(wtm, 1) 236 | & !self.position.all_pieces 237 | & targets; 238 | let captures_left = pawns.forward(wtm, 1).left(1) & them & targets; 239 | let captures_right = pawns.forward(wtm, 1).right(1) & them & targets; 240 | 241 | for to in single_step_targets.squares() { 242 | if to.rank() == promoting { 243 | for promoted in &[Piece::Queen, Piece::Knight, Piece::Rook, Piece::Bishop] { 244 | moves.push(Move { 245 | from: to.backward(wtm, 1), 246 | to, 247 | piece: Piece::Pawn, 248 | captured: None, 249 | promoted: Some(*promoted), 250 | en_passant: false, 251 | }); 252 | } 253 | } else { 254 | moves.push(Move { 255 | from: to.backward(wtm, 1), 256 | to, 257 | piece: Piece::Pawn, 258 | captured: None, 259 | promoted: None, 260 | en_passant: false, 261 | }); 262 | } 263 | } 264 | 265 | for to in double_step_targets.squares() { 266 | moves.push(Move { 267 | from: to.backward(wtm, 2), 268 | to, 269 | piece: Piece::Pawn, 270 | captured: None, 271 | promoted: None, 272 | en_passant: false, 273 | }); 274 | } 275 | 276 | // en passant 277 | if self.position.details.en_passant != 255 { 278 | let en_passant_capturers_rank = if self.position.white_to_move { 279 | RANK_5 280 | } else { 281 | RANK_4 282 | }; 283 | let ep_square = if self.position.white_to_move { 284 | Square::file_rank(self.position.details.en_passant, 5) 285 | } else { 286 | Square::file_rank(self.position.details.en_passant, 2) 287 | }; 288 | let capturers = us 289 | & self.position.pawns() 290 | & EN_PASSANT_FILES[self.position.details.en_passant as usize] 291 | & en_passant_capturers_rank; 292 | 293 | if targets & ep_square { 294 | for from in capturers.squares() { 295 | moves.push(Move { 296 | from, 297 | to: Square::file_rank( 298 | self.position.details.en_passant, 299 | from.forward(wtm, 1).rank(), 300 | ), 301 | piece: Piece::Pawn, 302 | captured: Some(Piece::Pawn), 303 | promoted: None, 304 | en_passant: true, 305 | }); 306 | } 307 | } 308 | } 309 | 310 | // ordinary pawn captures (including promoting captures) 311 | // captures to the left (file b to file a, ...) 312 | for to in captures_left.squares() { 313 | let captured = self.position.find_piece(to); 314 | 315 | if to.rank() == promoting { 316 | for promoted in &[Piece::Queen, Piece::Knight, Piece::Rook, Piece::Bishop] { 317 | moves.push(Move { 318 | from: to.backward(self.position.white_to_move, 1).right(1), 319 | to, 320 | piece: Piece::Pawn, 321 | captured, 322 | promoted: Some(*promoted), 323 | en_passant: false, 324 | }); 325 | } 326 | } else { 327 | moves.push(Move { 328 | from: to.backward(self.position.white_to_move, 1).right(1), 329 | to, 330 | piece: Piece::Pawn, 331 | captured, 332 | promoted: None, 333 | en_passant: false, 334 | }); 335 | } 336 | } 337 | 338 | // captures to the right (file a to file b, ...) 339 | for to in captures_right.squares() { 340 | let captured = self.position.find_piece(to); 341 | 342 | if to.rank() == promoting { 343 | for promoted in &[Piece::Queen, Piece::Knight, Piece::Rook, Piece::Bishop] { 344 | moves.push(Move { 345 | from: to.backward(wtm, 1).left(1), 346 | to, 347 | piece: Piece::Pawn, 348 | captured, 349 | promoted: Some(*promoted), 350 | en_passant: false, 351 | }); 352 | } 353 | } else { 354 | moves.push(Move { 355 | from: to.backward(wtm, 1).left(1), 356 | to, 357 | piece: Piece::Pawn, 358 | captured, 359 | promoted: None, 360 | en_passant: false, 361 | }); 362 | } 363 | } 364 | } 365 | 366 | pub fn knight(&self, targets: Bitboard, moves: &mut MoveList) { 367 | let us = self.position.us(self.position.white_to_move); 368 | for from in (self.position.knights() & us).squares() { 369 | for to in (targets & self.knight_from(from)).squares() { 370 | moves.push(Move { 371 | from, 372 | to, 373 | piece: Piece::Knight, 374 | captured: self.position.find_piece(to), 375 | promoted: None, 376 | en_passant: false, 377 | }); 378 | } 379 | } 380 | } 381 | 382 | pub fn knight_from(&self, from: Square) -> Bitboard { 383 | KNIGHT_ATTACKS[from] 384 | } 385 | 386 | pub fn bishop(&self, targets: Bitboard, moves: &mut MoveList) { 387 | let us = self.position.us(self.position.white_to_move); 388 | for from in (self.position.bishops() & us).squares() { 389 | for to in (targets & get_bishop_attacks_from(from, self.position.all_pieces)).squares() 390 | { 391 | moves.push(Move { 392 | from, 393 | to, 394 | piece: Piece::Bishop, 395 | captured: self.position.find_piece(to), 396 | promoted: None, 397 | en_passant: false, 398 | }); 399 | } 400 | } 401 | } 402 | 403 | pub fn rook(&self, targets: Bitboard, moves: &mut MoveList) { 404 | let us = self.position.us(self.position.white_to_move); 405 | for from in (self.position.rooks() & us).squares() { 406 | for to in (targets & get_rook_attacks_from(from, self.position.all_pieces)).squares() { 407 | moves.push(Move { 408 | from, 409 | to, 410 | piece: Piece::Rook, 411 | captured: self.position.find_piece(to), 412 | promoted: None, 413 | en_passant: false, 414 | }); 415 | } 416 | } 417 | } 418 | 419 | pub fn queen(&self, targets: Bitboard, moves: &mut MoveList) { 420 | let us = self.position.us(self.position.white_to_move); 421 | for from in (self.position.queens() & us).squares() { 422 | for to in (targets 423 | & (get_bishop_attacks_from(from, self.position.all_pieces) 424 | | get_rook_attacks_from(from, self.position.all_pieces))) 425 | .squares() 426 | { 427 | moves.push(Move { 428 | from, 429 | to, 430 | piece: Piece::Queen, 431 | captured: self.position.find_piece(to), 432 | promoted: None, 433 | en_passant: false, 434 | }); 435 | } 436 | } 437 | } 438 | 439 | pub fn king(&self, targets: Bitboard, moves: &mut MoveList) { 440 | let us = self.position.us(self.position.white_to_move); 441 | let castle_kside; 442 | let castle_qside; 443 | if self.position.white_to_move { 444 | castle_kside = (self.position.details.castling & CASTLE_WHITE_KSIDE) > 0 445 | && (self.position.all_pieces & Bitboard::from(0x00_00_00_00_00_00_00_60)) 446 | .is_empty() 447 | && (self.position.rooks() & us & SQUARE_H1); 448 | castle_qside = (self.position.details.castling & CASTLE_WHITE_QSIDE) > 0 449 | && (self.position.all_pieces & Bitboard::from(0x00_00_00_00_00_00_00_0E)) 450 | .is_empty() 451 | && (self.position.rooks() & us & SQUARE_A1); 452 | } else { 453 | castle_kside = (self.position.details.castling & CASTLE_BLACK_KSIDE) > 0 454 | && (self.position.all_pieces & Bitboard::from(0x60_00_00_00_00_00_00_00)) 455 | .is_empty() 456 | && (self.position.rooks() & us & SQUARE_H8); 457 | castle_qside = (self.position.details.castling & CASTLE_BLACK_QSIDE) > 0 458 | && (self.position.all_pieces & Bitboard::from(0x0E_00_00_00_00_00_00_00)) 459 | .is_empty() 460 | && (self.position.rooks() & us & SQUARE_A8); 461 | } 462 | 463 | let from = self.position.king_sq(self.position.white_to_move); 464 | for to in (targets & self.king_from(from)).squares() { 465 | moves.push(Move { 466 | from, 467 | to, 468 | piece: Piece::King, 469 | captured: self.position.find_piece(to), 470 | promoted: None, 471 | en_passant: false, 472 | }); 473 | } 474 | 475 | // TODO: check king position? 476 | if castle_kside && targets & from.right(2) { 477 | moves.push(Move { 478 | from, 479 | to: from.right(2), 480 | piece: Piece::King, 481 | captured: None, 482 | promoted: None, 483 | en_passant: false, 484 | }); 485 | } 486 | 487 | if castle_qside && targets & from.left(2) { 488 | moves.push(Move { 489 | from, 490 | to: from.left(2), 491 | piece: Piece::King, 492 | captured: None, 493 | promoted: None, 494 | en_passant: false, 495 | }); 496 | } 497 | } 498 | 499 | pub fn king_from(&self, from: Square) -> Bitboard { 500 | KING_ATTACKS[from] 501 | } 502 | } 503 | 504 | impl<'p> From<&'p Position> for MoveGenerator<'p> { 505 | fn from(pos: &'p Position) -> Self { 506 | MoveGenerator { position: pos } 507 | } 508 | } 509 | 510 | impl Move { 511 | pub fn is_quiet(self) -> bool { 512 | self.captured.is_none() && self.promoted.is_none() 513 | } 514 | 515 | pub fn is_kingside_castle(self) -> bool { 516 | self.piece == Piece::King 517 | && (self.from == SQUARE_E1 || self.from == SQUARE_E8) 518 | && self.to == self.from.right(2) 519 | } 520 | 521 | pub fn is_queenside_castle(self) -> bool { 522 | self.piece == Piece::King 523 | && (self.from == SQUARE_E1 || self.from == SQUARE_E8) 524 | && self.to == self.from.left(2) 525 | } 526 | 527 | pub fn mvv_lva_score(self) -> i64 { 528 | let mut score = i64::from(self.captured.map_or(0, Piece::value)) * 128; 529 | if self.promoted == Some(Piece::Queen) { 530 | score += i64::from(Piece::Queen.value()); 531 | } 532 | score -= i64::from(self.piece.value()); 533 | score 534 | } 535 | 536 | pub fn from_algebraic(pos: &Position, alg: &str) -> Move { 537 | let mut from_rank = 0; 538 | let mut from_file = 0; 539 | let mut to_rank = 0; 540 | let mut to_file = 0; 541 | 542 | let letters: Vec<_> = "abcdefgh".chars().collect(); 543 | let numbers: Vec<_> = "12345678".chars().collect(); 544 | 545 | for (i, &letter) in letters.iter().enumerate() { 546 | if letter == alg.chars().nth(0).unwrap() { 547 | from_file = i; 548 | } 549 | 550 | if letter == alg.chars().nth(2).unwrap() { 551 | to_file = i; 552 | } 553 | } 554 | 555 | for (i, &number) in numbers.iter().enumerate() { 556 | if number == alg.chars().nth(1).unwrap() { 557 | from_rank = i; 558 | } 559 | 560 | if number == alg.chars().nth(3).unwrap() { 561 | to_rank = i; 562 | } 563 | } 564 | 565 | let from = Square::file_rank(from_file as u8, from_rank as u8); 566 | let to = Square::file_rank(to_file as u8, to_rank as u8); 567 | let piece = pos.find_piece(from).unwrap(); 568 | let captured; 569 | 570 | let en_passant; 571 | if piece == Piece::Pawn && !(pos.all_pieces & to) && from.file() != to.file() { 572 | en_passant = true; 573 | captured = Some(Piece::Pawn); 574 | } else { 575 | en_passant = false; 576 | captured = pos.find_piece(to); 577 | } 578 | 579 | let mut promoted = None; 580 | for &(sym, piece) in &[ 581 | ('q', Piece::Queen), 582 | ('n', Piece::Knight), 583 | ('r', Piece::Rook), 584 | ('b', Piece::Bishop), 585 | ] { 586 | if alg.chars().nth(4) == Some(sym) { 587 | promoted = Some(piece); 588 | break; 589 | } 590 | } 591 | 592 | Move { 593 | from, 594 | to, 595 | piece, 596 | captured, 597 | promoted, 598 | en_passant, 599 | } 600 | } 601 | 602 | pub fn to_algebraic(self) -> String { 603 | let mut alg = String::with_capacity(5); 604 | let letters: Vec<_> = "abcdefgh".chars().collect(); 605 | let numbers: Vec<_> = "12345678".chars().collect(); 606 | alg.push(letters[self.from.file() as usize]); 607 | alg.push(numbers[self.from.rank() as usize]); 608 | alg.push(letters[self.to.file() as usize]); 609 | alg.push(numbers[self.to.rank() as usize]); 610 | match self.promoted { 611 | Some(Piece::Queen) => alg.push('q'), 612 | Some(Piece::Knight) => alg.push('n'), 613 | Some(Piece::Rook) => alg.push('r'), 614 | Some(Piece::Bishop) => alg.push('b'), 615 | Some(x) => panic!("Invalid promotion piece: {:?}", x), 616 | None => {} 617 | } 618 | alg 619 | } 620 | } 621 | -------------------------------------------------------------------------------- /src/bitboard.rs: -------------------------------------------------------------------------------- 1 | /* Asymptote, a UCI chess engine 2 | Copyright (C) 2018-2022 Maximilian Lupke 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, Shr}; 18 | 19 | use crate::types::SquareMap; 20 | 21 | #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] 22 | pub struct Bitboard(pub u64); 23 | 24 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 25 | pub struct Square(u8); 26 | 27 | pub struct SquareIterator { 28 | bb: Bitboard, 29 | } 30 | 31 | // ========================================================= 32 | // Bitboard impls 33 | // ========================================================= 34 | impl Bitboard { 35 | pub fn forward(self, white_to_move: bool, ranks: u8) -> Self { 36 | if white_to_move { 37 | Bitboard(self.0 << (8 * ranks)) 38 | } else { 39 | Bitboard(self.0 >> (8 * ranks)) 40 | } 41 | } 42 | 43 | pub fn backward(self, white_to_move: bool, ranks: u8) -> Self { 44 | self.forward(!white_to_move, ranks) 45 | } 46 | 47 | pub fn right(self, files: u8) -> Self { 48 | (self & LEFT_FILES[8 - files as usize]) << files 49 | } 50 | 51 | pub fn left(self, files: u8) -> Self { 52 | (self & RIGHT_FILES[8 - files as usize]) >> files 53 | } 54 | 55 | pub fn popcount(self) -> usize { 56 | self.0.count_ones() as usize 57 | } 58 | 59 | pub fn lsb_bb(&mut self) -> Bitboard { 60 | Bitboard::from(self.0 ^ (self.0 & (self.0 - 1))) 61 | } 62 | 63 | pub fn is_empty(self) -> bool { 64 | self.0 == 0 65 | } 66 | 67 | pub fn at_least_one(self) -> bool { 68 | self.0 != 0 69 | } 70 | 71 | pub fn more_than_one(self) -> bool { 72 | self.0 & (self.0.wrapping_sub(1)) != 0 73 | } 74 | 75 | pub fn squares(self) -> SquareIterator { 76 | SquareIterator { bb: self } 77 | } 78 | } 79 | 80 | impl From for Bitboard { 81 | fn from(bb: u64) -> Self { 82 | Bitboard(bb) 83 | } 84 | } 85 | 86 | impl BitAnd for Bitboard { 87 | type Output = Self; 88 | fn bitand(self, other: Self) -> Self { 89 | Bitboard(self.0 & other.0) 90 | } 91 | } 92 | 93 | impl BitAnd for Bitboard { 94 | type Output = bool; 95 | fn bitand(self, other: Square) -> bool { 96 | self.0 & (1 << other.0) > 0 97 | } 98 | } 99 | 100 | impl BitOr for Bitboard { 101 | type Output = Self; 102 | fn bitor(self, other: Self) -> Self { 103 | Bitboard(self.0 | other.0) 104 | } 105 | } 106 | 107 | impl BitXor for Bitboard { 108 | type Output = Self; 109 | fn bitxor(self, other: Self) -> Self { 110 | Bitboard(self.0 ^ other.0) 111 | } 112 | } 113 | 114 | impl BitOrAssign for Bitboard { 115 | fn bitor_assign(&mut self, other: Self) { 116 | self.0 |= other.0; 117 | } 118 | } 119 | 120 | impl BitAndAssign for Bitboard { 121 | fn bitand_assign(&mut self, other: Self) { 122 | self.0 &= other.0; 123 | } 124 | } 125 | 126 | impl BitXorAssign for Bitboard { 127 | fn bitxor_assign(&mut self, other: Self) { 128 | self.0 ^= other.0; 129 | } 130 | } 131 | 132 | impl BitOrAssign for Bitboard { 133 | fn bitor_assign(&mut self, other: Square) { 134 | *self |= other.to_bb(); 135 | } 136 | } 137 | 138 | impl BitAndAssign for Bitboard { 139 | fn bitand_assign(&mut self, other: Square) { 140 | *self &= other.to_bb(); 141 | } 142 | } 143 | 144 | impl BitXorAssign for Bitboard { 145 | fn bitxor_assign(&mut self, other: Square) { 146 | *self ^= other.to_bb(); 147 | } 148 | } 149 | 150 | impl Not for Bitboard { 151 | type Output = Self; 152 | fn not(self) -> Self { 153 | Bitboard(!self.0) 154 | } 155 | } 156 | 157 | impl Shl for Bitboard { 158 | type Output = Self; 159 | fn shl(self, shift: u8) -> Self { 160 | Bitboard(self.0 << shift) 161 | } 162 | } 163 | 164 | impl Shr for Bitboard { 165 | type Output = Self; 166 | fn shr(self, shift: u8) -> Self { 167 | Bitboard(self.0 >> shift) 168 | } 169 | } 170 | 171 | // ========================================================= 172 | // Square impls 173 | // ========================================================= 174 | impl Square { 175 | pub fn to_bb(self) -> Bitboard { 176 | Bitboard(1 << self.0) 177 | } 178 | 179 | pub fn file_rank(file: u8, rank: u8) -> Self { 180 | assert!(file < 8); 181 | assert!(rank < 8); 182 | Square(rank * 8 + file) 183 | } 184 | 185 | pub fn forward(self, white_to_move: bool, ranks: u8) -> Self { 186 | // TODO: Are these possible overflows dangerous? 187 | if white_to_move { 188 | Square(self.0 + (8 * ranks)) 189 | } else { 190 | Square(self.0 - (8 * ranks)) 191 | } 192 | } 193 | 194 | pub fn backward(self, white_to_move: bool, ranks: u8) -> Self { 195 | self.forward(!white_to_move, ranks) 196 | } 197 | 198 | pub fn right(self, files: u8) -> Self { 199 | debug_assert!(self.file() + files < 8); 200 | Square(self.0 + files) 201 | } 202 | 203 | pub fn left(self, files: u8) -> Self { 204 | debug_assert!(self.file() >= files); 205 | Square(self.0 - files) 206 | } 207 | 208 | pub fn rank(self) -> u8 { 209 | self.0 >> 3 210 | } 211 | 212 | pub fn file(self) -> u8 { 213 | self.0 & 0x7 214 | } 215 | 216 | pub fn flip_rank(self) -> Square { 217 | Square(self.0 ^ 0b11_1000) 218 | } 219 | 220 | pub fn relative_rank(self, white: bool) -> u8 { 221 | if white { 222 | self.rank() 223 | } else { 224 | self.flip_rank().rank() 225 | } 226 | } 227 | 228 | pub fn is_dark_square(self) -> bool { 229 | (self.rank() + self.file()) % 2 == 0 230 | } 231 | } 232 | 233 | impl Into for Square { 234 | fn into(self) -> u8 { 235 | self.0 236 | } 237 | } 238 | 239 | // TODO: Should be TryFrom, really. 240 | impl From for Square { 241 | fn from(other: u8) -> Square { 242 | assert!(other < 64); 243 | Square(other) 244 | } 245 | } 246 | 247 | pub const SQUARE_A1: Square = Square(0); 248 | pub const SQUARE_E1: Square = Square(4); 249 | pub const SQUARE_H1: Square = Square(7); 250 | pub const SQUARE_A8: Square = Square(56); 251 | pub const SQUARE_E8: Square = Square(60); 252 | pub const SQUARE_H8: Square = Square(63); 253 | 254 | // ========================================================= 255 | // SquareIterator impls 256 | // ========================================================= 257 | impl Iterator for SquareIterator { 258 | type Item = Square; 259 | 260 | fn next(&mut self) -> Option { 261 | if self.bb.0 > 0 { 262 | let sq = Square(self.bb.0.trailing_zeros() as u8); 263 | self.bb.0 &= self.bb.0 - 1; 264 | return Some(sq); 265 | } 266 | 267 | None 268 | } 269 | 270 | fn size_hint(&self) -> (usize, Option) { 271 | let popcount = self.bb.popcount(); 272 | (popcount, Some(popcount)) 273 | } 274 | 275 | fn nth(&mut self, n: usize) -> Option { 276 | for _ in 0..n { 277 | self.bb.0 &= self.bb.0 - 1; 278 | } 279 | 280 | self.next() 281 | } 282 | } 283 | 284 | impl std::iter::FusedIterator for SquareIterator {} 285 | 286 | impl ExactSizeIterator for SquareIterator { 287 | fn len(&self) -> usize { 288 | self.bb.popcount() 289 | } 290 | } 291 | 292 | // ========================================================= 293 | // Constants 294 | // ========================================================= 295 | pub const ALL_SQUARES: Bitboard = Bitboard(0xFF_FF_FF_FF_FF_FF_FF_FF); 296 | pub const FILE_A: Bitboard = Bitboard(0x01_01_01_01_01_01_01_01); 297 | pub const FILE_B: Bitboard = Bitboard(0x02_02_02_02_02_02_02_02); 298 | pub const FILE_C: Bitboard = Bitboard(0x04_04_04_04_04_04_04_04); 299 | pub const FILE_D: Bitboard = Bitboard(0x08_08_08_08_08_08_08_08); 300 | pub const FILE_E: Bitboard = Bitboard(0x10_10_10_10_10_10_10_10); 301 | pub const FILE_F: Bitboard = Bitboard(0x20_20_20_20_20_20_20_20); 302 | pub const FILE_G: Bitboard = Bitboard(0x40_40_40_40_40_40_40_40); 303 | pub const FILE_H: Bitboard = Bitboard(0x80_80_80_80_80_80_80_80); 304 | pub const FILES: [Bitboard; 8] = [ 305 | FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, 306 | ]; 307 | 308 | pub const RANK_1: Bitboard = Bitboard(0x00_00_00_00_00_00_00_FF); 309 | pub const RANK_2: Bitboard = Bitboard(0x00_00_00_00_00_00_FF_00); 310 | pub const RANK_3: Bitboard = Bitboard(0x00_00_00_00_00_FF_00_00); 311 | pub const RANK_4: Bitboard = Bitboard(0x00_00_00_00_FF_00_00_00); 312 | pub const RANK_5: Bitboard = Bitboard(0x00_00_00_FF_00_00_00_00); 313 | pub const RANK_6: Bitboard = Bitboard(0x00_00_FF_00_00_00_00_00); 314 | pub const RANK_7: Bitboard = Bitboard(0x00_FF_00_00_00_00_00_00); 315 | pub const RANK_8: Bitboard = Bitboard(0xFF_00_00_00_00_00_00_00); 316 | pub const RANKS: [Bitboard; 8] = [ 317 | RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, 318 | ]; 319 | 320 | pub const STARTING_COLOR: Bitboard = Bitboard(0x00_00_00_00_00_00_FF_FF); 321 | pub const STARTING_PAWNS: Bitboard = Bitboard(0x00_FF_00_00_00_00_FF_00); 322 | pub const STARTING_KNIGHTS: Bitboard = Bitboard(0x42_00_00_00_00_00_00_42); 323 | pub const STARTING_BISHOPS: Bitboard = Bitboard(0x24_00_00_00_00_00_00_24); 324 | pub const STARTING_ROOKS: Bitboard = Bitboard(0x81_00_00_00_00_00_00_81); 325 | pub const STARTING_QUEENS: Bitboard = Bitboard(0x08_00_00_00_00_00_00_08); 326 | pub const STARTING_KINGS: Bitboard = Bitboard(0x10_00_00_00_00_00_00_10); 327 | pub const STARTING_ALL: Bitboard = Bitboard(0xFF_FF_00_00_00_00_FF_FF); 328 | pub const STARTING_BLACK: Bitboard = Bitboard(0xFF_FF_00_00_00_00_00_00); 329 | 330 | pub const EN_PASSANT_FILES: [Bitboard; 8] = [ 331 | FILE_B, 332 | Bitboard(0x05_05_05_05_05_05_05_05), // FILE_A | FILE_C, 333 | Bitboard(0x0A_0A_0A_0A_0A_0A_0A_0A), // FILE_B | FILE_D, 334 | Bitboard(0x14_14_14_14_14_14_14_14), // FILE_C | FILE_E, 335 | Bitboard(0x28_28_28_28_28_28_28_28), // FILE_D | FILE_F, 336 | Bitboard(0x50_50_50_50_50_50_50_50), // FILE_E | FILE_G, 337 | Bitboard(0xA0_A0_A0_A0_A0_A0_A0_A0), // FILE_F | FILE_H, 338 | FILE_G, 339 | ]; 340 | 341 | pub const KNIGHT_ATTACKS: SquareMap = SquareMap::from_array([ 342 | // rank 1 343 | Bitboard(0x00_00_00_00_00_02_04_00), 344 | Bitboard(0x00_00_00_00_00_05_08_00), 345 | Bitboard(0x00_00_00_00_00_0A_11_00), 346 | Bitboard(0x00_00_00_00_00_14_22_00), 347 | Bitboard(0x00_00_00_00_00_28_44_00), 348 | Bitboard(0x00_00_00_00_00_50_88_00), 349 | Bitboard(0x00_00_00_00_00_A0_10_00), 350 | Bitboard(0x00_00_00_00_00_40_20_00), 351 | // rank 2 352 | Bitboard(0x00_00_00_00_02_04_00_04), 353 | Bitboard(0x00_00_00_00_05_08_00_08), 354 | Bitboard(0x00_00_00_00_0A_11_00_11), 355 | Bitboard(0x00_00_00_00_14_22_00_22), 356 | Bitboard(0x00_00_00_00_28_44_00_44), 357 | Bitboard(0x00_00_00_00_50_88_00_88), 358 | Bitboard(0x00_00_00_00_A0_10_00_10), 359 | Bitboard(0x00_00_00_00_40_20_00_20), 360 | // rank 3 361 | Bitboard(0x00_00_00_02_04_00_04_02), 362 | Bitboard(0x00_00_00_05_08_00_08_05), 363 | Bitboard(0x00_00_00_0A_11_00_11_0A), 364 | Bitboard(0x00_00_00_14_22_00_22_14), 365 | Bitboard(0x00_00_00_28_44_00_44_28), 366 | Bitboard(0x00_00_00_50_88_00_88_50), 367 | Bitboard(0x00_00_00_A0_10_00_10_A0), 368 | Bitboard(0x00_00_00_40_20_00_20_40), 369 | // rank 4 370 | Bitboard(0x00_00_02_04_00_04_02_00), 371 | Bitboard(0x00_00_05_08_00_08_05_00), 372 | Bitboard(0x00_00_0A_11_00_11_0A_00), 373 | Bitboard(0x00_00_14_22_00_22_14_00), 374 | Bitboard(0x00_00_28_44_00_44_28_00), 375 | Bitboard(0x00_00_50_88_00_88_50_00), 376 | Bitboard(0x00_00_A0_10_00_10_A0_00), 377 | Bitboard(0x00_00_40_20_00_20_40_00), 378 | // rank 5 379 | Bitboard(0x00_02_04_00_04_02_00_00), 380 | Bitboard(0x00_05_08_00_08_05_00_00), 381 | Bitboard(0x00_0A_11_00_11_0A_00_00), 382 | Bitboard(0x00_14_22_00_22_14_00_00), 383 | Bitboard(0x00_28_44_00_44_28_00_00), 384 | Bitboard(0x00_50_88_00_88_50_00_00), 385 | Bitboard(0x00_A0_10_00_10_A0_00_00), 386 | Bitboard(0x00_40_20_00_20_40_00_00), 387 | // rank 6 388 | Bitboard(0x02_04_00_04_02_00_00_00), 389 | Bitboard(0x05_08_00_08_05_00_00_00), 390 | Bitboard(0x0A_11_00_11_0A_00_00_00), 391 | Bitboard(0x14_22_00_22_14_00_00_00), 392 | Bitboard(0x28_44_00_44_28_00_00_00), 393 | Bitboard(0x50_88_00_88_50_00_00_00), 394 | Bitboard(0xA0_10_00_10_A0_00_00_00), 395 | Bitboard(0x40_20_00_20_40_00_00_00), 396 | // rank 7 397 | Bitboard(0x04_00_04_02_00_00_00_00), 398 | Bitboard(0x08_00_08_05_00_00_00_00), 399 | Bitboard(0x11_00_11_0A_00_00_00_00), 400 | Bitboard(0x22_00_22_14_00_00_00_00), 401 | Bitboard(0x44_00_44_28_00_00_00_00), 402 | Bitboard(0x88_00_88_50_00_00_00_00), 403 | Bitboard(0x10_00_10_A0_00_00_00_00), 404 | Bitboard(0x20_00_20_40_00_00_00_00), 405 | // rank 8 406 | Bitboard(0x00_04_02_00_00_00_00_00), 407 | Bitboard(0x00_08_05_00_00_00_00_00), 408 | Bitboard(0x00_11_0A_00_00_00_00_00), 409 | Bitboard(0x00_22_14_00_00_00_00_00), 410 | Bitboard(0x00_44_28_00_00_00_00_00), 411 | Bitboard(0x00_88_50_00_00_00_00_00), 412 | Bitboard(0x00_10_A0_00_00_00_00_00), 413 | Bitboard(0x00_20_40_00_00_00_00_00), 414 | ]); 415 | 416 | pub const KING_ATTACKS: SquareMap = SquareMap::from_array([ 417 | // rank 1 418 | Bitboard(0x00_00_00_00_00_00_03_02), 419 | Bitboard(0x00_00_00_00_00_00_07_05), 420 | Bitboard(0x00_00_00_00_00_00_0E_0A), 421 | Bitboard(0x00_00_00_00_00_00_1C_14), 422 | Bitboard(0x00_00_00_00_00_00_38_28), 423 | Bitboard(0x00_00_00_00_00_00_70_50), 424 | Bitboard(0x00_00_00_00_00_00_E0_A0), 425 | Bitboard(0x00_00_00_00_00_00_C0_40), 426 | // rank 2 427 | Bitboard(0x00_00_00_00_00_03_02_03), 428 | Bitboard(0x00_00_00_00_00_07_05_07), 429 | Bitboard(0x00_00_00_00_00_0E_0A_0E), 430 | Bitboard(0x00_00_00_00_00_1C_14_1C), 431 | Bitboard(0x00_00_00_00_00_38_28_38), 432 | Bitboard(0x00_00_00_00_00_70_50_70), 433 | Bitboard(0x00_00_00_00_00_E0_A0_E0), 434 | Bitboard(0x00_00_00_00_00_C0_40_C0), 435 | // rank 3 436 | Bitboard(0x00_00_00_00_03_02_03_00), 437 | Bitboard(0x00_00_00_00_07_05_07_00), 438 | Bitboard(0x00_00_00_00_0E_0A_0E_00), 439 | Bitboard(0x00_00_00_00_1C_14_1C_00), 440 | Bitboard(0x00_00_00_00_38_28_38_00), 441 | Bitboard(0x00_00_00_00_70_50_70_00), 442 | Bitboard(0x00_00_00_00_E0_A0_E0_00), 443 | Bitboard(0x00_00_00_00_C0_40_C0_00), 444 | // rank 4 445 | Bitboard(0x00_00_00_03_02_03_00_00), 446 | Bitboard(0x00_00_00_07_05_07_00_00), 447 | Bitboard(0x00_00_00_0E_0A_0E_00_00), 448 | Bitboard(0x00_00_00_1C_14_1C_00_00), 449 | Bitboard(0x00_00_00_38_28_38_00_00), 450 | Bitboard(0x00_00_00_70_50_70_00_00), 451 | Bitboard(0x00_00_00_E0_A0_E0_00_00), 452 | Bitboard(0x00_00_00_C0_40_C0_00_00), 453 | // rank 5 454 | Bitboard(0x00_00_03_02_03_00_00_00), 455 | Bitboard(0x00_00_07_05_07_00_00_00), 456 | Bitboard(0x00_00_0E_0A_0E_00_00_00), 457 | Bitboard(0x00_00_1C_14_1C_00_00_00), 458 | Bitboard(0x00_00_38_28_38_00_00_00), 459 | Bitboard(0x00_00_70_50_70_00_00_00), 460 | Bitboard(0x00_00_E0_A0_E0_00_00_00), 461 | Bitboard(0x00_00_C0_40_C0_00_00_00), 462 | // rank 6 463 | Bitboard(0x00_03_02_03_00_00_00_00), 464 | Bitboard(0x00_07_05_07_00_00_00_00), 465 | Bitboard(0x00_0E_0A_0E_00_00_00_00), 466 | Bitboard(0x00_1C_14_1C_00_00_00_00), 467 | Bitboard(0x00_38_28_38_00_00_00_00), 468 | Bitboard(0x00_70_50_70_00_00_00_00), 469 | Bitboard(0x00_E0_A0_E0_00_00_00_00), 470 | Bitboard(0x00_C0_40_C0_00_00_00_00), 471 | // rank 7 472 | Bitboard(0x03_02_03_00_00_00_00_00), 473 | Bitboard(0x07_05_07_00_00_00_00_00), 474 | Bitboard(0x0E_0A_0E_00_00_00_00_00), 475 | Bitboard(0x1C_14_1C_00_00_00_00_00), 476 | Bitboard(0x38_28_38_00_00_00_00_00), 477 | Bitboard(0x70_50_70_00_00_00_00_00), 478 | Bitboard(0xE0_A0_E0_00_00_00_00_00), 479 | Bitboard(0xC0_40_C0_00_00_00_00_00), 480 | // rank 8 481 | Bitboard(0x02_03_00_00_00_00_00_00), 482 | Bitboard(0x05_07_00_00_00_00_00_00), 483 | Bitboard(0x0A_0E_00_00_00_00_00_00), 484 | Bitboard(0x14_1C_00_00_00_00_00_00), 485 | Bitboard(0x28_38_00_00_00_00_00_00), 486 | Bitboard(0x50_70_00_00_00_00_00_00), 487 | Bitboard(0xA0_E0_00_00_00_00_00_00), 488 | Bitboard(0x40_C0_00_00_00_00_00_00), 489 | ]); 490 | 491 | const LEFT_FILES: [Bitboard; 9] = [ 492 | Bitboard(0x00_00_00_00_00_00_00_00), 493 | Bitboard(0x01_01_01_01_01_01_01_01), 494 | Bitboard(0x03_03_03_03_03_03_03_03), 495 | Bitboard(0x07_07_07_07_07_07_07_07), 496 | Bitboard(0x0F_0F_0F_0F_0F_0F_0F_0F), 497 | Bitboard(0x1F_1F_1F_1F_1F_1F_1F_1F), 498 | Bitboard(0x3F_3F_3F_3F_3F_3F_3F_3F), 499 | Bitboard(0x7F_7F_7F_7F_7F_7F_7F_7F), 500 | Bitboard(0xFF_FF_FF_FF_FF_FF_FF_FF), 501 | ]; 502 | 503 | const RIGHT_FILES: [Bitboard; 9] = [ 504 | Bitboard(0x00_00_00_00_00_00_00_00), 505 | Bitboard(0x80_80_80_80_80_80_80_80), 506 | Bitboard(0xC0_C0_C0_C0_C0_C0_C0_C0), 507 | Bitboard(0xE0_E0_E0_E0_E0_E0_E0_E0), 508 | Bitboard(0xF0_F0_F0_F0_F0_F0_F0_F0), 509 | Bitboard(0xF8_F8_F8_F8_F8_F8_F8_F8), 510 | Bitboard(0xFC_FC_FC_FC_FC_FC_FC_FC), 511 | Bitboard(0xFE_FE_FE_FE_FE_FE_FE_FE), 512 | Bitboard(0xFF_FF_FF_FF_FF_FF_FF_FF), 513 | ]; 514 | 515 | pub static DARK_SQUARES: Bitboard = Bitboard(0xAA_55_AA_55_AA_55_AA_55); 516 | pub static LIGHT_SQUARES: Bitboard = Bitboard(0x55_AA_55_AA_55_AA_55_AA); 517 | 518 | pub const PAWN_CORRIDOR: [SquareMap; 2] = [ 519 | // Black 520 | SquareMap::from_array([ 521 | // Rank 1. Black pawns never are on rank 1 522 | Bitboard(0x0), 523 | Bitboard(0x0), 524 | Bitboard(0x0), 525 | Bitboard(0x0), 526 | Bitboard(0x0), 527 | Bitboard(0x0), 528 | Bitboard(0x0), 529 | Bitboard(0x0), 530 | // Rank 2. No white pawn can stop a black pawn on rank 2 531 | Bitboard(0x0), 532 | Bitboard(0x0), 533 | Bitboard(0x0), 534 | Bitboard(0x0), 535 | Bitboard(0x0), 536 | Bitboard(0x0), 537 | Bitboard(0x0), 538 | Bitboard(0x0), 539 | // Rank 3 540 | Bitboard(0x00_00_00_00_00_00_03_00), 541 | Bitboard(0x00_00_00_00_00_00_07_00), 542 | Bitboard(0x00_00_00_00_00_00_0E_00), 543 | Bitboard(0x00_00_00_00_00_00_1C_00), 544 | Bitboard(0x00_00_00_00_00_00_38_00), 545 | Bitboard(0x00_00_00_00_00_00_70_00), 546 | Bitboard(0x00_00_00_00_00_00_E0_00), 547 | Bitboard(0x00_00_00_00_00_00_C0_00), 548 | // Rank 4 549 | Bitboard(0x00_00_00_00_00_03_03_00), 550 | Bitboard(0x00_00_00_00_00_07_07_00), 551 | Bitboard(0x00_00_00_00_00_0E_0E_00), 552 | Bitboard(0x00_00_00_00_00_1C_1C_00), 553 | Bitboard(0x00_00_00_00_00_38_38_00), 554 | Bitboard(0x00_00_00_00_00_70_70_00), 555 | Bitboard(0x00_00_00_00_00_E0_E0_00), 556 | Bitboard(0x00_00_00_00_00_C0_C0_00), 557 | // Rank 5 558 | Bitboard(0x00_00_00_00_03_03_03_00), 559 | Bitboard(0x00_00_00_00_07_07_07_00), 560 | Bitboard(0x00_00_00_00_0E_0E_0E_00), 561 | Bitboard(0x00_00_00_00_1C_1C_1C_00), 562 | Bitboard(0x00_00_00_00_38_38_38_00), 563 | Bitboard(0x00_00_00_00_70_70_70_00), 564 | Bitboard(0x00_00_00_00_E0_E0_E0_00), 565 | Bitboard(0x00_00_00_00_C0_C0_C0_00), 566 | // Rank 6 567 | Bitboard(0x00_00_00_03_03_03_03_00), 568 | Bitboard(0x00_00_00_07_07_07_07_00), 569 | Bitboard(0x00_00_00_0E_0E_0E_0E_00), 570 | Bitboard(0x00_00_00_1C_1C_1C_1C_00), 571 | Bitboard(0x00_00_00_38_38_38_38_00), 572 | Bitboard(0x00_00_00_70_70_70_70_00), 573 | Bitboard(0x00_00_00_E0_E0_E0_E0_00), 574 | Bitboard(0x00_00_00_C0_C0_C0_C0_00), 575 | // Rank 7 576 | Bitboard(0x00_00_03_03_03_03_03_00), 577 | Bitboard(0x00_00_07_07_07_07_07_00), 578 | Bitboard(0x00_00_0E_0E_0E_0E_0E_00), 579 | Bitboard(0x00_00_1C_1C_1C_1C_1C_00), 580 | Bitboard(0x00_00_38_38_38_38_38_00), 581 | Bitboard(0x00_00_70_70_70_70_70_00), 582 | Bitboard(0x00_00_E0_E0_E0_E0_E0_00), 583 | Bitboard(0x00_00_C0_C0_C0_C0_C0_00), 584 | // Rank 8. Black pawns never are on rank 8 585 | Bitboard(0), 586 | Bitboard(0), 587 | Bitboard(0), 588 | Bitboard(0), 589 | Bitboard(0), 590 | Bitboard(0), 591 | Bitboard(0), 592 | Bitboard(0), 593 | ]), 594 | // White 595 | SquareMap::from_array([ 596 | // Rank 1. White pawns never are on rank 1 597 | Bitboard(0), 598 | Bitboard(0), 599 | Bitboard(0), 600 | Bitboard(0), 601 | Bitboard(0), 602 | Bitboard(0), 603 | Bitboard(0), 604 | Bitboard(0), 605 | // Rank 2 606 | Bitboard(0x00_03_03_03_03_03_00_00), 607 | Bitboard(0x00_07_07_07_07_07_00_00), 608 | Bitboard(0x00_0E_0E_0E_0E_0E_00_00), 609 | Bitboard(0x00_1C_1C_1C_1C_1C_00_00), 610 | Bitboard(0x00_38_38_38_38_38_00_00), 611 | Bitboard(0x00_70_70_70_70_70_00_00), 612 | Bitboard(0x00_E0_E0_E0_E0_E0_00_00), 613 | Bitboard(0x00_C0_C0_C0_C0_C0_00_00), 614 | // Rank 3 615 | Bitboard(0x00_03_03_03_03_00_00_00), 616 | Bitboard(0x00_07_07_07_07_00_00_00), 617 | Bitboard(0x00_0E_0E_0E_0E_00_00_00), 618 | Bitboard(0x00_1C_1C_1C_1C_00_00_00), 619 | Bitboard(0x00_38_38_38_38_00_00_00), 620 | Bitboard(0x00_70_70_70_70_00_00_00), 621 | Bitboard(0x00_E0_E0_E0_E0_00_00_00), 622 | Bitboard(0x00_C0_C0_C0_C0_00_00_00), 623 | // Rank 4 624 | Bitboard(0x00_03_03_03_00_00_00_00), 625 | Bitboard(0x00_07_07_07_00_00_00_00), 626 | Bitboard(0x00_0E_0E_0E_00_00_00_00), 627 | Bitboard(0x00_1C_1C_1C_00_00_00_00), 628 | Bitboard(0x00_38_38_38_00_00_00_00), 629 | Bitboard(0x00_70_70_70_00_00_00_00), 630 | Bitboard(0x00_E0_E0_E0_00_00_00_00), 631 | Bitboard(0x00_C0_C0_C0_00_00_00_00), 632 | // Rank 5 633 | Bitboard(0x00_03_03_00_00_00_00_00), 634 | Bitboard(0x00_07_07_00_00_00_00_00), 635 | Bitboard(0x00_0E_0E_00_00_00_00_00), 636 | Bitboard(0x00_1C_1C_00_00_00_00_00), 637 | Bitboard(0x00_38_38_00_00_00_00_00), 638 | Bitboard(0x00_70_70_00_00_00_00_00), 639 | Bitboard(0x00_E0_E0_00_00_00_00_00), 640 | Bitboard(0x00_C0_C0_00_00_00_00_00), 641 | // Rank 6 642 | Bitboard(0x00_03_00_00_00_00_00_00), 643 | Bitboard(0x00_07_00_00_00_00_00_00), 644 | Bitboard(0x00_0E_00_00_00_00_00_00), 645 | Bitboard(0x00_1C_00_00_00_00_00_00), 646 | Bitboard(0x00_38_00_00_00_00_00_00), 647 | Bitboard(0x00_70_00_00_00_00_00_00), 648 | Bitboard(0x00_E0_00_00_00_00_00_00), 649 | Bitboard(0x00_C0_00_00_00_00_00_00), 650 | // Rank 7. No black pawn can stop a white pawn on rank 7. 651 | Bitboard(0), 652 | Bitboard(0), 653 | Bitboard(0), 654 | Bitboard(0), 655 | Bitboard(0), 656 | Bitboard(0), 657 | Bitboard(0), 658 | Bitboard(0), 659 | // Rank 8. White pawns never are on rank 8. 660 | Bitboard(0), 661 | Bitboard(0), 662 | Bitboard(0), 663 | Bitboard(0), 664 | Bitboard(0), 665 | Bitboard(0), 666 | Bitboard(0), 667 | Bitboard(0), 668 | ]), 669 | ]; 670 | 671 | pub const KNIGHT_OUTPOSTS: [Bitboard; 2] = [ 672 | Bitboard(0x00_00_00_3C_3C_3C_00_00), 673 | Bitboard(0x00_00_3C_3C_3C_00_00_00), 674 | ]; 675 | 676 | pub const CENTER: Bitboard = Bitboard(0x00_00_00_3C_3C_00_00_00); 677 | 678 | #[cfg(test)] 679 | mod tests { 680 | use super::*; 681 | 682 | #[test] 683 | fn pawn_corridor_does_not_contain_origin() { 684 | for s in 0..2 { 685 | for sq in ALL_SQUARES.squares() { 686 | assert!(!(PAWN_CORRIDOR[s][sq] & sq)); 687 | } 688 | } 689 | } 690 | 691 | #[test] 692 | fn dark_squares() { 693 | for sq in DARK_SQUARES.squares() { 694 | assert!(sq.is_dark_square()); 695 | } 696 | } 697 | } 698 | --------------------------------------------------------------------------------