├── rust-toolchain
├── common
├── src
│ ├── lib.rs
│ └── time
│ │ └── mod.rs
└── Cargo.toml
├── rustfmt.toml
├── src
├── interface
│ └── mod.rs
├── tablebases
│ ├── syzygy
│ │ ├── mod.rs
│ │ ├── probe.rs
│ │ └── bindings.rs
│ └── mod.rs
├── engine
│ ├── qsearch
│ │ ├── mod.rs
│ │ ├── movepick.rs
│ │ └── runner.rs
│ ├── search
│ │ └── mod.rs
│ ├── mod.rs
│ ├── movesort.rs
│ ├── clock.rs
│ ├── see.rs
│ ├── stats.rs
│ └── params.rs
├── testing
│ ├── mod.rs
│ ├── testset.rs
│ └── benchmark.rs
├── tuning
│ ├── mod.rs
│ └── dataset.rs
├── cache
│ ├── mod.rs
│ ├── counters.rs
│ ├── killers.rs
│ ├── history.rs
│ ├── pawns.rs
│ └── perft.rs
├── utils
│ ├── bitflags.rs
│ ├── minmax.rs
│ ├── mod.rs
│ ├── bithelpers.rs
│ └── rand.rs
├── bin
│ └── inanis.rs
├── lib.rs
├── perft
│ ├── normal.rs
│ ├── context.rs
│ ├── divided.rs
│ ├── fast.rs
│ └── mod.rs
├── state
│ ├── text
│ │ ├── mod.rs
│ │ └── pgn.rs
│ ├── zobrist.rs
│ ├── mod.rs
│ └── patterns.rs
└── evaluation
│ ├── params.rs
│ ├── material.rs
│ ├── safety.rs
│ ├── mod.rs
│ ├── mobility.rs
│ ├── pawns.rs
│ └── pst
│ └── mod.rs
├── misc
├── time.xlsx
├── inanis.bmp
└── inanis.xcf
├── Cross.toml
├── CONTRIBUTING.md
├── benches
├── fen_benchmark.rs
├── evaluation_benchmark.rs
├── perft_benchmark.rs
└── see_benchmark.rs
├── .github
└── workflows
│ ├── build.yml
│ ├── docs.yml
│ └── release.yml
├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── Cargo.toml
├── deps
└── fathom
│ ├── LICENSE
│ ├── README.md
│ └── src
│ └── tbconfig.h
├── tests
├── perft_tests.rs
├── fen_tests.rs
├── integrity_tests.rs
├── see_tests.rs
└── board_tests.rs
└── CHANGELOG.md
/rust-toolchain:
--------------------------------------------------------------------------------
1 | 1.77.2
--------------------------------------------------------------------------------
/common/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod time;
2 |
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | max_width = 160
2 | use_small_heuristics = "Max"
--------------------------------------------------------------------------------
/src/interface/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod terminal;
2 | pub mod uci;
3 |
--------------------------------------------------------------------------------
/misc/time.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tearth/Inanis/HEAD/misc/time.xlsx
--------------------------------------------------------------------------------
/misc/inanis.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tearth/Inanis/HEAD/misc/inanis.bmp
--------------------------------------------------------------------------------
/misc/inanis.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tearth/Inanis/HEAD/misc/inanis.xcf
--------------------------------------------------------------------------------
/common/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "common"
3 | version = "1.0.0"
4 | edition = "2021"
--------------------------------------------------------------------------------
/src/tablebases/syzygy/mod.rs:
--------------------------------------------------------------------------------
1 | #![allow(warnings)]
2 | pub mod bindings;
3 | pub mod probe;
4 |
--------------------------------------------------------------------------------
/src/engine/qsearch/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod movepick;
2 | pub mod runner;
3 |
4 | pub use runner::run;
5 |
--------------------------------------------------------------------------------
/src/engine/search/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod movepick;
2 | pub mod runner;
3 |
4 | pub use runner::run;
5 |
--------------------------------------------------------------------------------
/src/testing/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod benchmark;
2 |
3 | #[cfg(feature = "dev")]
4 | pub mod testset;
5 |
--------------------------------------------------------------------------------
/Cross.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | pre-build = [
3 | "apt-get update && apt-get install clang --assume-yes"
4 | ]
--------------------------------------------------------------------------------
/src/tuning/mod.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "dev")]
2 | pub mod tuner;
3 |
4 | #[cfg(feature = "dev")]
5 | pub mod dataset;
6 |
--------------------------------------------------------------------------------
/src/cache/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod counters;
2 | pub mod history;
3 | pub mod killers;
4 | pub mod pawns;
5 | pub mod perft;
6 | pub mod search;
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Because Inanis is a pet project, pull requests are not currently accepted - this may or may not change in the future, depending on the way the project will go. However, feel free to make issues or suggestions, they are greatly appreciated.
--------------------------------------------------------------------------------
/benches/fen_benchmark.rs:
--------------------------------------------------------------------------------
1 | use criterion::criterion_group;
2 | use criterion::criterion_main;
3 | use criterion::Criterion;
4 | use inanis::state::representation::Board;
5 |
6 | fn fen_benchmark(criterion: &mut Criterion) {
7 | criterion.bench_function("fen_benchmark", |bencher| {
8 | bencher.iter(|| {
9 | Board::new_from_fen(criterion::black_box("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1")).unwrap().to_fen();
10 | })
11 | });
12 | }
13 |
14 | criterion_group!(benches, fen_benchmark);
15 | criterion_main!(benches);
16 |
--------------------------------------------------------------------------------
/benches/evaluation_benchmark.rs:
--------------------------------------------------------------------------------
1 | use criterion::criterion_group;
2 | use criterion::criterion_main;
3 | use criterion::Criterion;
4 | use inanis::state::representation::Board;
5 | use inanis::state::*;
6 |
7 | fn evaluation_benchmark(criterion: &mut Criterion) {
8 | let fen = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1";
9 | let board = Board::new_from_fen(fen).unwrap();
10 |
11 | criterion.bench_function("evaluation_benchmark", |bencher| bencher.iter(|| board.evaluate_without_cache(WHITE)));
12 | }
13 |
14 | criterion_group!(benches, evaluation_benchmark);
15 | criterion_main!(benches);
16 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - '**'
5 | jobs:
6 | build_and_check:
7 | name: Build and check
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@master
11 | - uses: actions-rs/toolchain@v1
12 | with:
13 | profile: minimal
14 | override: true
15 | - uses: actions-rs/cargo@v1
16 | with:
17 | command: fmt
18 | args: -- --check
19 | - uses: actions-rs/cargo@v1
20 | with:
21 | command: clippy
22 | args: -- -Dwarnings
23 | - uses: actions-rs/cargo@v1
24 | with:
25 | command: test
26 | args: --release
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
7 | # Cargo.lock
8 |
9 | # These are backup files generated by rustfmt
10 | **/*.rs.bk
11 |
12 | # Editor settings
13 | /.vscode/settings.json
14 |
15 | # Directory with private notes
16 | /notes/
17 |
18 | # Input files for tuner
19 | /input/
20 |
21 | # Output files generated by tuner
22 | /output/
23 |
24 | # Crash files
25 | /crash/
26 |
27 | # Tablebases
28 | /syzygy/
29 |
30 | # VSCodeCounter output
31 | /.VSCodeCounter/
--------------------------------------------------------------------------------
/benches/perft_benchmark.rs:
--------------------------------------------------------------------------------
1 | use criterion::criterion_group;
2 | use criterion::criterion_main;
3 | use criterion::Criterion;
4 | use inanis::perft;
5 | use inanis::state::representation::Board;
6 |
7 | fn perft_benchmark(criterion: &mut Criterion) {
8 | let fen = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1";
9 | let mut board = Board::new_from_fen(fen).unwrap();
10 |
11 | criterion.bench_function("perft_benchmark", |bencher| {
12 | bencher.iter(|| perft::normal::run(criterion::black_box(2), criterion::black_box(&mut board), criterion::black_box(false)))
13 | });
14 | }
15 |
16 | criterion_group!(benches, perft_benchmark);
17 | criterion_main!(benches);
18 |
--------------------------------------------------------------------------------
/src/utils/bitflags.rs:
--------------------------------------------------------------------------------
1 | pub trait BitFlags {
2 | type Item;
3 |
4 | fn contains(&self, value: Self::Item) -> bool;
5 | }
6 |
7 | macro_rules! bit_flags {
8 | ($type:ident) => {
9 | impl BitFlags for $type {
10 | type Item = $type;
11 |
12 | /// Checks if the specified flags (bytes) are present.
13 | fn contains(&self, value: $type) -> bool {
14 | (self & value) != 0
15 | }
16 | }
17 | };
18 | }
19 |
20 | bit_flags!(i8);
21 | bit_flags!(u8);
22 | bit_flags!(i16);
23 | bit_flags!(u16);
24 | bit_flags!(i32);
25 | bit_flags!(u32);
26 | bit_flags!(i64);
27 | bit_flags!(u64);
28 | bit_flags!(isize);
29 | bit_flags!(usize);
30 |
--------------------------------------------------------------------------------
/src/utils/minmax.rs:
--------------------------------------------------------------------------------
1 | pub trait MinMax {
2 | fn min() -> Self;
3 | fn max() -> Self;
4 | }
5 |
6 | macro_rules! min_max {
7 | ($type:ident) => {
8 | impl MinMax for $type {
9 | /// Gets a minimal possible value for the type.
10 | fn min() -> $type {
11 | $type::MIN
12 | }
13 |
14 | /// Gets a maximal possible value for the type.
15 | fn max() -> $type {
16 | $type::MAX
17 | }
18 | }
19 | };
20 | }
21 |
22 | min_max!(i8);
23 | min_max!(u8);
24 | min_max!(i16);
25 | min_max!(u16);
26 | min_max!(i32);
27 | min_max!(u32);
28 | min_max!(i64);
29 | min_max!(u64);
30 | min_max!(isize);
31 | min_max!(usize);
32 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - 'master'
5 | jobs:
6 | generate_docs:
7 | name: Generate docs
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@master
11 | - uses: actions-rs/toolchain@v1
12 | with:
13 | profile: minimal
14 | override: true
15 | - uses: actions-rs/cargo@v1
16 | with:
17 | command: doc
18 | args: --no-deps --document-private-items
19 | - run: echo "" > ./target/doc/index.html
20 | - uses: peaceiris/actions-gh-pages@v3
21 | with:
22 | github_token: ${{ secrets.GITHUB_TOKEN }}
23 | publish_dir: ./target/doc
--------------------------------------------------------------------------------
/src/engine/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod clock;
2 | pub mod context;
3 | pub mod movesort;
4 | pub mod params;
5 | pub mod qsearch;
6 | pub mod search;
7 | pub mod see;
8 | pub mod stats;
9 |
10 | pub const MAX_DEPTH: i8 = 64;
11 | pub const MIN_ALPHA: i16 = -32000;
12 | pub const MIN_BETA: i16 = 32000;
13 | pub const MAX_MOVES_COUNT: usize = 218;
14 |
15 | pub const INVALID_SCORE: i16 = -32700;
16 | pub const DRAW_SCORE: i16 = 0;
17 | pub const CHECKMATE_SCORE: i16 = 31900;
18 | pub const TBMATE_SCORE: i16 = 10000;
19 |
20 | /// Checks if `score` is within mate range (from -[CHECKMATE_SCORE] to -[CHECKMATE_SCORE] + [MAX_DEPTH] and
21 | /// from [CHECKMATE_SCORE] - [MAX_DEPTH] to [CHECKMATE_SCORE]).
22 | pub fn is_score_near_checkmate(score: i16) -> bool {
23 | score.abs() >= CHECKMATE_SCORE - (MAX_DEPTH as i16) && score.abs() <= CHECKMATE_SCORE + (MAX_DEPTH as i16)
24 | }
25 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Windows configuration",
6 | "type": "cppvsdbg",
7 | "request": "launch",
8 | "program": "${workspaceFolder}/target/x86_64-pc-windows-msvc/debug/inanis.exe",
9 | "cwd": "${workspaceFolder}",
10 | "console": "integratedTerminal",
11 | "preLaunchTask": "build debug windows"
12 | },
13 | {
14 | "name": "Linux configuration",
15 | "type": "lldb",
16 | "request": "launch",
17 | "program": "${workspaceFolder}/target/x86_64-unknown-linux-musl/debug/inanis",
18 | "cwd": "${workspaceFolder}",
19 | "console": "integratedTerminal",
20 | "preLaunchTask": "build debug linux"
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/src/bin/inanis.rs:
--------------------------------------------------------------------------------
1 | use inanis::engine::see;
2 | use inanis::interface::terminal;
3 | use inanis::state::movegen;
4 | use std::env;
5 |
6 | /// Entry point of the Inanis engine.
7 | pub fn main() {
8 | let args = env::args_os().collect();
9 | let features = get_target_features();
10 |
11 | see::init();
12 | movegen::init();
13 | terminal::run(args, features);
14 | }
15 |
16 | /// Gets a list of target features (POPCNT, BMI1, BMI2) with which the executable was built.
17 | fn get_target_features() -> Vec<&'static str> {
18 | let mut target_features = Vec::new();
19 |
20 | if cfg!(target_feature = "popcnt") {
21 | target_features.push("POPCNT");
22 | }
23 |
24 | if cfg!(target_feature = "bmi1") {
25 | target_features.push("BMI1");
26 | }
27 |
28 | if cfg!(target_feature = "bmi2") {
29 | target_features.push("BMI2");
30 | }
31 |
32 | target_features
33 | }
34 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![allow(
2 | unused_assignments,
3 | clippy::needless_range_loop,
4 | clippy::identity_op,
5 | clippy::collapsible_if,
6 | clippy::too_many_arguments,
7 | clippy::manual_range_patterns,
8 | clippy::collapsible_else_if
9 | )]
10 |
11 | //! The main page of the Inanis documentation. Feel free to explore it by going into the specific module below,
12 | //! or by clicking "See all inanis's items" on the left panel to see every possible item.
13 | //!
14 | //! Homepage:
15 |
16 | use engine::*;
17 | use state::movescan::Move;
18 | use std::mem::MaybeUninit;
19 |
20 | pub mod cache;
21 | pub mod engine;
22 | pub mod evaluation;
23 | pub mod interface;
24 | pub mod perft;
25 | pub mod state;
26 | pub mod tablebases;
27 | pub mod testing;
28 | pub mod tuning;
29 | pub mod utils;
30 |
31 | pub type Moves = [MaybeUninit; MAX_MOVES_COUNT];
32 | pub type MoveScores = [MaybeUninit; MAX_MOVES_COUNT];
33 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "inanis"
3 | version = "1.6.0"
4 | authors = [ "Pawel Osikowski" ]
5 | repository = "https://github.com/Tearth/Inanis"
6 | license = "GPL-3.0"
7 | edition = "2021"
8 |
9 | [lib]
10 | doctest = false
11 |
12 | [features]
13 | dev = []
14 | syzygy = [ "dep:bindgen", "dep:cc" ]
15 |
16 | [dependencies]
17 | common = { path = "./common/" }
18 |
19 | [build-dependencies]
20 | common = { path = "./common/" }
21 | cc = { version = "1.0.103", optional = true }
22 | bindgen = { version = "0.69.4", optional = true }
23 |
24 | [dev-dependencies]
25 | criterion = { version = "0.3.6", features = [ "html_reports" ] }
26 |
27 | [profile.release]
28 | panic = "abort"
29 | lto = true
30 | strip = true
31 | codegen-units = 1
32 |
33 | [[bench]]
34 | name = "evaluation_benchmark"
35 | harness = false
36 |
37 | [[bench]]
38 | name = "fen_benchmark"
39 | harness = false
40 |
41 | [[bench]]
42 | name = "perft_benchmark"
43 | harness = false
44 |
45 | [[bench]]
46 | name = "see_benchmark"
47 | harness = false
--------------------------------------------------------------------------------
/src/perft/normal.rs:
--------------------------------------------------------------------------------
1 | use super::context::PerftStats;
2 | use super::*;
3 | use crate::cache::perft::PerftHashTable;
4 | use crate::perft::context::PerftContext;
5 | use crate::state::representation::Board;
6 | use std::sync::Arc;
7 |
8 | pub struct NormalPerftResult {
9 | pub nodes: u64,
10 | pub stats: PerftStats,
11 | }
12 |
13 | impl NormalPerftResult {
14 | /// Constructs a new instance of [NormalPerftResult] with stored `nodes` and `stats`.
15 | pub fn new(nodes: u64, stats: PerftStats) -> Self {
16 | Self { nodes, stats }
17 | }
18 | }
19 |
20 | /// Entry point of the fixed-`depth` simple perft. Use `check_integrity` to allow panics when internal state becomes invalid due to some bug.
21 | pub fn run(depth: i32, board: &mut Board, check_integrity: bool) -> NormalPerftResult {
22 | let hashtable = Arc::new(PerftHashTable::new(0));
23 | let mut context = PerftContext::new(board, &hashtable, check_integrity, false);
24 |
25 | NormalPerftResult::new(run_internal(&mut context, depth), context.stats)
26 | }
27 |
--------------------------------------------------------------------------------
/src/perft/context.rs:
--------------------------------------------------------------------------------
1 | use crate::cache::perft::PerftHashTable;
2 | use crate::state::representation::Board;
3 | use std::sync::Arc;
4 |
5 | pub struct PerftContext<'a> {
6 | pub board: &'a mut Board,
7 | pub hashtable: &'a Arc,
8 | pub check_integrity: bool,
9 | pub fast: bool,
10 | pub stats: PerftStats,
11 | }
12 |
13 | #[derive(Default)]
14 | pub struct PerftStats {
15 | pub captures: u64,
16 | pub en_passants: u64,
17 | pub castles: u64,
18 | pub promotions: u64,
19 | pub checks: u64,
20 | }
21 |
22 | impl<'a> PerftContext<'a> {
23 | /// Constructs a new instance of [PerftContext] with `board` as initial state and `hashtable`. Use `check_integrity` to allow panics when internal state
24 | /// becomes invalid due to some bug, and `fast` to allow `hashtable` work.
25 | pub fn new(board: &'a mut Board, hashtable: &'a Arc, check_integrity: bool, fast: bool) -> Self {
26 | Self { board, hashtable, check_integrity, fast, stats: PerftStats::default() }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/state/text/mod.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 |
3 | pub mod fen;
4 | pub mod moves;
5 | pub mod pgn;
6 |
7 | /// Converts piece `symbol` (p/P, n/N, b/B, r/R, q/Q, k/K) into the corresponding [u8] value. Returns [Err] with the proper error messages when the `symbol` is unknown.
8 | pub fn symbol_to_piece(symbol: char) -> Result {
9 | match symbol {
10 | 'p' | 'P' => Ok(PAWN),
11 | 'n' | 'N' => Ok(KNIGHT),
12 | 'b' | 'B' => Ok(BISHOP),
13 | 'r' | 'R' => Ok(ROOK),
14 | 'q' | 'Q' => Ok(QUEEN),
15 | 'k' | 'K' => Ok(KING),
16 | _ => Err(format!("Invalid parameter: symbol={}", symbol)),
17 | }
18 | }
19 |
20 | /// Converts `piece` into the corresponding character (p/P, n/N, b/B, r/R, q/Q, k/K). Returns [Err] with the proper error message when the `piece` is unknown.
21 | pub fn piece_to_symbol(piece: usize) -> Result {
22 | match piece {
23 | PAWN => Ok('P'),
24 | KNIGHT => Ok('N'),
25 | BISHOP => Ok('B'),
26 | ROOK => Ok('R'),
27 | QUEEN => Ok('Q'),
28 | KING => Ok('K'),
29 | _ => Err(format!("Invalid parameter: piece={}", piece)),
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/perft/divided.rs:
--------------------------------------------------------------------------------
1 | use super::context::PerftContext;
2 | use super::*;
3 | use crate::cache::perft::PerftHashTable;
4 | use crate::state::representation::Board;
5 | use std::mem::MaybeUninit;
6 | use std::sync::Arc;
7 |
8 | /// Entry point of the fixed-`depth` divided perft, which performs a separate perfts for every possible move in the position specified by `board`.
9 | /// Returns a map with the long notation moves as the key, and calculated nodes count as the associated value.
10 | pub fn run(depth: i32, board: &mut Board) -> Vec<(String, u64)> {
11 | let mut moves = [MaybeUninit::uninit(); engine::MAX_MOVES_COUNT];
12 | let moves_count = board.get_all_moves(&mut moves, u64::MAX);
13 |
14 | let hashtable = Arc::new(PerftHashTable::new(0));
15 | let mut context = PerftContext::new(board, &hashtable, false, false);
16 | let mut result = Vec::<(String, u64)>::new();
17 |
18 | for r#move in &moves[0..moves_count] {
19 | let r#move = unsafe { r#move.assume_init() };
20 |
21 | context.board.make_move(r#move);
22 | result.push((r#move.to_long_notation(), run_internal(&mut context, depth - 1)));
23 | context.board.undo_move(r#move);
24 | }
25 |
26 | result
27 | }
28 |
--------------------------------------------------------------------------------
/deps/fathom/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013-2018 Ronald de Man
4 | Copyright (c) 2015 basil00
5 | Copyright (c) 2016-2020 by Jon Dart
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/benches/see_benchmark.rs:
--------------------------------------------------------------------------------
1 | use criterion::criterion_group;
2 | use criterion::criterion_main;
3 | use criterion::Criterion;
4 | use inanis::engine::see;
5 | use inanis::state::representation::Board;
6 |
7 | fn see_benchmark(criterion: &mut Criterion) {
8 | let fen = "1b2r2k/2qnrn2/5p2/4R3/5P2/3N1N2/1B2Q3/K3R3 w - - 0 1";
9 | let board = Board::new_from_fen(fen).unwrap();
10 |
11 | criterion.bench_function("see_benchmark", |bencher| {
12 | bencher.iter(|| {
13 | let attacking_piece = board.get_piece(criterion::black_box(51));
14 | let captured_piece = board.get_piece(criterion::black_box(35));
15 | let attackers = board.get_attacking_pieces(criterion::black_box(board.stm ^ 1), criterion::black_box(35));
16 | let defenders = board.get_attacking_pieces(criterion::black_box(board.stm), criterion::black_box(35));
17 |
18 | see::get(
19 | criterion::black_box(attacking_piece),
20 | criterion::black_box(captured_piece),
21 | criterion::black_box(attackers),
22 | criterion::black_box(defenders),
23 | );
24 | })
25 | });
26 | }
27 |
28 | criterion_group!(benches, see_benchmark);
29 | criterion_main!(benches);
30 |
--------------------------------------------------------------------------------
/src/engine/movesort.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use crate::state::movescan::Move;
3 | use crate::utils::assert_fast;
4 | use crate::MoveScores;
5 | use crate::Moves;
6 |
7 | /// Performs a selection sort on `moves` and `move_scores` arrays with the length specified in `moves_count`, starting from `start_index`.
8 | /// When it completes, the move and corresponding score will be under `start_index` - the function also explicitly returns both of them.
9 | pub fn sort_next_move(moves: &mut Moves, move_scores: &mut MoveScores, start_index: usize, moves_count: usize) -> (Move, i16) {
10 | assert_fast!(start_index < MAX_MOVES_COUNT);
11 | assert_fast!(start_index <= moves_count);
12 |
13 | let mut best_score = unsafe { move_scores[start_index].assume_init() };
14 | let mut best_index = start_index;
15 |
16 | for index in (start_index + 1)..moves_count {
17 | assert_fast!(index < MAX_MOVES_COUNT);
18 |
19 | let score = unsafe { move_scores[index].assume_init() };
20 | if score > best_score {
21 | best_score = score;
22 | best_index = index;
23 | }
24 | }
25 |
26 | moves.swap(start_index, best_index);
27 | move_scores.swap(start_index, best_index);
28 |
29 | unsafe { (moves[start_index].assume_init(), best_score) }
30 | }
31 |
--------------------------------------------------------------------------------
/src/tablebases/mod.rs:
--------------------------------------------------------------------------------
1 | use crate::engine::*;
2 | use crate::state::movescan::Move;
3 | use crate::state::representation::Board;
4 | use std::cmp;
5 |
6 | pub mod syzygy;
7 |
8 | #[derive(PartialEq, Eq, Debug)]
9 | pub enum WdlResult {
10 | Win,
11 | Draw,
12 | Loss,
13 | }
14 |
15 | pub struct WdlDtzResult {
16 | pub wdl: WdlResult,
17 | pub dtz: u32,
18 | pub r#move: Move,
19 | }
20 |
21 | impl WdlDtzResult {
22 | /// Constructs a new instance of [WdlDtzResult] with stored `wdl`, `dtz` and `r#move`.
23 | pub fn new(wdl: WdlResult, dtz: u32, r#move: Move) -> Self {
24 | WdlDtzResult { wdl, dtz, r#move }
25 | }
26 | }
27 |
28 | /// Checks if there's a tablebase move (only Syzygy supported for now) and returns it as [Some], otherwise [None].
29 | pub fn get_tablebase_move(board: &Board, probe_limit: u32) -> Option<(Move, i16)> {
30 | if board.get_pieces_count() > cmp::min(probe_limit as u8, syzygy::probe::get_max_pieces_count()) {
31 | return None;
32 | }
33 |
34 | if let Some(result) = syzygy::probe::get_root_wdl_dtz(board) {
35 | let score = match result.wdl {
36 | WdlResult::Win => TBMATE_SCORE,
37 | WdlResult::Draw => 0,
38 | WdlResult::Loss => -TBMATE_SCORE,
39 | };
40 |
41 | return Some((result.r#move, score));
42 | }
43 |
44 | None
45 | }
46 |
--------------------------------------------------------------------------------
/tests/perft_tests.rs:
--------------------------------------------------------------------------------
1 | #[cfg(test)]
2 | mod perft_tests {
3 | use inanis::engine::see;
4 | use inanis::perft;
5 | use inanis::state::representation::Board;
6 | use inanis::state::*;
7 | use std::sync::Once;
8 |
9 | static INIT: Once = Once::new();
10 |
11 | macro_rules! perft_tests {
12 | ($($name:ident: $depth:expr, $fen:expr, $expected_leafs_count:expr,)*) => {
13 | $(
14 | #[test]
15 | fn $name() {
16 | INIT.call_once(|| {
17 | see::init();
18 | movegen::init();
19 | });
20 |
21 | assert_eq!($expected_leafs_count, perft::normal::run($depth, &mut Board::new_from_fen($fen).unwrap(), false).nodes);
22 | }
23 | )*
24 | }
25 | }
26 |
27 | perft_tests! {
28 | perft_position_1: 6, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 119060324,
29 | perft_position_2: 5, "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1", 193690690,
30 | perft_position_3: 7, "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1", 178633661,
31 | perft_position_4: 6, "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1", 706045033,
32 | perft_position_5: 5, "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8", 89941194,
33 | perft_position_6: 5, "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10", 164075551,
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/utils/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod bitflags;
2 | pub mod bithelpers;
3 | pub mod minmax;
4 | pub mod rand;
5 |
6 | macro_rules! dev {
7 | ($expression: expr) => {
8 | if cfg!(feature = "dev") {
9 | $expression;
10 | }
11 | };
12 | }
13 |
14 | macro_rules! percent {
15 | ($from: expr, $all: expr) => {
16 | (($from as f32) / ($all as f32)) * 100.0
17 | };
18 | }
19 |
20 | macro_rules! param {
21 | ($a : ident . $b : ident . $c : ident) => {
22 | if cfg!(feature = "dev") {
23 | $a.$b.$c
24 | } else {
25 | crate::engine::params::SearchParams::$c
26 | }
27 | };
28 | }
29 |
30 | macro_rules! panic_fast {
31 | ($fmt:expr) => ({
32 | if cfg!(feature = "dev") {
33 | panic!(concat!($fmt, "\n"));
34 | } else {
35 | std::process::abort();
36 | }
37 | });
38 | ($fmt:expr, $($arg:tt)*) => (
39 | {
40 | if cfg!(feature = "dev") {
41 | panic!(concat!($fmt, "\n"), $($arg)*);
42 | } else {
43 | std::process::abort();
44 | }
45 | });
46 | }
47 |
48 | macro_rules! assert_fast {
49 | ($($arg:tt)*) => {
50 | if cfg!(debug_assertions) {
51 | debug_assert!($($arg)*);
52 | } else {
53 | if !($($arg)*) {
54 | unsafe { std::hint::unreachable_unchecked() };
55 | }
56 | }
57 | };
58 | }
59 |
60 | pub(crate) use assert_fast;
61 | pub(crate) use dev;
62 | pub(crate) use panic_fast;
63 | pub(crate) use param;
64 | pub(crate) use percent;
65 |
--------------------------------------------------------------------------------
/src/cache/counters.rs:
--------------------------------------------------------------------------------
1 | use crate::state::movescan::Move;
2 | use crate::utils::assert_fast;
3 | use std::alloc;
4 | use std::alloc::Layout;
5 | use std::mem;
6 |
7 | pub struct CMTable {
8 | pub table: Box<[[CMTableEntry; 64]; 64]>,
9 | }
10 |
11 | pub struct CMTableEntry {
12 | pub r#move: Move,
13 | }
14 |
15 | impl CMTable {
16 | /// Adds countermove `r#move` as response to `previous_move`.
17 | pub fn add(&mut self, previous_move: Move, r#move: Move) {
18 | assert_fast!(previous_move.is_some());
19 | assert_fast!(previous_move.get_from() < 64);
20 | assert_fast!(previous_move.get_to() < 64);
21 | assert_fast!(r#move.is_some());
22 |
23 | self.table[previous_move.get_from()][previous_move.get_to()].r#move = r#move;
24 | }
25 |
26 | /// Gets countermove for `previous_move`.
27 | pub fn get(&self, previous_move: Move) -> Move {
28 | assert_fast!(previous_move.get_from() < 64);
29 | assert_fast!(previous_move.get_to() < 64);
30 |
31 | self.table[previous_move.get_from()][previous_move.get_to()].r#move
32 | }
33 | }
34 |
35 | impl Default for CMTable {
36 | /// Constructs a default instance of [CMTable] by allocating `64 * 64 * mem::size_of::()`
37 | /// boxed array with zeroed elements.
38 | fn default() -> Self {
39 | const SIZE: usize = mem::size_of::();
40 | unsafe {
41 | let ptr = alloc::alloc_zeroed(Layout::from_size_align(64 * 64 * SIZE, SIZE).unwrap());
42 | Self { table: Box::from_raw(ptr as *mut [[CMTableEntry; 64]; 64]) }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/fen_tests.rs:
--------------------------------------------------------------------------------
1 | #[cfg(test)]
2 | mod fen_tests {
3 | use inanis::engine::see;
4 | use inanis::state::representation::Board;
5 | use inanis::state::*;
6 | use std::sync::Once;
7 |
8 | static INIT: Once = Once::new();
9 |
10 | macro_rules! fen_tests {
11 | ($($name:ident: $original_fen:expr,)*) => {
12 | $(
13 | #[test]
14 | fn $name() {
15 | INIT.call_once(|| {
16 | see::init();
17 | movegen::init();
18 | });
19 |
20 | assert_eq!($original_fen, Board::new_from_fen($original_fen).unwrap().to_fen());
21 | }
22 | )*
23 | }
24 | }
25 |
26 | fen_tests! {
27 | fen_default: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
28 | fen_mid_game1: "5rk1/2b1qp1p/1r2p1pB/1ppnn3/3pN3/1P1P2P1/2P1QPBP/R4RK1 b - - 7 22",
29 | fen_mid_game2: "2k4r/1p3pp1/p2p2n1/2P1p2q/P1P1P3/3PBPP1/2R3Qr/5RK1 b - - 2 22",
30 | fen_mid_game3: "r6k/p1B4p/Pp3rp1/3p4/2nP4/2PQ1PPq/7P/1R3RK1 b - - 0 32",
31 | fen_en_passant1: "r3kb2/p4pp1/2q1p3/1pP1n1N1/3B2nr/1QP1P3/PP1N3P/R2R2K1 w q b6 0 2",
32 | fen_en_passant2: "rn1qkbnr/pp3ppp/4p3/3pPb2/1PpP4/4BN2/P1P1BPPP/RN1QK2R b KQkq b3 0 2",
33 | fen_en_passant3: "rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 2",
34 | fen_end_game1: "8/8/6Q1/8/6k1/1P2q3/7p/7K b - - 14 75",
35 | fen_end_game2: "8/8/4nPk1/8/6pK/8/1R3P1P/2B3r1 b - - 1 54",
36 | fen_end_game3: "8/7q/5K2/2q5/6k1/8/8/8 b - - 5 60",
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/integrity_tests.rs:
--------------------------------------------------------------------------------
1 | #[cfg(test)]
2 | mod integrity_tests {
3 | use inanis::engine::see;
4 | use inanis::perft;
5 | use inanis::state::representation::Board;
6 | use inanis::state::*;
7 | use std::sync::Once;
8 |
9 | static INIT: Once = Once::new();
10 |
11 | macro_rules! integrity_tests {
12 | ($($name:ident: $depth:expr, $fen:expr,)*) => {
13 | $(
14 | #[test]
15 | fn $name() {
16 | INIT.call_once(|| {
17 | see::init();
18 | movegen::init();
19 | });
20 |
21 | perft::normal::run($depth, &mut Board::new_from_fen($fen).unwrap(), true);
22 | }
23 | )*
24 | }
25 | }
26 |
27 | integrity_tests! {
28 | integrity_default: 6, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
29 | integrity_mid_game1: 5, "5rk1/2b1qp1p/1r2p1pB/1ppnn3/3pN3/1P1P2P1/2P1QPBP/R4RK1 b - - 7 22",
30 | integrity_mid_game2: 5, "2k4r/1p3pp1/p2p2n1/2P1p2q/P1P1P3/3PBPP1/2R3Qr/5RK1 b - - 2 22",
31 | integrity_mid_game3: 5, "r6k/p1B4p/Pp3rp1/3p4/2nP4/2PQ1PPq/7P/1R3RK1 b - - 0 32",
32 | integrity_en_passant1: 5, "r3kb2/p4pp1/2q1p3/1pP1n1N1/3B2nr/1QP1P3/PP1N3P/R2R2K1 w q b6 0 2",
33 | integrity_en_passant2: 5, "rn1qkbnr/pp3ppp/4p3/3pPb2/1PpP4/4BN2/P1P1BPPP/RN1QK2R b KQkq b3 0 2",
34 | integrity_en_passant3: 5, "rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 2",
35 | integrity_end_game1: 7, "8/8/6Q1/8/6k1/1P2q3/7p/7K b - - 14 75",
36 | integrity_end_game2: 6, "8/8/4nPk1/8/6pK/8/1R3P1P/2B3r1 b - - 1 54",
37 | integrity_end_game3: 7, "8/7q/5K2/2q5/6k1/8/8/8 b - - 5 60",
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/utils/bithelpers.rs:
--------------------------------------------------------------------------------
1 | pub trait BitHelpers {
2 | type Item;
3 |
4 | fn get_lsb(&self) -> Self::Item;
5 | fn pop_lsb(&self) -> Self::Item;
6 | fn bit_count(&self) -> usize;
7 | fn bit_scan(&self) -> usize;
8 | }
9 |
10 | macro_rules! bit_helpers {
11 | ($type:ident) => {
12 | impl BitHelpers for $type {
13 | type Item = $type;
14 |
15 | /// Extracts the lowest set isolated bit.
16 | ///
17 | /// More about asm instruction:
18 | fn get_lsb(&self) -> Self::Item {
19 | self & self.wrapping_neg()
20 | }
21 |
22 | /// Resets the lowest set bit.
23 | ///
24 | /// More about asm instruction:
25 | fn pop_lsb(&self) -> Self::Item {
26 | self & (self - 1)
27 | }
28 |
29 | /// Counts the number of set bits.
30 | ///
31 | /// More about asm instruction:
32 | fn bit_count(&self) -> usize {
33 | self.count_ones() as usize
34 | }
35 |
36 | /// Gets an index of the first set bit by counting trailing zero bits.
37 | ///
38 | /// More about asm instruction:
39 | fn bit_scan(&self) -> usize {
40 | self.trailing_zeros() as usize
41 | }
42 | }
43 | };
44 | }
45 |
46 | bit_helpers!(i8);
47 | bit_helpers!(u8);
48 | bit_helpers!(i16);
49 | bit_helpers!(u16);
50 | bit_helpers!(i32);
51 | bit_helpers!(u32);
52 | bit_helpers!(i64);
53 | bit_helpers!(u64);
54 | bit_helpers!(isize);
55 | bit_helpers!(usize);
56 |
--------------------------------------------------------------------------------
/src/engine/clock.rs:
--------------------------------------------------------------------------------
1 | use super::context::SearchContext;
2 | use crate::utils::param;
3 |
4 | /// Calculates time bounds (soft and hard) which should be used for the next move. Formula and plot for the case when `moves_to_go` is zeroed can
5 | /// be found in the `/misc/time.xlsx` Excel sheet, but in general outline it tries to allocate more time during midgame where usually there's
6 | /// a lot of pieces on the board and it's crucial to find some advantage at this phase, that can be converted later to decisive result. Formula used
7 | /// when `moves_to_go` is greater than zero is simpler and allocates time evenly.
8 | pub fn get_time_bounds(context: &SearchContext) -> (u32, u32) {
9 | if context.max_move_time != 0 {
10 | let soft_bound = context.max_move_time * param!(context.params.time_soft_bound) as u32 / 100;
11 | let hard_bound = context.max_move_time;
12 |
13 | return (soft_bound, hard_bound);
14 | }
15 |
16 | if context.time > u32::MAX / 2 {
17 | return (u32::MAX, u32::MAX);
18 | }
19 |
20 | let allocated_time = if context.moves_to_go == 0 {
21 | let a = param!(context.params.time_a) as f32;
22 | let b = param!(context.params.time_b) as f32;
23 | let c = param!(context.params.time_c) as f32;
24 | let d = param!(context.params.time_d) as f32;
25 | let e = param!(context.params.time_e) as f32;
26 |
27 | context.time / ((a + b * f32::sin(f32::min(c, (context.board.fullmove_number as f32) + d) / e)) as u32) + context.inc_time
28 | } else {
29 | context.time / (context.moves_to_go + 2) + context.inc_time
30 | };
31 |
32 | let soft_bound = allocated_time * param!(context.params.time_soft_bound) as u32 / 100;
33 | let hard_bound = allocated_time * param!(context.params.time_hard_bound) as u32 / 100;
34 |
35 | (soft_bound.min(context.time), hard_bound.min(context.time))
36 | }
37 |
--------------------------------------------------------------------------------
/common/src/time/mod.rs:
--------------------------------------------------------------------------------
1 | use std::time::SystemTime;
2 | use std::time::UNIX_EPOCH;
3 |
4 | pub struct DateTime {
5 | pub year: u16,
6 | pub month: u8,
7 | pub day: u8,
8 | pub hour: u8,
9 | pub minute: u8,
10 | pub second: u8,
11 | }
12 |
13 | impl DateTime {
14 | /// Constructs a new instance of [DateTime] with `year`, `month`, `day`, `hour`, `minute` and `second`.
15 | pub fn new(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> DateTime {
16 | debug_assert!(month <= 12);
17 | debug_assert!(day <= 31);
18 | debug_assert!(hour < 24);
19 | debug_assert!(minute < 60);
20 | debug_assert!(second < 60);
21 |
22 | DateTime { year, month, day, hour, minute, second }
23 | }
24 |
25 | /// Constructs a new instance of [DateTime] using current Unix timestamp converted with algorithms
26 | /// described here: http://howardhinnant.github.io/date_algorithms.html.
27 | pub fn now() -> DateTime {
28 | let timestamp = get_unix_timestamp();
29 |
30 | let z = ((timestamp as i64) / 86400) + 719468;
31 | let era = (if z >= 0 { z } else { z - 146096 }) / 146097;
32 | let doe = (z - era * 146097) as u64;
33 | let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
34 | let y = yoe as i64 + era * 400;
35 | let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
36 | let mp = (5 * doy + 2) / 153;
37 |
38 | let day = (doy - (153 * mp + 2) / 5 + 1) as u8;
39 | let month = (if mp < 10 { mp + 3 } else { mp - 9 }) as u8;
40 | let year = (y + (if month <= 2 { 1 } else { 0 })) as u16;
41 | let hour = ((timestamp / 3600) % 24) as u8;
42 | let minute = ((timestamp / 60) % 60) as u8;
43 | let second = (timestamp % 60) as u8;
44 |
45 | DateTime::new(year, month, day, hour, minute, second)
46 | }
47 | }
48 |
49 | /// Gets the current Unix timestamp.
50 | pub fn get_unix_timestamp() -> u64 {
51 | SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()
52 | }
53 |
--------------------------------------------------------------------------------
/src/engine/qsearch/movepick.rs:
--------------------------------------------------------------------------------
1 | use crate::engine::context::SearchContext;
2 | use crate::engine::*;
3 | use crate::evaluation::*;
4 | use crate::state::*;
5 | use crate::utils::assert_fast;
6 | use crate::MoveScores;
7 | use crate::Moves;
8 |
9 | /// Assigns scores for `moves` by filling `move_scores` array with `moves_count` length, based on current `context`. Move ordering in
10 | /// quiescence search is mainly based on SEE and works as follows:
11 | /// - for every en passant, assign 0
12 | /// - for every promotion, ignore all of them except queens
13 | /// - for rest of the moves, assign SEE result
14 | pub fn assign_move_scores(context: &SearchContext, moves: &Moves, move_scores: &mut MoveScores, moves_count: usize) {
15 | assert_fast!(moves_count < MAX_MOVES_COUNT);
16 |
17 | let mut attackers_cache = [0; 64];
18 | let mut defenders_cache = [0; 64];
19 |
20 | for move_index in 0..moves_count {
21 | let r#move = unsafe { moves[move_index].assume_init() };
22 |
23 | if r#move.is_en_passant() {
24 | move_scores[move_index].write(0);
25 | } else if r#move.is_promotion() {
26 | move_scores[move_index].write(if r#move.get_promotion_piece() == QUEEN { PIECE_VALUES[QUEEN] } else { -9999 });
27 | } else {
28 | let square = r#move.get_to();
29 | let attacking_piece = context.board.get_piece(r#move.get_from());
30 | let captured_piece = context.board.get_piece(r#move.get_to());
31 |
32 | let attackers = if attackers_cache[square] != 0 {
33 | attackers_cache[square] as usize
34 | } else {
35 | attackers_cache[square] = context.board.get_attacking_pieces(context.board.stm ^ 1, square) as u8;
36 | attackers_cache[square] as usize
37 | };
38 |
39 | let defenders = if defenders_cache[square] != 0 {
40 | defenders_cache[square] as usize
41 | } else {
42 | defenders_cache[square] = context.board.get_attacking_pieces(context.board.stm, square) as u8;
43 | defenders_cache[square] as usize
44 | };
45 |
46 | move_scores[move_index].write(see::get(attacking_piece, captured_piece, attackers, defenders));
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/utils/rand.rs:
--------------------------------------------------------------------------------
1 | use std::cell::Cell;
2 | use std::ops::Bound;
3 | use std::ops::RangeBounds;
4 |
5 | pub struct RandState {
6 | pub seed: Cell,
7 | }
8 |
9 | impl RandState {
10 | /// Constructs a new instance of [RandState] with stored `seed`.
11 | pub fn new(seed: u64) -> Self {
12 | Self { seed: Cell::new(seed) }
13 | }
14 | }
15 |
16 | thread_local! {
17 | static SEED: RandState = RandState::new(common::time::get_unix_timestamp())
18 | }
19 |
20 | macro_rules! rand_definition {
21 | ($type:ident, $min_value:expr, $max_value:expr) => {
22 | /// Gets a random number within `range`.
23 | pub fn $type(range: impl RangeBounds<$type>) -> $type {
24 | let from = match range.start_bound() {
25 | Bound::Included(v) => *v,
26 | Bound::Excluded(v) => *v + 1,
27 | Bound::Unbounded => $min_value,
28 | };
29 |
30 | let to = match range.end_bound() {
31 | Bound::Included(v) => *v,
32 | Bound::Excluded(v) => *v - 1,
33 | Bound::Unbounded => $max_value,
34 | };
35 |
36 | SEED.with(|state| {
37 | let (value, seed) = rand(state.seed.get());
38 | let result = if from == $min_value && to == $max_value {
39 | value as $type
40 | } else {
41 | (value % (((to as i128) - (from as i128) + 1) as u64)) as $type + from
42 | };
43 |
44 | state.seed.set(seed);
45 | result
46 | })
47 | }
48 | };
49 | }
50 |
51 | rand_definition!(i8, i8::MIN, i8::MAX);
52 | rand_definition!(u8, u8::MIN, u8::MAX);
53 | rand_definition!(i16, i16::MIN, i16::MAX);
54 | rand_definition!(u16, u16::MIN, u16::MAX);
55 | rand_definition!(i32, i32::MIN, i32::MAX);
56 | rand_definition!(u32, u32::MIN, u32::MAX);
57 | rand_definition!(i64, i64::MIN, i64::MAX);
58 | rand_definition!(u64, u64::MIN, u64::MAX);
59 | rand_definition!(isize, isize::MIN, isize::MAX);
60 | rand_definition!(usize, usize::MIN, usize::MAX);
61 |
62 | /// Sets an initial seed for LCG.
63 | pub fn seed(seed: u64) {
64 | SEED.with(|state| {
65 | state.seed.set(seed);
66 | });
67 | }
68 |
69 | /// https://en.wikipedia.org/wiki/Xorshift#xorshift*
70 | pub const fn rand(seed: u64) -> (u64, u64) {
71 | let mut x = seed;
72 | x ^= x >> 12;
73 | x ^= x << 25;
74 | x ^= x >> 27;
75 |
76 | (x.wrapping_mul(0x2545f4914f6cdd1d), x)
77 | }
78 |
--------------------------------------------------------------------------------
/src/perft/fast.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use crate::cache::perft::PerftHashTable;
3 | use crate::engine;
4 | use crate::perft::context::PerftContext;
5 | use crate::state::representation::Board;
6 | use std::mem::MaybeUninit;
7 | use std::sync::Arc;
8 | use std::sync::Mutex;
9 | use std::thread;
10 |
11 | /// Entry point of the fixed-`depth` fast perft, which uses both hashtable (with size specified in `hashtable_size`) and multithreading (with `threads_count` threads).
12 | /// Returns calculated nodes count as the first value, and approximated hashtable usage as the second value.
13 | pub fn run(depth: i32, board: &mut Board, hashtable_size: usize, threads_count: usize) -> (u64, f32) {
14 | let queue = Arc::new(Mutex::new(Vec::new()));
15 | let hashtable = Arc::new(PerftHashTable::new(hashtable_size));
16 | let mut threads = Vec::new();
17 |
18 | let mut moves = [MaybeUninit::uninit(); engine::MAX_MOVES_COUNT];
19 | let moves_count = board.get_all_moves(&mut moves, u64::MAX);
20 |
21 | for r#move in &moves[0..moves_count] {
22 | let r#move = unsafe { r#move.assume_init() };
23 |
24 | let mut cloned_board = board.clone();
25 | cloned_board.make_move(r#move);
26 |
27 | queue.lock().unwrap().push(cloned_board);
28 | }
29 |
30 | for _ in 0..threads_count {
31 | let queue_arc = queue.clone();
32 | let hashtable_arc = hashtable.clone();
33 |
34 | threads.push(thread::spawn(move || {
35 | let mut count = 0;
36 | let mut hashtable_usage = 0.0;
37 |
38 | loop {
39 | let mut board = {
40 | match queue_arc.lock().unwrap().pop() {
41 | Some(value) => value,
42 | None => break,
43 | }
44 | };
45 |
46 | let mut context = PerftContext::new(&mut board, &hashtable_arc, false, true);
47 | count += run_internal(&mut context, depth - 1);
48 |
49 | hashtable_usage = context.hashtable.get_usage(10000);
50 | }
51 |
52 | (count, hashtable_usage)
53 | }));
54 | }
55 |
56 | let mut total_count = 0;
57 | let mut hashtable_usage_accumulator = 0.0;
58 |
59 | for thread in threads {
60 | let (count, hashtable_usage) = thread.join().unwrap();
61 |
62 | total_count += count;
63 | hashtable_usage_accumulator += hashtable_usage;
64 | }
65 |
66 | (total_count, hashtable_usage_accumulator / (threads_count as f32))
67 | }
68 |
--------------------------------------------------------------------------------
/src/evaluation/params.rs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------- //
2 | // Generated at 28-12-2024 13:59:28 UTC (e = 0.067562, k = 0.0077, r = 0.70) //
3 | // ------------------------------------------------------------------------- //
4 |
5 | use super::*;
6 |
7 | pub const TEMPO: i16 = 15;
8 | pub const BISHOP_PAIR: PackedEval = s!(20, 55);
9 | pub const PAWNS_ATTACKING_PIECES: PackedEval = s!(42, 43);
10 | pub const ROOK_OPEN_FILE: PackedEval = s!(24, -4);
11 | pub const ROOK_SEMI_OPEN_FILE: PackedEval = s!(9, 15);
12 | pub const MOBILITY_INNER: [PackedEval; 6] = [s!(0, 0), s!(11, 2), s!(10, 12), s!(7, 2), s!(4, 5), s!(0, 0)];
13 | pub const MOBILITY_OUTER: [PackedEval; 6] = [s!(0, 0), s!(3, 0), s!(2, 0), s!(3, 1), s!(2, 0), s!(0, 0)];
14 | pub const DOUBLED_PAWN: [PackedEval; 8] = [s!(-1, -8), s!(-15, -20), s!(-25, -41), s!(-54, -49), s!(-22, -39), s!(0, 0), s!(0, 0), s!(0, 0)];
15 | pub const ISOLATED_PAWN: [PackedEval; 8] = [s!(-1, -9), s!(-13, -20), s!(-24, -31), s!(-33, -39), s!(-37, -56), s!(0, 0), s!(0, 0), s!(0, 0)];
16 | pub const CHAINED_PAWN: [PackedEval; 8] = [s!(0, 1), s!(10, 8), s!(19, 18), s!(26, 32), s!(32, 49), s!(40, 62), s!(44, 42), s!(0, 0)];
17 | pub const PASSED_PAWN: [PackedEval; 8] = [s!(11, -33), s!(16, 17), s!(19, 55), s!(30, 59), s!(41, 50), s!(22, 41), s!(25, 14), s!(37, 26)];
18 | pub const BACKWARD_PAWN_OPEN_FILE: [PackedEval; 8] = [s!(36, 21), s!(23, 11), s!(10, 0), s!(-6, 8), s!(15, 43), s!(0, 0), s!(0, 0), s!(0, 0)];
19 | pub const BACKWARD_PAWN_CLOSED_FILE: [PackedEval; 8] = [s!(21, 19), s!(17, 16), s!(16, 8), s!(14, 5), s!(14, 41), s!(0, 0), s!(0, 0), s!(0, 0)];
20 | pub const PAWN_SHIELD: [PackedEval; 8] = [s!(3, 15), s!(14, 19), s!(20, 20), s!(20, 19), s!(65, 12), s!(20, 21), s!(0, 0), s!(0, 0)];
21 | pub const PAWN_SHIELD_OPEN_FILE: [PackedEval; 8] = [s!(-25, -21), s!(-24, -18), s!(-26, -20), s!(-29, -27), s!(0, 0), s!(0, 0), s!(0, 0), s!(0, 0)];
22 | pub const KING_AREA_THREATS: [PackedEval; 8] = [s!(-53, 42), s!(-55, 37), s!(-48, 36), s!(-32, 33), s!(0, 16), s!(41, 1), s!(86, -20), s!(166, -56)];
23 | pub const KNIGHT_SAFE_CHECKS: [PackedEval; 8] = [s!(-118, 50), s!(-75, 46), s!(-34, 37), s!(-40, 35), s!(7, 14), s!(0, 0), s!(0, 0), s!(0, 0)];
24 | pub const BISHOP_SAFE_CHECKS: [PackedEval; 8] = [s!(-88, 37), s!(-85, 54), s!(-54, 46), s!(0, 0), s!(0, 0), s!(0, 0), s!(0, 0), s!(0, 0)];
25 | pub const ROOK_SAFE_CHECKS: [PackedEval; 8] = [s!(-118, 41), s!(-70, 32), s!(-48, 30), s!(-32, 57), s!(17, 34), s!(0, 0), s!(0, 0), s!(0, 0)];
26 | pub const QUEEN_SAFE_CHECKS: [PackedEval; 8] = [s!(-110, 29), s!(-104, 62), s!(-81, 60), s!(-39, 37), s!(34, 5), s!(82, -5), s!(131, -20), s!(202, -69)];
27 |
--------------------------------------------------------------------------------
/src/cache/killers.rs:
--------------------------------------------------------------------------------
1 | use crate::engine::*;
2 | use crate::state::movescan::Move;
3 | use crate::utils::assert_fast;
4 | use std::alloc;
5 | use std::alloc::Layout;
6 | use std::mem;
7 |
8 | const KILLER_SLOTS: usize = 2;
9 |
10 | pub struct KTable {
11 | pub table: Box<[[KTableEntry; KILLER_SLOTS]; MAX_DEPTH as usize]>,
12 | }
13 |
14 | pub struct KTableEntry {
15 | pub data: Move,
16 | }
17 |
18 | impl KTable {
19 | /// Adds a new killer `r#move` at the level specified by `ply` value. Maximal amount of slots for each of them is set by
20 | /// [KILLER_SLOTS] constant, and newer entries have always a priority over old ones. If there's already exactly the same
21 | /// move in the slot 0, the table is not changed.
22 | pub fn add(&mut self, ply: u16, r#move: Move) {
23 | assert_fast!(r#move.is_some());
24 |
25 | if ply >= MAX_DEPTH as u16 || self.table[ply as usize][0].data == r#move {
26 | return;
27 | }
28 |
29 | for slot_index in (1..KILLER_SLOTS).rev() {
30 | let entry = &mut self.table[ply as usize][slot_index - 1];
31 | self.table[ply as usize][slot_index].data = entry.data;
32 | }
33 |
34 | self.table[ply as usize][0].data = r#move;
35 | }
36 |
37 | /// Gets all killer moves at the level specified by `ply`.
38 | pub fn get(&self, ply: u16) -> [Move; KILLER_SLOTS] {
39 | let mut result = [Move::default(); KILLER_SLOTS];
40 |
41 | if ply >= MAX_DEPTH as u16 {
42 | return result;
43 | }
44 |
45 | for (index, slot) in self.table[ply as usize].iter().enumerate() {
46 | result[index] = slot.data
47 | }
48 |
49 | result
50 | }
51 |
52 | /// Clears all killer moves at the level specified by `ply`.
53 | pub fn clear(&mut self, ply: u16) {
54 | if ply >= MAX_DEPTH as u16 {
55 | return;
56 | }
57 |
58 | for slot_index in 0..KILLER_SLOTS {
59 | self.table[ply as usize][slot_index].data = Move::default();
60 | }
61 | }
62 | }
63 |
64 | impl Default for KTable {
65 | /// Constructs a default instance of [KTable] by allocating `KILLER_SLOTS * MAX_DEPTH * mem::size_of::()`
66 | /// boxed array with zeroed elements.
67 | fn default() -> Self {
68 | const SIZE: usize = mem::size_of::();
69 | unsafe {
70 | let ptr = alloc::alloc_zeroed(Layout::from_size_align(KILLER_SLOTS * MAX_DEPTH as usize * SIZE, SIZE).unwrap());
71 | Self { table: Box::from_raw(ptr as *mut [[KTableEntry; KILLER_SLOTS]; MAX_DEPTH as usize]) }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/cache/history.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::assert_fast;
2 | use std::alloc;
3 | use std::alloc::Layout;
4 | use std::cmp;
5 | use std::mem;
6 |
7 | const AGING_DIVISOR: u32 = 16;
8 |
9 | pub struct HTable {
10 | pub table: Box<[[HTableEntry; 64]; 64]>,
11 | pub max: u32,
12 | }
13 |
14 | pub struct HTableEntry {
15 | pub data: u32,
16 | }
17 |
18 | impl HTable {
19 | /// Increases `[from][to]` history slot value based on `depth`.
20 | pub fn add(&mut self, from: usize, to: usize, depth: u8) {
21 | assert_fast!(from < 64);
22 | assert_fast!(to < 64);
23 |
24 | let entry = &mut self.table[from][to];
25 | let value = (depth as u32).pow(2);
26 | let updated_value = entry.data + value;
27 | self.max = cmp::max(self.max, updated_value);
28 |
29 | entry.data = updated_value;
30 | }
31 |
32 | /// Punishes `[from][to]` history slot value based on `depth`.
33 | pub fn punish(&mut self, from: usize, to: usize, depth: u8) {
34 | assert_fast!(from < 64);
35 | assert_fast!(to < 64);
36 |
37 | let entry = &mut self.table[from][to];
38 | let value = depth as u32;
39 | let updated_value = match value <= entry.data {
40 | true => entry.data - value,
41 | false => 0,
42 | };
43 |
44 | entry.data = updated_value;
45 | }
46 |
47 | /// Gets `[from][to]` history slot value, relative to `max`.
48 | pub fn get(&self, from: usize, to: usize, max: u8) -> u8 {
49 | assert_fast!(from < 64);
50 | assert_fast!(to < 64);
51 | assert_fast!(max > 0);
52 | assert_fast!(self.max > 0);
53 |
54 | (self.table[from][to].data * (max as u32)).div_ceil(self.max) as u8
55 | }
56 |
57 | /// Ages all values in the history table by dividing them by the [AGING_DIVISOR].
58 | pub fn age_values(&mut self) {
59 | for row in self.table.iter_mut() {
60 | for entry in row {
61 | entry.data = entry.data.div_ceil(AGING_DIVISOR);
62 | }
63 | }
64 |
65 | self.max = self.age_value(self.max);
66 | }
67 |
68 | /// Ages a single value by dividing value by the [AGING_DIVISOR].
69 | fn age_value(&self, value: u32) -> u32 {
70 | value.div_ceil(AGING_DIVISOR)
71 | }
72 | }
73 |
74 | impl Default for HTable {
75 | /// Constructs a default instance of [HTable] by allocating `64 * 64 * mem::size_of::()`
76 | /// boxed array with zeroed elements.
77 | fn default() -> Self {
78 | const SIZE: usize = mem::size_of::();
79 | unsafe {
80 | let ptr = alloc::alloc_zeroed(Layout::from_size_align(64 * 64 * SIZE, SIZE).unwrap());
81 | Self { table: Box::from_raw(ptr as *mut [[HTableEntry; 64]; 64]), max: 1 }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | on:
2 | release:
3 | types:
4 | - created
5 | env:
6 | CRATE_NAME: inanis
7 | OUTPUT_NAME: inanis
8 | jobs:
9 | build_release:
10 | name: ${{ matrix.name }}
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | include:
15 | - name: Windows 32-bit x86
16 | target: i686-pc-windows-gnu
17 | output: windows_32bit_x86
18 | extension: .exe
19 |
20 | - name: Windows 64-bit x86-64
21 | target: x86_64-pc-windows-gnu
22 | output: windows_64bit_x86-64
23 | extension: .exe
24 |
25 | - name: Windows 64-bit x86-64 POPCNT
26 | target: x86_64-pc-windows-gnu
27 | output: windows_64bit_x86-64_popcnt
28 | features: +popcnt
29 | extension: .exe
30 |
31 | - name: Windows 64-bit x86-64 POPCNT BMI2
32 | target: x86_64-pc-windows-gnu
33 | output: windows_64bit_x86-64_popcnt_bmi2
34 | features: +popcnt,+lzcnt,+bmi1,+bmi2
35 | extension: .exe
36 |
37 | - name: Linux 32-bit x86
38 | target: i686-unknown-linux-musl
39 | output: linux_32bit_x86
40 |
41 | - name: Linux 64-bit x86-64
42 | target: x86_64-unknown-linux-musl
43 | output: linux_64bit_x86-64
44 |
45 | - name: Linux 64-bit x86-64 POPCNT
46 | target: x86_64-unknown-linux-musl
47 | output: linux_64bit_x86-64_popcnt
48 | features: +popcnt
49 |
50 | - name: Linux 64-bit x86-64 POPCNT BMI2
51 | target: x86_64-unknown-linux-musl
52 | output: linux_64bit_x86-64_popcnt_bmi2
53 | features: +popcnt,+lzcnt,+bmi1,+bmi2
54 |
55 | - name: Linux 32-bit ARM
56 | target: arm-unknown-linux-musleabi
57 | output: linux_32bit_arm
58 |
59 | - name: Linux 64-bit AArch64
60 | target: aarch64-unknown-linux-musl
61 | output: linux_64bit_aarch64
62 | steps:
63 | - uses: actions/checkout@master
64 | - uses: actions-rs/toolchain@v1
65 | with:
66 | profile: minimal
67 | override: true
68 | - uses: actions-rs/cargo@v1
69 | with:
70 | command: rustc
71 | args: --release --bin inanis --target=${{ matrix.target }} --features syzygy -- -C target-feature=${{ matrix.features }}
72 | use-cross: true
73 | - uses: thebongy/version-check@v1
74 | with:
75 | file: Cargo.toml
76 | tagFormat: ${version}
77 | id: version_check
78 | - run: mv target/${{ matrix.target }}/release/${{ env.CRATE_NAME }}${{ matrix.extension }} ${{ env.CRATE_NAME }}${{ matrix.extension }}
79 | - run: mv misc/${{ env.CRATE_NAME }}.bmp ${{ env.CRATE_NAME }}.bmp
80 | - run: zip ${{ env.OUTPUT_NAME }}_${{ steps.version_check.outputs.releaseVersion }}_${{ matrix.output }}.zip ${{ env.CRATE_NAME }}${{ matrix.extension }} ${{ env.OUTPUT_NAME }}.bmp LICENSE.md
81 | - uses: JasonEtco/upload-to-release@master
82 | with:
83 | args: ${{ env.OUTPUT_NAME }}_${{ steps.version_check.outputs.releaseVersion }}_${{ matrix.output }}.zip application/octet-stream
84 | env:
85 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/src/evaluation/material.rs:
--------------------------------------------------------------------------------
1 | use super::PackedEval;
2 | use crate::evaluation::params;
3 | use crate::state::representation::Board;
4 | use crate::state::*;
5 | use crate::utils::assert_fast;
6 | use crate::utils::bithelpers::BitHelpers;
7 |
8 | #[cfg(feature = "dev")]
9 | use crate::tuning::tuner::TunerCoeff;
10 |
11 | pub struct MaterialData {
12 | pub bishop_pair: i8,
13 | pub pawns_attacking_pieces: i8,
14 | }
15 |
16 | /// Evaluates material on the `board` and returns score from the white color perspective (more than 0 when advantage, less than 0 when disadvantage).
17 | /// The piece values themself are included in PST so it's no longer evaluated here, instead other features like bishop pair are processed.
18 | pub fn evaluate(board: &Board) -> PackedEval {
19 | let mut result = PackedEval::default();
20 | let white_data = get_material_data(board, WHITE);
21 | let black_data = get_material_data(board, BLACK);
22 |
23 | result += (white_data.bishop_pair - black_data.bishop_pair) * params::BISHOP_PAIR;
24 | result += (white_data.pawns_attacking_pieces - black_data.pawns_attacking_pieces) * params::PAWNS_ATTACKING_PIECES;
25 |
26 | result
27 | }
28 |
29 | /// Gets material data for `board` and `color`.
30 | fn get_material_data(board: &Board, color: usize) -> MaterialData {
31 | assert_fast!(color < 2);
32 |
33 | let bishop_pair = if board.pieces[color][BISHOP].bit_count() == 2 { 1 } else { 0 };
34 | let enemy_pieces = board.occupancy[color ^ 1] & !board.pieces[color ^ 1][PAWN];
35 | let pawns_attacking_pieces = (board.pawn_attacks[color] & enemy_pieces).bit_count() as i8;
36 |
37 | MaterialData { bishop_pair, pawns_attacking_pieces }
38 | }
39 |
40 | /// Gets coefficients of material for `board` and inserts them into `coeffs`. Similarly, their indices (starting from `index`) are inserted into `indices`.
41 | #[cfg(feature = "dev")]
42 | pub fn get_coeffs(board: &Board, index: &mut u16, coeffs: &mut Vec, indices: &mut Vec) {
43 | let white_data = get_material_data(board, WHITE);
44 | let black_data = get_material_data(board, BLACK);
45 |
46 | let mut data = [
47 | TunerCoeff::new(board.pieces[WHITE][PAWN].bit_count() as i8 - board.pieces[BLACK][PAWN].bit_count() as i8, OPENING),
48 | TunerCoeff::new(board.pieces[WHITE][KNIGHT].bit_count() as i8 - board.pieces[BLACK][KNIGHT].bit_count() as i8, OPENING),
49 | TunerCoeff::new(board.pieces[WHITE][BISHOP].bit_count() as i8 - board.pieces[BLACK][BISHOP].bit_count() as i8, OPENING),
50 | TunerCoeff::new(board.pieces[WHITE][ROOK].bit_count() as i8 - board.pieces[BLACK][ROOK].bit_count() as i8, OPENING),
51 | TunerCoeff::new(board.pieces[WHITE][QUEEN].bit_count() as i8 - board.pieces[BLACK][QUEEN].bit_count() as i8, OPENING),
52 | TunerCoeff::new(board.pieces[WHITE][KING].bit_count() as i8 - board.pieces[BLACK][KING].bit_count() as i8, OPENING),
53 | TunerCoeff::new(white_data.bishop_pair - black_data.bishop_pair, OPENING),
54 | TunerCoeff::new(white_data.bishop_pair - black_data.bishop_pair, ENDING),
55 | TunerCoeff::new(white_data.pawns_attacking_pieces - black_data.pawns_attacking_pieces, OPENING),
56 | TunerCoeff::new(white_data.pawns_attacking_pieces - black_data.pawns_attacking_pieces, ENDING),
57 | ];
58 |
59 | for coeff in &mut data {
60 | let (value, _) = coeff.get_data();
61 | if value != 0 {
62 | coeffs.push(coeff.clone());
63 | indices.push(*index);
64 | }
65 |
66 | *index += 1;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/see_tests.rs:
--------------------------------------------------------------------------------
1 | #![allow(clippy::eq_op)]
2 |
3 | #[cfg(test)]
4 | mod see_tests {
5 | use inanis::engine;
6 | use inanis::engine::see;
7 | use inanis::engine::see::*;
8 | use inanis::state::representation::Board;
9 | use inanis::state::*;
10 | use std::mem::MaybeUninit;
11 | use std::sync::Once;
12 |
13 | static INIT: Once = Once::new();
14 |
15 | const P: i16 = SEE_PAWN_VALUE as i16 * 50;
16 | const N: i16 = SEE_KNISHOP_VALUE as i16 * 50;
17 | const B: i16 = SEE_KNISHOP_VALUE as i16 * 50;
18 | const R: i16 = SEE_ROOK_VALUE as i16 * 50;
19 | const Q: i16 = SEE_QUEEN_VALUE as i16 * 50;
20 |
21 | macro_rules! see_tests {
22 | ($($name:ident: $fen:expr, $move:expr, $expected_result:expr, )*) => {
23 | $(
24 | #[test]
25 | fn $name() {
26 | INIT.call_once(|| {
27 | see::init();
28 | movegen::init();
29 | });
30 |
31 | let board = Board::new_from_fen($fen).unwrap();
32 | let mut moves = [MaybeUninit::uninit(); engine::MAX_MOVES_COUNT];
33 | let moves_count = board.get_all_moves(&mut moves, u64::MAX);
34 |
35 | for move_index in 0..moves_count {
36 | let r#move = unsafe { moves[move_index].assume_init() };
37 | if r#move.to_long_notation() == $move {
38 | let attacking_piece = board.get_piece(r#move.get_from());
39 | let target_piece = board.get_piece(r#move.get_to());
40 | let attackers = board.get_attacking_pieces(board.stm ^ 1, r#move.get_to());
41 | let defenders = board.get_attacking_pieces(board.stm, r#move.get_to());
42 |
43 | assert_eq!($expected_result, see::get(attacking_piece, target_piece, attackers, defenders));
44 | return;
45 | }
46 | }
47 |
48 | assert!(false);
49 | }
50 | )*
51 | }
52 | }
53 |
54 | see_tests! {
55 | see_simple_01: "8/8/8/4p3/3P4/8/8/8 w - - 0 1", "d4e5", P,
56 | see_simple_02: "8/8/5p2/4p3/3P4/8/8/8 w - - 0 1", "d4e5", P - P,
57 | see_simple_03: "8/8/5p2/4p3/3P4/8/7B/8 w - - 0 1", "d4e5", P - P + P,
58 | see_simple_04: "8/8/5p2/4p3/3P4/8/7B/8 w - - 0 1", "h2e5", P - B + P,
59 | see_simple_05: "8/8/8/3k4/3P4/8/8/8 b - - 0 1", "d5d4", P,
60 | see_simple_06: "8/8/2n2b2/8/3P4/8/4N3/8 b - - 0 1", "c6d4", P - N + N,
61 | see_complex_01: "8/2bn1n2/8/4p3/6N1/2B2N2/8/8 w - - 0 1", "f3e5", P - N + N - N,
62 | see_complex_02: "8/2bn1n2/8/4p3/6N1/2B2N2/8/4Q3 w - - 0 1", "f3e5", P - N + N - N + N - B + B,
63 | see_complex_03: "8/3n2b1/2n5/4R3/5P2/3N1N2/8/8 b - - 0 1", "d7e5", R - N,
64 | see_complex_04: "8/3n2b1/2nq4/4R3/5P2/3N1N2/8/8 b - - 0 1", "d6e5", R - Q + P - N + N - N + N,
65 | see_complex_05: "8/8/2q5/3r4/4b3/8/6P1/4K1R1 b - - 0 1", "e4g2", P - B,
66 | see_complex_06: "8/8/2q5/2b5/2r5/8/2P5/2R1K3 b - - 0 1", "c4c2", P - R,
67 | see_xray_01: "4r3/8/4p3/8/8/8/4R3/4R3 w - - 0 1", "e2e6", P - R + R,
68 | see_xray_02: "4n3/8/5p2/8/8/2B5/1Q6/8 w - - 0 1", "c3f6", P - B + N,
69 | see_xray_03: "8/8/5p1q/8/8/5Q2/8/5R2 w - - 0 1", "f3f6", P - Q + Q,
70 | see_xray_04: "4q3/4r3/4r3/8/8/RQR1P3/8/8 b - - 0 1", "e6e3", P - R + R - R + Q - Q,
71 | see_xray_05: "7q/8/5b2/8/8/2B5/3P4/8 b - - 0 1", "f6c3", B - B + P,
72 | see_xray_06: "4r3/8/4q3/8/4P3/5P2/8/8 b - - 0 1", "e6e4", P - Q + P,
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/perft/mod.rs:
--------------------------------------------------------------------------------
1 | use self::context::PerftContext;
2 | use crate::engine;
3 | use crate::state::*;
4 | use crate::utils::panic_fast;
5 | use std::mem::MaybeUninit;
6 |
7 | pub mod context;
8 | pub mod divided;
9 | pub mod fast;
10 | pub mod normal;
11 |
12 | /// Internal perft function, common for every mode.
13 | pub fn run_internal(context: &mut PerftContext, depth: i32) -> u64 {
14 | if context.check_integrity {
15 | let original_hash = context.board.state.hash;
16 | let original_pawn_hash = context.board.state.pawn_hash;
17 | let original_eval = context.board.evaluate_without_cache(WHITE);
18 |
19 | context.board.recalculate_hashes();
20 | context.board.recalculate_incremental_values();
21 |
22 | if original_hash != context.board.state.hash {
23 | panic_fast!(
24 | "Integrity check failed, invalid hash: fen={}, original_hash={}, context.board.state.hash={}",
25 | context.board,
26 | original_hash,
27 | context.board.state.hash
28 | );
29 | }
30 |
31 | if original_pawn_hash != context.board.state.pawn_hash {
32 | panic_fast!(
33 | "Integrity check failed, invalid pawn hash: fen={}, original_pawn_hash={}, context.board.state.pawn_hash={}",
34 | context.board,
35 | original_pawn_hash,
36 | context.board.state.pawn_hash
37 | );
38 | }
39 |
40 | let eval = context.board.evaluate_without_cache(WHITE);
41 | if original_eval != eval {
42 | panic_fast!("Integrity check failed, invalid evaluation: fen={}, original_eval={}, eval={}", context.board, original_eval, eval)
43 | }
44 | }
45 |
46 | if depth <= 0 {
47 | return 1;
48 | }
49 |
50 | if context.fast {
51 | if let Some(entry) = context.hashtable.get(context.board.state.hash, depth as u8) {
52 | return entry.leafs_count;
53 | }
54 | }
55 |
56 | let mut moves = [MaybeUninit::uninit(); engine::MAX_MOVES_COUNT];
57 | let moves_count = context.board.get_all_moves(&mut moves, u64::MAX);
58 |
59 | let mut count = 0;
60 | for r#move in &moves[0..moves_count] {
61 | let r#move = unsafe { r#move.assume_init() };
62 | if context.check_integrity && !r#move.is_legal(context.board) {
63 | panic_fast!("Integrity check failed, illegal move: fen={}, r#move.data={}", context.board, r#move.data);
64 | }
65 |
66 | context.board.make_move(r#move);
67 |
68 | if !context.board.is_king_checked(context.board.stm ^ 1) {
69 | count += run_internal(context, depth - 1);
70 |
71 | if !context.fast && depth == 1 {
72 | if r#move.is_capture() {
73 | context.stats.captures += 1;
74 | }
75 |
76 | if r#move.is_en_passant() {
77 | context.stats.en_passants += 1;
78 | }
79 |
80 | if r#move.is_castling() {
81 | context.stats.castles += 1;
82 | }
83 |
84 | if r#move.is_promotion() {
85 | context.stats.promotions += 1;
86 | }
87 |
88 | if context.board.is_king_checked(context.board.stm) {
89 | context.stats.checks += 1;
90 | }
91 | }
92 | }
93 |
94 | context.board.undo_move(r#move);
95 | }
96 |
97 | if context.fast {
98 | context.hashtable.add(context.board.state.hash, depth as u8, count);
99 | }
100 |
101 | count
102 | }
103 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "rust: cargo clean",
6 | "type": "shell",
7 | "command": "cargo clean",
8 | "group": {
9 | "kind": "build"
10 | },
11 | },
12 | {
13 | "label": "rust: cargo fmt",
14 | "type": "shell",
15 | "command": "cargo fmt -- --check",
16 | "group": {
17 | "kind": "build"
18 | },
19 | },
20 | {
21 | "label": "rust: cargo clippy",
22 | "type": "shell",
23 | "command": "cargo clippy -- -Dwarnings",
24 | "group": {
25 | "kind": "build"
26 | },
27 | },
28 | {
29 | "label": "build debug windows",
30 | "type": "shell",
31 | "command": "cargo rustc --bin inanis --target=x86_64-pc-windows-msvc --features dev,syzygy -- -C target-feature=+popcnt,+lzcnt,+bmi1,+bmi2",
32 | "group": {
33 | "kind": "build"
34 | },
35 | },
36 | {
37 | "label": "build debug linux",
38 | "type": "shell",
39 | "command": "cross rustc --bin inanis --target=aarch64-unknown-linux-musl --features dev,syzygy -- -C linker=rust-lld",
40 | "group": {
41 | "kind": "build"
42 | },
43 | },
44 | {
45 | "label": "build release windows",
46 | "type": "shell",
47 | "command": "cargo rustc --bin inanis --release --target=x86_64-pc-windows-msvc --features syzygy -- -C target-feature=+popcnt,+lzcnt,+bmi1,+bmi2 -C llvm-args=-x86-asm-syntax=intel --emit asm",
48 | "group": {
49 | "kind": "build"
50 | },
51 | },
52 | {
53 | "label": "build release windows DEV",
54 | "type": "shell",
55 | "command": "cargo rustc --bin inanis --release --target=x86_64-pc-windows-msvc --features dev,syzygy -- -C target-feature=+popcnt,+lzcnt,+bmi1,+bmi2 -C llvm-args=-x86-asm-syntax=intel --emit asm",
56 | "group": {
57 | "kind": "build"
58 | },
59 | },
60 | {
61 | "label": "build release linux",
62 | "type": "shell",
63 | "command": "cross rustc --bin inanis --release --target=aarch64-unknown-linux-musl --features syzygy -- -C linker=rust-lld -C llvm-args=-x86-asm-syntax=intel --emit asm",
64 | "group": {
65 | "kind": "build"
66 | },
67 | },
68 | {
69 | "label": "build release linux DEV",
70 | "type": "shell",
71 | "command": "cross rustc --bin inanis --release --target=aarch64-unknown-linux-musl --features dev,syzygy -- -C linker=rust-lld -C llvm-args=-x86-asm-syntax=intel --emit asm",
72 | "group": {
73 | "kind": "build"
74 | },
75 | },
76 | {
77 | "label": "build and run release windows",
78 | "type": "shell",
79 | "command": "${workspaceFolder}/target/x86_64-pc-windows-msvc/release/inanis.exe",
80 | "group": {
81 | "kind": "build"
82 | },
83 | "dependsOn": [
84 | "build release windows"
85 | ]
86 | },
87 | {
88 | "label": "build and run release windows DEV",
89 | "type": "shell",
90 | "command": "${workspaceFolder}/target/x86_64-pc-windows-msvc/release/inanis.exe",
91 | "group": {
92 | "kind": "build"
93 | },
94 | "dependsOn": [
95 | "build release windows DEV"
96 | ]
97 | }
98 | ],
99 | }
--------------------------------------------------------------------------------
/tests/board_tests.rs:
--------------------------------------------------------------------------------
1 | #[cfg(test)]
2 | mod board_tests {
3 | use inanis::engine::see;
4 | use inanis::state::representation::Board;
5 | use inanis::state::*;
6 | use std::sync::Once;
7 |
8 | static INIT: Once = Once::new();
9 |
10 | macro_rules! is_square_attacked_tests {
11 | ($($name:ident: $fen:expr, $white_mask:expr, $black_mask:expr,)*) => {
12 | $(
13 | #[test]
14 | fn $name() {
15 | INIT.call_once(|| {
16 | see::init();
17 | movegen::init();
18 | });
19 |
20 | let board = Board::new_from_fen($fen).unwrap();
21 |
22 | for color in ALL_COLORS {
23 | let mut result = 0u64;
24 | for square in ALL_SQUARES {
25 | if board.is_square_attacked(color, square) {
26 | result |= 1u64 << square;
27 | }
28 | }
29 |
30 | match color {
31 | WHITE => assert_eq!($white_mask, result),
32 | BLACK => assert_eq!($black_mask, result),
33 | _ => panic!("Invalid value: color={}", color)
34 | };
35 | }
36 | }
37 | )*
38 | }
39 | }
40 |
41 | is_square_attacked_tests! {
42 | is_square_attacked_default: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 9151313343305220096, 16777086,
43 | is_square_attacked_mid_game1: "5rk1/2b1qp1p/1r2p1pB/1ppnn3/3pN3/1P1P2P1/2P1QPBP/R4RK1 b - - 7 22", 18410713627276083200, 9548357590732224511,
44 | is_square_attacked_mid_game2: "2k4r/1p3pp1/p2p2n1/2P1p2q/P1P1P3/3PBPP1/2R3Qr/5RK1 b - - 2 22", 9185565806661272321, 89568307576831,
45 | is_square_attacked_mid_game3: "r6k/p1B4p/Pp3rp1/3p4/2nP4/2PQ1PPq/7P/1R3RK1 b - - 0 32", 9193953148057572100, 5782712547491610623,
46 | is_square_attacked_end_game1: "8/8/6Q1/8/6k1/1P2q3/7p/7K b - - 14 75", 614821815842708522, 722824474576036674,
47 | is_square_attacked_end_game2: "8/8/4nPk1/8/6pK/8/1R3P1P/2B3r1 b - - 1 54", 1452135070281695805, 4632586923901975616,
48 | is_square_attacked_end_game3: "8/7q/5K2/2q5/6k1/8/8/8 b - - 5 60", 2881868215288276323, 3951704919769088,
49 | }
50 |
51 | macro_rules! get_attacking_pieces_tests {
52 | ($($name:ident: $fen:expr, $color:expr, $square:expr, $expected_result:expr,)*) => {
53 | $(
54 | #[test]
55 | fn $name() {
56 | INIT.call_once(|| {
57 | see::init();
58 | movegen::init();
59 | });
60 |
61 | let board = Board::new_from_fen($fen).unwrap();
62 | assert_eq!($expected_result, board.get_attacking_pieces($color, $square));
63 | }
64 | )*
65 | }
66 | }
67 |
68 | get_attacking_pieces_tests! {
69 | get_attacking_pieces_default: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", BLACK, 18, 3,
70 | get_attacking_pieces_mid_game1: "5rk1/2b1qp1p/1r2p1pB/1ppnn3/3pN3/1P1P2P1/2P1QPBP/R4RK1 b - - 7 22", WHITE, 44, 82,
71 | get_attacking_pieces_mid_game2: "2b2rk1/4qp1p/1r2pnpB/1pp1n3/3pN3/1P1P2P1/2P1QPBP/R4RK1 b - - 7 22", WHITE, 52, 78,
72 | get_attacking_pieces_mid_game3: "2k4r/1p3pp1/p2p2n1/2P1p2q/P1P1P3/3PBPP1/2R3Qr/5RK1 b - - 2 22", BLACK, 5, 50,
73 | get_attacking_pieces_mid_game4: "r6k/p1B4p/Pp3rp1/3p4/2nP4/2PQ1PPq/7P/1R3RK1 b - - 0 32", BLACK, 26, 3,
74 | get_attacking_pieces_mid_game5: "r1b2rk1/1p2qppp/8/1P1R4/p7/Pn2B1P1/4QPBP/3R2K1 b - - 1 22", BLACK, 12, 114,
75 | get_attacking_pieces_end_game1: "8/8/6Q1/8/6k1/1P2q3/7p/7K b - - 14 75", WHITE, 17, 192,
76 | get_attacking_pieces_end_game2: "8/8/4nPk1/8/6pK/8/1R3P1P/2B3r1 b - - 1 54", BLACK, 17, 129,
77 | get_attacking_pieces_end_game3: "8/7q/5K2/2q5/6k1/8/8/8 b - - 5 60", BLACK, 34, 128,
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/cache/pawns.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::assert_fast;
2 | use crate::utils::percent;
3 | use std::mem;
4 | use std::sync::atomic::AtomicI16;
5 | use std::sync::atomic::AtomicU16;
6 | use std::sync::atomic::Ordering;
7 |
8 | pub struct PHTable {
9 | pub table: Vec,
10 | }
11 |
12 | pub struct PHTableEntry {
13 | pub key: AtomicU16,
14 | pub score_opening: AtomicI16,
15 | pub score_ending: AtomicI16,
16 | }
17 |
18 | pub struct PHTableResult {
19 | pub key: u16,
20 | pub score_opening: i16,
21 | pub score_ending: i16,
22 | }
23 |
24 | impl PHTable {
25 | /// Constructs a new instance of [PHTable] by allocating `size` bytes of memory.
26 | pub fn new(size: usize) -> Self {
27 | const SIZE: usize = mem::size_of::();
28 | let mut hashtable = Self { table: Vec::with_capacity(size / SIZE) };
29 |
30 | if size != 0 {
31 | hashtable.table.resize_with(hashtable.table.capacity(), PHTableEntry::default);
32 | }
33 |
34 | hashtable
35 | }
36 |
37 | /// Adds a new entry (storing the key, `score_opening` and `score_ending`) using `hash` to calculate an index.
38 | pub fn add(&self, hash: u64, score_opening: i16, score_ending: i16) {
39 | let key = self.get_key(hash);
40 | let index = self.get_index(hash);
41 | assert_fast!(index < self.table.len());
42 |
43 | self.table[index].set_data(key, score_opening, score_ending);
44 | }
45 |
46 | /// Gets a wanted entry using `hash` to calculate an index. Returns [None] if entry does not exists
47 | /// or `hash` is incompatible with the stored key.
48 | pub fn get(&self, hash: u64) -> Option {
49 | let index = self.get_index(hash);
50 | assert_fast!(index < self.table.len());
51 |
52 | let entry = &self.table[index];
53 | let entry_data = entry.get_data();
54 |
55 | if entry_data.key == self.get_key(hash) {
56 | return Some(entry_data);
57 | }
58 |
59 | None
60 | }
61 |
62 | /// Calculates an approximate percentage usage of the table, based on the first `resolution` entries.
63 | pub fn get_usage(&self, resolution: usize) -> f32 {
64 | let mut filled_entries = 0;
65 | for entry in self.table.iter().take(resolution) {
66 | let entry_data = entry.get_data();
67 | if entry_data.key != 0 {
68 | filled_entries += 1;
69 | }
70 | }
71 |
72 | percent!(filled_entries, resolution)
73 | }
74 |
75 | /// Calculates a key for the `hash` by taking first 16 bits of it.
76 | fn get_key(&self, hash: u64) -> u16 {
77 | hash as u16
78 | }
79 |
80 | /// Calculates an index for the `hash`.
81 | fn get_index(&self, hash: u64) -> usize {
82 | (((hash as u128).wrapping_mul(self.table.len() as u128)) >> 64) as usize
83 | }
84 | }
85 |
86 | impl PHTableEntry {
87 | /// Loads and parses atomic value into a [PHTableResult] struct.
88 | pub fn get_data(&self) -> PHTableResult {
89 | let key = self.key.load(Ordering::Relaxed);
90 | let score_opening = self.score_opening.load(Ordering::Relaxed);
91 | let score_ending = self.score_ending.load(Ordering::Relaxed);
92 |
93 | PHTableResult::new(key, score_opening, score_ending)
94 | }
95 |
96 | /// Converts `key`, `score_opening` and `score_ending` into an atomic word, and stores it.
97 | pub fn set_data(&self, key: u16, score_opening: i16, score_ending: i16) {
98 | self.key.store(key, Ordering::Relaxed);
99 | self.score_opening.store(score_opening, Ordering::Relaxed);
100 | self.score_ending.store(score_ending, Ordering::Relaxed);
101 | }
102 | }
103 |
104 | impl Default for PHTableEntry {
105 | /// Constructs a default instance of [PHTableEntry] with zeroed elements.
106 | fn default() -> Self {
107 | PHTableEntry { key: AtomicU16::new(0), score_opening: AtomicI16::new(0), score_ending: AtomicI16::new(0) }
108 | }
109 | }
110 |
111 | impl PHTableResult {
112 | /// Constructs a new instance of [PHTableResult] with stored `key`, `score_opening` and `score_ending`.
113 | pub fn new(key: u16, score_opening: i16, score_ending: i16) -> Self {
114 | Self { key, score_opening, score_ending }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/cache/perft.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::assert_fast;
2 | use crate::utils::percent;
3 | use std::mem;
4 | use std::sync::atomic::AtomicU64;
5 | use std::sync::atomic::Ordering;
6 |
7 | const BUCKET_SLOTS: usize = 4;
8 |
9 | pub struct PerftHashTable {
10 | pub table: Vec,
11 | }
12 |
13 | #[repr(align(64))]
14 | #[derive(Default)]
15 | pub struct PerftHashTableBucket {
16 | pub entries: [PerftHashTableEntry; BUCKET_SLOTS],
17 | }
18 |
19 | #[derive(Default)]
20 | pub struct PerftHashTableEntry {
21 | pub key: AtomicU64,
22 | pub data: AtomicU64,
23 | }
24 |
25 | pub struct PerftHashTableResult {
26 | pub leafs_count: u64,
27 | }
28 |
29 | impl PerftHashTable {
30 | /// Constructs a new instance of [PerftHashTable] by allocating `size` bytes of memory.
31 | pub fn new(size: usize) -> Self {
32 | const BUCKET_SIZE: usize = mem::size_of::();
33 | let mut hashtable = Self { table: Vec::with_capacity(size / BUCKET_SIZE) };
34 |
35 | if BUCKET_SIZE != 0 {
36 | hashtable.table.resize_with(hashtable.table.capacity(), PerftHashTableBucket::default);
37 | }
38 |
39 | hashtable
40 | }
41 |
42 | /// Adds a new entry (storing `hash`, `depth` and `leafs_count`) using `hash` to calculate an index of the bucket.
43 | pub fn add(&self, hash: u64, depth: u8, leafs_count: u64) {
44 | let index = self.get_index(hash);
45 | assert_fast!(index < self.table.len());
46 |
47 | let bucket = &self.table[index];
48 | let mut smallest_depth = u8::MAX;
49 | let mut smallest_depth_index = 0;
50 |
51 | for (entry_index, entry) in bucket.entries.iter().enumerate() {
52 | let entry_key = entry.key.load(Ordering::Relaxed);
53 | let entry_data = entry.data.load(Ordering::Relaxed);
54 | let entry_depth = ((entry_key ^ entry_data) as u8) & 0xf;
55 |
56 | if entry_depth < smallest_depth {
57 | smallest_depth = entry_depth;
58 | smallest_depth_index = entry_index;
59 | }
60 | }
61 |
62 | let key = (hash & !0xf) | (depth as u64);
63 | let data = leafs_count;
64 |
65 | assert_fast!(smallest_depth_index < BUCKET_SLOTS);
66 | bucket.entries[smallest_depth_index].key.store(key ^ data, Ordering::Relaxed);
67 | bucket.entries[smallest_depth_index].data.store(data, Ordering::Relaxed);
68 | }
69 |
70 | /// Gets a wanted entry from the specified `depth` using `hash` to calculate an index of the bucket.
71 | /// Returns [None] if entry does not exists or `hash` is incompatible with the stored key.
72 | pub fn get(&self, hash: u64, depth: u8) -> Option {
73 | let index = self.get_index(hash);
74 | assert_fast!(index < self.table.len());
75 |
76 | let bucket = &self.table[index];
77 | for entry in &bucket.entries {
78 | let entry_key = entry.key.load(Ordering::Relaxed);
79 | let entry_data = entry.data.load(Ordering::Relaxed);
80 | let key = (hash & !0xf) | (depth as u64);
81 |
82 | if (entry_key ^ entry_data) == key {
83 | return Some(PerftHashTableResult::new(entry_data));
84 | }
85 | }
86 |
87 | None
88 | }
89 |
90 | /// Calculates an approximate percentage usage of the table, based on the first `resolution` entries.
91 | pub fn get_usage(&self, resolution: usize) -> f32 {
92 | let buckets_count_to_check: usize = resolution / BUCKET_SLOTS;
93 | let mut filled_entries = 0;
94 |
95 | for bucket in self.table.iter().take(buckets_count_to_check) {
96 | for entry in &bucket.entries {
97 | if entry.key.load(Ordering::Relaxed) != 0 && entry.data.load(Ordering::Relaxed) != 0 {
98 | filled_entries += 1;
99 | }
100 | }
101 | }
102 |
103 | percent!(filled_entries, resolution)
104 | }
105 |
106 | /// Calculates an index for the `hash`.
107 | fn get_index(&self, hash: u64) -> usize {
108 | (((hash as u128).wrapping_mul(self.table.len() as u128)) >> 64) as usize
109 | }
110 | }
111 |
112 | impl PerftHashTableResult {
113 | /// Constructs a new instance of [PerftHashTableResult] with stored `leafs_count`.
114 | pub fn new(leafs_count: u64) -> Self {
115 | Self { leafs_count }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/engine/qsearch/runner.rs:
--------------------------------------------------------------------------------
1 | use crate::engine::context::SearchContext;
2 | use crate::engine::*;
3 | use crate::state::*;
4 | use crate::utils::assert_fast;
5 | use crate::utils::dev;
6 | use crate::utils::param;
7 | use qsearch::movepick;
8 | use std::cmp;
9 | use std::mem::MaybeUninit;
10 |
11 | /// Entry point of the quiescence search. The main idea here is to reduce the horizon effect by processing capture sequences and eventually
12 | /// make a quiet position suitable for final evaluation. `context`, `ply`, `alpha` and `beta` are provided by the leaf of the regular search.
13 | ///
14 | /// Search steps:
15 | /// - test if the friendly king was not captured earlier
16 | /// - calculate stand-pat score and process initial pruning/alpha update
17 | /// - main loop:
18 | /// - score pruning
19 | /// - futility pruning ()
20 | pub fn run(context: &mut SearchContext, ply: u16, mut alpha: i16, beta: i16) -> i16 {
21 | assert_fast!(alpha <= beta);
22 | assert_fast!(context.board.stm < 2);
23 |
24 | context.stats.q_nodes_count += 1;
25 | context.stats.max_ply = cmp::max(ply, context.stats.max_ply);
26 |
27 | if context.board.pieces[context.board.stm][KING] == 0 {
28 | dev!(context.stats.q_leafs_count += 1);
29 | return -CHECKMATE_SCORE + (ply as i16);
30 | }
31 |
32 | let stand_pat = context.board.evaluate(context.board.stm, &context.phtable, &mut context.stats);
33 | if stand_pat >= beta {
34 | dev!(context.stats.q_leafs_count += 1);
35 | dev!(context.stats.q_beta_cutoffs += 1);
36 | return stand_pat;
37 | }
38 |
39 | alpha = cmp::max(alpha, stand_pat);
40 |
41 | let mut moves = [MaybeUninit::uninit(); MAX_MOVES_COUNT];
42 | let mut move_scores = [MaybeUninit::uninit(); MAX_MOVES_COUNT];
43 | let moves_count = context.board.get_moves::(&mut moves, 0, u64::MAX);
44 |
45 | movepick::assign_move_scores(context, &moves, &mut move_scores, moves_count);
46 |
47 | let mut found = false;
48 | for move_index in 0..moves_count {
49 | let (r#move, score) = movesort::sort_next_move(&mut moves, &mut move_scores, move_index, moves_count);
50 |
51 | if score_pruning_can_be_applied(context, score) {
52 | dev!(context.stats.q_score_pruning_accepted += 1);
53 | break;
54 | } else {
55 | dev!(context.stats.q_score_pruning_rejected += 1);
56 | }
57 |
58 | if futility_pruning_can_be_applied(context, score, stand_pat, alpha) {
59 | dev!(context.stats.q_futility_pruning_accepted += 1);
60 | break;
61 | } else {
62 | dev!(context.stats.q_futility_pruning_rejected += 1);
63 | }
64 |
65 | found = true;
66 |
67 | context.board.make_move(r#move);
68 | let score = -run(context, ply + 1, -beta, -alpha);
69 | context.board.undo_move(r#move);
70 |
71 | alpha = cmp::max(alpha, score);
72 | if alpha >= beta {
73 | dev!(context.stats.q_beta_cutoffs += 1);
74 | if move_index == 0 {
75 | dev!(context.stats.q_perfect_cutoffs += 1);
76 | } else {
77 | dev!(context.stats.q_non_perfect_cutoffs += 1);
78 | }
79 |
80 | break;
81 | }
82 | }
83 |
84 | if !found {
85 | dev!(context.stats.q_leafs_count += 1);
86 | }
87 |
88 | alpha
89 | }
90 |
91 | /// Checks if the score pruning can be applied for `move_score`. The main idea here is to omit all capture sequances, which are clearly
92 | /// loosing material (`move_score` is less than `q_score_pruning_treshold`) and with high probability won't improve alpha.
93 | fn score_pruning_can_be_applied(context: &SearchContext, move_score: i16) -> bool {
94 | move_score < param!(context.params.q_score_pruning_treshold)
95 | }
96 |
97 | /// Checks if the futility pruning can be applied for `move_score`. The main idea here is similar to score pruning, but instead of checking
98 | /// if the specified capture sequence loses some material or not, it checks if the final result added to the `stand_pat` and `q_futility_pruning_margin`
99 | /// will be below alpha - if yes, then we can assume that this move is not enough good to be relevant for the search.
100 | fn futility_pruning_can_be_applied(context: &SearchContext, move_score: i16, stand_pat: i16, alpha: i16) -> bool {
101 | stand_pat + move_score + param!(context.params.q_futility_pruning_margin) < alpha
102 | }
103 |
--------------------------------------------------------------------------------
/src/engine/see.rs:
--------------------------------------------------------------------------------
1 | use crate::state::*;
2 | use crate::utils::assert_fast;
3 | use crate::utils::bithelpers::BitHelpers;
4 | use crate::utils::panic_fast;
5 | use std::alloc;
6 | use std::alloc::Layout;
7 | use std::cmp;
8 | use std::mem;
9 | use std::sync::OnceLock;
10 |
11 | pub const SEE_PAWN_VALUE: i8 = 2;
12 | pub const SEE_KNISHOP_VALUE: i8 = 7;
13 | pub const SEE_ROOK_VALUE: i8 = 10;
14 | pub const SEE_QUEEN_VALUE: i8 = 22;
15 | pub const SEE_KING_VALUE: i8 = 60;
16 |
17 | static SEE_TABLE: OnceLock> = OnceLock::new();
18 |
19 | /// Initializes static exchange evaluation table.
20 | pub fn init() {
21 | const SIZE: usize = mem::size_of::();
22 | unsafe {
23 | let ptr = alloc::alloc_zeroed(Layout::from_size_align(256 * 256 * 6 * SIZE, SIZE).unwrap());
24 | let mut table = Box::from_raw(ptr as *mut [[[i8; 256]; 256]; 6]);
25 |
26 | for target_piece in ALL_PIECES {
27 | for attackers in 0..256 {
28 | for defenders in 0..256 {
29 | table[target_piece][attackers][defenders] = evaluate(target_piece, attackers, defenders);
30 | }
31 | }
32 | }
33 |
34 | let _ = SEE_TABLE.set(table);
35 | }
36 | }
37 |
38 | /// Gets a result of the static exchange evaluation, based on `attacking_piece`, `target_piece`, `attackers` and `defenders`.
39 | pub fn get(attacking_piece: usize, target_piece: usize, attackers: usize, defenders: usize) -> i16 {
40 | assert_fast!(attacking_piece <= 6);
41 | assert_fast!(target_piece <= 6);
42 | assert_fast!(attackers != 0);
43 |
44 | let attacking_piece_index = get_see_piece_index(attacking_piece);
45 | let target_piece_index = get_see_piece_index(target_piece);
46 | let updated_attackers = attackers & !(1 << attacking_piece_index);
47 |
48 | let table = unsafe { SEE_TABLE.get().unwrap_unchecked() };
49 | let see = table[attacking_piece][defenders][updated_attackers];
50 | (get_piece_value(target_piece_index) - see) as i16 * 50
51 | }
52 |
53 | /// Evaluates a static exchange evaluation result, based on `target_piece`, `attackers`, `defenders`.
54 | fn evaluate(target_piece: usize, attackers: usize, defenders: usize) -> i8 {
55 | assert_fast!(target_piece <= 6);
56 |
57 | if attackers == 0 {
58 | return 0;
59 | }
60 |
61 | let attacking_piece_index = attackers.get_lsb().bit_scan();
62 | let target_piece_index = get_see_piece_index(target_piece);
63 |
64 | evaluate_internal(attacking_piece_index, target_piece_index, attackers, defenders)
65 | }
66 |
67 | /// Recursive function called by `evaluate` to help evaluate a static exchange evaluation result.
68 | fn evaluate_internal(attacking_piece: usize, target_piece: usize, attackers: usize, defenders: usize) -> i8 {
69 | assert_fast!(target_piece < 8);
70 |
71 | if attackers == 0 {
72 | return 0;
73 | }
74 |
75 | let target_piece_value = get_piece_value(target_piece);
76 | let new_attackers = attackers & !(1 << attacking_piece);
77 | let new_attacking_piece = match defenders {
78 | 0 => 0,
79 | _ => defenders.get_lsb().bit_scan(),
80 | };
81 |
82 | cmp::max(0, target_piece_value - evaluate_internal(new_attacking_piece, attacking_piece, defenders, new_attackers))
83 | }
84 |
85 | /// Converts `piece` index to SEE piece index, which supports multiple pieces of the same type stored in one variable:
86 | /// - 1 pawn (index 0)
87 | /// - 3 knights/bishops (index 1-3)
88 | /// - 2 rooks (index 4-5)
89 | /// - 1 queen (index 6)
90 | /// - 1 king (index 7)
91 | fn get_see_piece_index(piece: usize) -> usize {
92 | assert_fast!(piece < 6);
93 |
94 | match piece {
95 | PAWN => 0,
96 | KNIGHT => 1,
97 | BISHOP => 1,
98 | ROOK => 4,
99 | QUEEN => 6,
100 | KING => 7,
101 | _ => panic_fast!("Invalid value: piece={}", piece),
102 | }
103 | }
104 |
105 | /// Gets a piece value based on `piece_index` saved in SEE format (look `get_see_piece_index`).
106 | fn get_piece_value(piece_index: usize) -> i8 {
107 | assert_fast!(piece_index < 8);
108 |
109 | match piece_index {
110 | 0 => SEE_PAWN_VALUE, // Pawn
111 | 1 | 2 | 3 => SEE_KNISHOP_VALUE, // 3x Knight/bishop
112 | 4 | 5 => SEE_ROOK_VALUE, // 2x Rook
113 | 6 => SEE_QUEEN_VALUE, // Queen
114 | 7 => SEE_KING_VALUE, // King
115 | _ => panic_fast!("Invalid value: piece_index={}", piece_index),
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/evaluation/safety.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use crate::state::representation::Board;
3 | use crate::utils::assert_fast;
4 | use crate::utils::bithelpers::BitHelpers;
5 | use mobility::EvalAux;
6 |
7 | #[cfg(feature = "dev")]
8 | use crate::tuning::tuner::TunerCoeff;
9 |
10 | pub struct SafetyData {
11 | pub knight_safe_checks: u8,
12 | pub bishop_safe_checks: u8,
13 | pub rook_safe_checks: u8,
14 | pub queen_safe_checks: u8,
15 | }
16 |
17 | /// Evaluates king safety on the `board` and returns score from the white color perspective (more than 0 when advantage,
18 | /// less than 0 when disadvantage). Both additional parameters, `white_aux` and `black_aux`, are
19 | /// calculated during mobility evaluation and are used here to get the final score.
20 | pub fn evaluate(board: &Board, white_aux: &EvalAux, black_aux: &EvalAux) -> PackedEval {
21 | evaluate_color(board, WHITE, white_aux, black_aux) - evaluate_color(board, BLACK, white_aux, black_aux)
22 | }
23 |
24 | /// Evaluates kibg safety on the `board` for the specified `color``, using `white_aux` and `black_aux`.
25 | pub fn evaluate_color(board: &Board, color: usize, white_aux: &EvalAux, black_aux: &EvalAux) -> PackedEval {
26 | assert_fast!(color < 2);
27 |
28 | let mut result = PackedEval::default();
29 | let (stm_aux, nstm_aux) = match color {
30 | WHITE => (white_aux, black_aux),
31 | BLACK => (black_aux, white_aux),
32 | _ => panic_fast!("Invalid value: color={}", color),
33 | };
34 | let data = get_safety_data(board, color, stm_aux, nstm_aux);
35 |
36 | result += params::KING_AREA_THREATS[((stm_aux.king_area_threats) as usize).min(7)];
37 | result += params::KNIGHT_SAFE_CHECKS[((data.knight_safe_checks) as usize).min(7)];
38 | result += params::BISHOP_SAFE_CHECKS[((data.bishop_safe_checks) as usize).min(7)];
39 | result += params::ROOK_SAFE_CHECKS[((data.rook_safe_checks) as usize).min(7)];
40 | result += params::QUEEN_SAFE_CHECKS[((data.queen_safe_checks) as usize).min(7)];
41 |
42 | result
43 | }
44 |
45 | /// Gets safety data for `board`, `color`, `our_aux` and `their_aux`.
46 | pub fn get_safety_data(board: &Board, color: usize, our_aux: &EvalAux, their_aux: &EvalAux) -> SafetyData {
47 | assert_fast!(color < 2);
48 |
49 | let occupancy_bb = board.occupancy[WHITE] | board.occupancy[BLACK];
50 | let enemy_king_square = (board.pieces[color ^ 1][KING]).bit_scan();
51 |
52 | let threats = their_aux.knight_threats | their_aux.bishop_threats | their_aux.rook_threats | their_aux.queen_threats | board.pawn_attacks[color ^ 1];
53 | let knight_moves_bb = movegen::get_knight_moves(enemy_king_square);
54 | let bishop_moves_bb = movegen::get_bishop_moves(occupancy_bb, enemy_king_square);
55 | let rook_moves_bb = movegen::get_rook_moves(occupancy_bb, enemy_king_square);
56 | let queen_moves_bb = movegen::get_queen_moves(occupancy_bb, enemy_king_square);
57 | let king_moves_bb = movegen::get_king_moves(enemy_king_square);
58 |
59 | let knight_safe_checks = ((knight_moves_bb & our_aux.knight_threats) & !threats & !king_moves_bb).bit_count() as u8;
60 | let bishop_safe_checks = ((bishop_moves_bb & our_aux.bishop_threats) & !threats & !king_moves_bb).bit_count() as u8;
61 | let rook_safe_checks = ((rook_moves_bb & our_aux.rook_threats) & !threats & !king_moves_bb).bit_count() as u8;
62 | let queen_safe_checks = ((queen_moves_bb & our_aux.queen_threats) & !threats & !king_moves_bb).bit_count() as u8;
63 |
64 | SafetyData { knight_safe_checks, bishop_safe_checks, rook_safe_checks, queen_safe_checks }
65 | }
66 |
67 | /// Gets coefficients of king safety for `board` and inserts them into `coeffs`. Similarly, their indices (starting from `index`) are inserted into `indices`.
68 | /// Additionally, `white_aux` and `black_aux` calculated during mobility phase are also used here.
69 | #[cfg(feature = "dev")]
70 | pub fn get_coeffs(board: &Board, white_aux: &EvalAux, black_aux: &EvalAux, index: &mut u16, coeffs: &mut Vec, indices: &mut Vec) {
71 | let white_data = get_safety_data(board, WHITE, white_aux, black_aux);
72 | let black_data = get_safety_data(board, BLACK, black_aux, white_aux);
73 |
74 | get_array_coeffs(white_aux.king_area_threats as u8, black_aux.king_area_threats as u8, 8, index, coeffs, indices);
75 | get_array_coeffs(white_data.knight_safe_checks, black_data.knight_safe_checks, 8, index, coeffs, indices);
76 | get_array_coeffs(white_data.bishop_safe_checks, black_data.bishop_safe_checks, 8, index, coeffs, indices);
77 | get_array_coeffs(white_data.rook_safe_checks, black_data.rook_safe_checks, 8, index, coeffs, indices);
78 | get_array_coeffs(white_data.queen_safe_checks, black_data.queen_safe_checks, 8, index, coeffs, indices);
79 | }
80 |
--------------------------------------------------------------------------------
/src/engine/stats.rs:
--------------------------------------------------------------------------------
1 | use std::cmp;
2 | use std::ops;
3 |
4 | #[derive(Default)]
5 | pub struct SearchStats {
6 | pub nodes_count: u64,
7 | pub q_nodes_count: u64,
8 | pub leafs_count: u64,
9 | pub q_leafs_count: u64,
10 |
11 | pub beta_cutoffs: u64,
12 | pub q_beta_cutoffs: u64,
13 |
14 | pub tb_hits: u64,
15 |
16 | pub perfect_cutoffs: u64,
17 | pub q_perfect_cutoffs: u64,
18 | pub non_perfect_cutoffs: u64,
19 | pub q_non_perfect_cutoffs: u64,
20 |
21 | pub pvs_full_window_searches: u64,
22 | pub pvs_zero_window_searches: u64,
23 | pub pvs_rejected_searches: u64,
24 |
25 | pub snmp_attempts: u64,
26 | pub snmp_accepted: u64,
27 | pub snmp_rejected: u64,
28 |
29 | pub nmp_attempts: u64,
30 | pub nmp_accepted: u64,
31 | pub nmp_rejected: u64,
32 |
33 | pub lmp_accepted: u64,
34 | pub lmp_rejected: u64,
35 |
36 | pub razoring_attempts: u64,
37 | pub razoring_accepted: u64,
38 | pub razoring_rejected: u64,
39 |
40 | pub q_score_pruning_accepted: u64,
41 | pub q_score_pruning_rejected: u64,
42 |
43 | pub q_futility_pruning_accepted: u64,
44 | pub q_futility_pruning_rejected: u64,
45 |
46 | pub tt_added: u64,
47 | pub tt_hits: u64,
48 | pub tt_misses: u64,
49 |
50 | pub tt_legal_hashmoves: u64,
51 | pub tt_illegal_hashmoves: u64,
52 | pub ktable_legal_moves: u64,
53 | pub ktable_illegal_moves: u64,
54 | pub cmtable_legal_moves: u64,
55 | pub cmtable_illegal_moves: u64,
56 |
57 | pub phtable_added: u64,
58 | pub phtable_hits: u64,
59 | pub phtable_misses: u64,
60 |
61 | pub movegen_hash_move_stages: u64,
62 | pub movegen_captures_stages: u64,
63 | pub movegen_killers_stages: u64,
64 | pub movegen_counters_stages: u64,
65 | pub movegen_quiets_stages: u64,
66 |
67 | pub max_ply: u16,
68 | }
69 |
70 | impl ops::AddAssign<&SearchStats> for SearchStats {
71 | /// Implements `+=` operator for [SearchStats] by adding all corresponding squares together (except `max_ply`, where the highest value is taken).
72 | fn add_assign(&mut self, rhs: &SearchStats) {
73 | self.nodes_count += rhs.nodes_count;
74 | self.q_nodes_count += rhs.q_nodes_count;
75 | self.leafs_count += rhs.leafs_count;
76 | self.q_leafs_count += rhs.q_leafs_count;
77 | self.beta_cutoffs += rhs.beta_cutoffs;
78 | self.q_beta_cutoffs += rhs.q_beta_cutoffs;
79 |
80 | self.tb_hits += rhs.tb_hits;
81 |
82 | self.perfect_cutoffs += rhs.perfect_cutoffs;
83 | self.q_perfect_cutoffs += rhs.q_perfect_cutoffs;
84 | self.non_perfect_cutoffs += rhs.non_perfect_cutoffs;
85 | self.q_non_perfect_cutoffs += rhs.q_non_perfect_cutoffs;
86 |
87 | self.pvs_full_window_searches += rhs.pvs_full_window_searches;
88 | self.pvs_zero_window_searches += rhs.pvs_zero_window_searches;
89 | self.pvs_rejected_searches += rhs.pvs_rejected_searches;
90 |
91 | self.snmp_attempts += rhs.snmp_attempts;
92 | self.snmp_accepted += rhs.snmp_accepted;
93 | self.snmp_rejected += rhs.snmp_rejected;
94 |
95 | self.nmp_attempts += rhs.nmp_attempts;
96 | self.nmp_accepted += rhs.nmp_accepted;
97 | self.nmp_rejected += rhs.nmp_rejected;
98 |
99 | self.lmp_accepted += rhs.lmp_accepted;
100 | self.lmp_rejected += rhs.lmp_rejected;
101 |
102 | self.razoring_attempts += rhs.razoring_attempts;
103 | self.razoring_accepted += rhs.razoring_accepted;
104 | self.razoring_rejected += rhs.razoring_rejected;
105 |
106 | self.q_score_pruning_accepted += rhs.q_score_pruning_accepted;
107 | self.q_score_pruning_rejected += rhs.q_score_pruning_rejected;
108 |
109 | self.q_futility_pruning_accepted += rhs.q_futility_pruning_accepted;
110 | self.q_futility_pruning_rejected += rhs.q_futility_pruning_rejected;
111 |
112 | self.tt_added += rhs.tt_added;
113 | self.tt_hits += rhs.tt_hits;
114 | self.tt_misses += rhs.tt_misses;
115 |
116 | self.tt_legal_hashmoves += rhs.tt_legal_hashmoves;
117 | self.tt_illegal_hashmoves += rhs.tt_illegal_hashmoves;
118 |
119 | self.phtable_added += rhs.phtable_added;
120 | self.phtable_hits += rhs.phtable_hits;
121 | self.phtable_misses += rhs.phtable_misses;
122 |
123 | self.movegen_hash_move_stages += rhs.movegen_hash_move_stages;
124 | self.movegen_captures_stages += rhs.movegen_captures_stages;
125 | self.movegen_quiets_stages += rhs.movegen_quiets_stages;
126 |
127 | self.max_ply = cmp::max(self.max_ply, rhs.max_ply);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/evaluation/mod.rs:
--------------------------------------------------------------------------------
1 | use crate::state::*;
2 | use crate::utils::panic_fast;
3 | use pst::*;
4 | use std::ops;
5 |
6 | #[cfg(feature = "dev")]
7 | use crate::tuning::tuner::TunerParameter;
8 |
9 | pub mod material;
10 | pub mod mobility;
11 | pub mod params;
12 | pub mod pawns;
13 | pub mod pst;
14 | pub mod safety;
15 |
16 | pub const INITIAL_GAME_PHASE: u8 = 24;
17 | pub const PIECE_VALUES: [i16; 6] = [100, 337, 338, 521, 1050, 10000];
18 | pub const PIECE_PHASE_VALUES: [u8; 6] = [0, 1, 1, 2, 4, 0];
19 |
20 | macro_rules! s {
21 | ($opening_score: expr, $ending_score: expr) => {
22 | PackedEval::new($opening_score, $ending_score)
23 | };
24 | }
25 | pub(crate) use s;
26 |
27 | #[derive(Copy, Clone, Default)]
28 | pub struct PackedEval {
29 | pub data: i32,
30 | }
31 |
32 | impl PackedEval {
33 | /// Constructs a new instance of [PackedEval] with `opening` and `ending` scores.
34 | pub const fn new(opening: i16, ending: i16) -> Self {
35 | Self { data: ((ending as i32) << 16) + opening as i32 }
36 | }
37 |
38 | /// Constructs a new instance of [PackedEval] with raw `data`.
39 | pub const fn new_raw(data: i32) -> Self {
40 | Self { data }
41 | }
42 |
43 | /// Gets opening score from the internal data.
44 | pub fn get_opening(&self) -> i16 {
45 | self.data as i16
46 | }
47 |
48 | /// Gets ending score from the internal data.
49 | pub fn get_ending(&self) -> i16 {
50 | ((self.data + 0x8000) >> 16) as i16
51 | }
52 |
53 | /// Blends `opening_score` and `ending_score` with the ratio passed in `game_phase`. The ratio is a number from 0 to `max_game_phase`, where:
54 | /// - `max_game_phase` represents a board with the initial state set (opening phase)
55 | /// - 0 represents a board without any piece (ending phase)
56 | /// - every value between them represents a board state somewhere in the middle game
57 | pub fn taper_score(&self, game_phase: u8) -> i16 {
58 | let opening_score = (self.get_opening() as i32) * (game_phase as i32);
59 | let ending_score = (self.get_ending() as i32) * ((INITIAL_GAME_PHASE as i32) - (game_phase as i32));
60 |
61 | ((opening_score + ending_score) / (INITIAL_GAME_PHASE as i32)) as i16
62 | }
63 |
64 | /// Gets tuner coefficients for opening and ending score, constrained by `min`, `min_init`, `max_init` and `max`. Additionally, `offset` is added to each score.
65 | #[cfg(feature = "dev")]
66 | pub fn to_tuner_params(&self, min: i16, min_init: i16, max_init: i16, max: i16, offset: i16) -> [TunerParameter; 2] {
67 | use crate::utils::assert_fast;
68 |
69 | assert_fast!(min <= max);
70 | assert_fast!(min_init <= max_init);
71 | assert_fast!(min_init >= min && min_init <= max);
72 | assert_fast!(max_init >= min && max_init <= max);
73 |
74 | [
75 | TunerParameter::new(self.get_opening() + offset, min, min_init, max_init, max),
76 | TunerParameter::new(self.get_ending() + offset, min, min_init, max_init, max),
77 | ]
78 | }
79 | }
80 |
81 | impl ops::Add for PackedEval {
82 | type Output = PackedEval;
83 |
84 | /// Implements `+` operator for [PackedEval].
85 | fn add(self, rhs: PackedEval) -> PackedEval {
86 | PackedEval::new_raw(self.data + rhs.data)
87 | }
88 | }
89 |
90 | impl ops::AddAssign for PackedEval {
91 | /// Implements `+=` operator for [PackedEval].
92 | fn add_assign(&mut self, rhs: PackedEval) {
93 | self.data += rhs.data;
94 | }
95 | }
96 |
97 | impl ops::Sub for PackedEval {
98 | type Output = PackedEval;
99 |
100 | /// Implements `-` operator for [PackedEval].
101 | fn sub(self, rhs: PackedEval) -> PackedEval {
102 | PackedEval::new_raw(self.data - rhs.data)
103 | }
104 | }
105 |
106 | impl ops::SubAssign for PackedEval {
107 | /// Implements `-=` operator for [PackedEval].
108 | fn sub_assign(&mut self, rhs: PackedEval) {
109 | self.data -= rhs.data;
110 | }
111 | }
112 |
113 | impl ops::Mul for i8 {
114 | type Output = PackedEval;
115 |
116 | /// Implements `*` operator for [PackedEval].
117 | fn mul(self, rhs: PackedEval) -> PackedEval {
118 | PackedEval::new_raw(self as i32 * rhs.data)
119 | }
120 | }
121 |
122 | impl ops::Mul for i16 {
123 | type Output = PackedEval;
124 |
125 | /// Implements `*` operator for [PackedEval].
126 | fn mul(self, rhs: PackedEval) -> PackedEval {
127 | PackedEval::new_raw(self as i32 * rhs.data)
128 | }
129 | }
130 |
131 | impl ops::Mul for i32 {
132 | type Output = PackedEval;
133 |
134 | /// Implements `*` operator for [PackedEval].
135 | fn mul(self, rhs: PackedEval) -> PackedEval {
136 | PackedEval::new_raw(self * rhs.data)
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/tablebases/syzygy/probe.rs:
--------------------------------------------------------------------------------
1 | use super::bindings::*;
2 | use crate::engine;
3 | use crate::state::movescan::Move;
4 | use crate::state::movescan::MoveFlags;
5 | use crate::state::representation::Board;
6 | use crate::state::*;
7 | use crate::tablebases::WdlDtzResult;
8 | use crate::tablebases::WdlResult;
9 | use std::ffi::CString;
10 | use std::mem::MaybeUninit;
11 | use std::ptr;
12 |
13 | /// Loads Syzygy tablebases stored in `syzygy_path` location.
14 | pub fn init(syzygy_path: &str) {
15 | #[cfg(feature = "syzygy")]
16 | unsafe {
17 | tb_init(CString::new(syzygy_path).unwrap().as_ptr());
18 | }
19 | }
20 |
21 | /// Gets maximal pieces count supported by loaded Syzygy tablebases. Returns 0 if the feature is disabled.
22 | pub fn get_max_pieces_count() -> u8 {
23 | #[cfg(feature = "syzygy")]
24 | unsafe {
25 | return TB_LARGEST as u8;
26 | }
27 |
28 | 0
29 | }
30 |
31 | /// Gets WDL (Win-Draw-Loss) for the position specified in `board`. Returns [None] if data couldn't be obtained or the feature is disabled.
32 | pub fn get_wdl(board: &Board) -> Option {
33 | #[cfg(feature = "syzygy")]
34 | unsafe {
35 | let wdl = tb_probe_wdl(
36 | board.occupancy[WHITE],
37 | board.occupancy[BLACK],
38 | board.pieces[WHITE][KING] | board.pieces[BLACK][KING],
39 | board.pieces[WHITE][QUEEN] | board.pieces[BLACK][QUEEN],
40 | board.pieces[WHITE][ROOK] | board.pieces[BLACK][ROOK],
41 | board.pieces[WHITE][BISHOP] | board.pieces[BLACK][BISHOP],
42 | board.pieces[WHITE][KNIGHT] | board.pieces[BLACK][KNIGHT],
43 | board.pieces[WHITE][PAWN] | board.pieces[BLACK][PAWN],
44 | 0,
45 | 0,
46 | 0,
47 | board.stm == WHITE,
48 | );
49 |
50 | return match wdl {
51 | TB_WIN => Some(WdlResult::Win),
52 | TB_LOSS => Some(WdlResult::Loss),
53 | TB_DRAW | TB_CURSED_WIN | TB_BLESSED_LOSS => Some(WdlResult::Draw),
54 | _ => None,
55 | };
56 | }
57 |
58 | None
59 | }
60 |
61 | /// Gets WDL (Win-Draw-Loss), DTZ (Distance To Zeroing) and the best move for the position specified in `board`.
62 | /// Returns [None] if data couldn't be obtained or the feature is disabled.
63 | pub fn get_root_wdl_dtz(board: &Board) -> Option {
64 | #[cfg(feature = "syzygy")]
65 | unsafe {
66 | let result = tb_probe_root(
67 | board.occupancy[WHITE],
68 | board.occupancy[BLACK],
69 | board.pieces[WHITE][KING] | board.pieces[BLACK][KING],
70 | board.pieces[WHITE][QUEEN] | board.pieces[BLACK][QUEEN],
71 | board.pieces[WHITE][ROOK] | board.pieces[BLACK][ROOK],
72 | board.pieces[WHITE][BISHOP] | board.pieces[BLACK][BISHOP],
73 | board.pieces[WHITE][KNIGHT] | board.pieces[BLACK][KNIGHT],
74 | board.pieces[WHITE][PAWN] | board.pieces[BLACK][PAWN],
75 | board.state.halfmove_clock as u32,
76 | 0,
77 | 0,
78 | board.stm == WHITE,
79 | ptr::null_mut(),
80 | );
81 |
82 | let wdl = ((result & TB_RESULT_WDL_MASK) >> TB_RESULT_WDL_SHIFT);
83 | let wdl = match wdl {
84 | TB_WIN => WdlResult::Win,
85 | TB_LOSS => WdlResult::Loss,
86 | _ => WdlResult::Draw,
87 | };
88 | let dtz = ((result & TB_RESULT_DTZ_MASK) >> TB_RESULT_DTZ_SHIFT);
89 |
90 | if result == TB_RESULT_FAILED {
91 | return None;
92 | }
93 |
94 | let mut moves = [MaybeUninit::uninit(); engine::MAX_MOVES_COUNT];
95 | let moves_count = board.get_all_moves(&mut moves, u64::MAX);
96 |
97 | let from = ((result & TB_RESULT_FROM_MASK) >> TB_RESULT_FROM_SHIFT) as usize;
98 | let to = ((result & TB_RESULT_TO_MASK) >> TB_RESULT_TO_SHIFT) as usize;
99 | let promotion = ((result & TB_RESULT_PROMOTES_MASK) >> TB_RESULT_PROMOTES_SHIFT);
100 |
101 | let promotion_flags = match promotion {
102 | TB_PROMOTES_QUEEN => MoveFlags::QUEEN_PROMOTION,
103 | TB_PROMOTES_ROOK => MoveFlags::ROOK_PROMOTION,
104 | TB_PROMOTES_BISHOP => MoveFlags::BISHOP_PROMOTION,
105 | TB_PROMOTES_KNIGHT => MoveFlags::KNIGHT_PROMOTION,
106 | _ => MoveFlags::SINGLE_PUSH,
107 | };
108 |
109 | for r#move in &moves[0..moves_count] {
110 | let r#move = unsafe { r#move.assume_init() };
111 | if r#move.get_from() == from && r#move.get_to() == to {
112 | let flags = r#move.get_flags();
113 | if promotion == 0 || (flags & promotion_flags) == flags {
114 | return Some(WdlDtzResult::new(wdl, dtz, r#move));
115 | }
116 | }
117 | }
118 |
119 | return None;
120 | }
121 |
122 | None
123 | }
124 |
--------------------------------------------------------------------------------
/src/state/zobrist.rs:
--------------------------------------------------------------------------------
1 | use super::representation::Board;
2 | use super::representation::CastlingRights;
3 | use super::*;
4 | use crate::utils::assert_fast;
5 | use crate::utils::bitflags::BitFlags;
6 | use crate::utils::bithelpers::BitHelpers;
7 | use crate::utils::rand;
8 |
9 | pub const PIECE_HASHES: [[[u64; 64]; 6]; 2] = generate_piece_hashes();
10 | pub const CASTLING_HASHES: [u64; 4] = generate_castling_hashes();
11 | pub const EN_PASSANT_HASHES: [u64; 8] = generate_en_passant_hashes();
12 | pub const STM_HASH: u64 = generate_stm_hash();
13 |
14 | /// Generates a constant array of piece hashes.
15 | pub const fn generate_piece_hashes() -> [[[u64; 64]; 6]; 2] {
16 | let mut result = [[[0; 64]; 6]; 2];
17 | let mut seed = 584578;
18 |
19 | let mut color = 0;
20 | while color < 2 {
21 | let mut piece = 0;
22 | while piece < 6 {
23 | let mut square = 0;
24 | while square < 64 {
25 | let (value, new_seed) = rand::rand(seed);
26 | result[color][piece][square] = value;
27 | seed = new_seed;
28 | square += 1;
29 | }
30 |
31 | piece += 1;
32 | }
33 |
34 | color += 1;
35 | }
36 |
37 | result
38 | }
39 |
40 | /// Generates a constant array of castling hashes.
41 | pub const fn generate_castling_hashes() -> [u64; 4] {
42 | let mut result = [0; 4];
43 | let mut seed = 8652221015076841656;
44 |
45 | let mut castling_index = 0;
46 | while castling_index < 4 {
47 | let (value, new_seed) = rand::rand(seed);
48 | result[castling_index] = value;
49 | seed = new_seed;
50 | castling_index += 1;
51 | }
52 |
53 | result
54 | }
55 |
56 | /// Generates a constant array of en passant hashes.
57 | pub const fn generate_en_passant_hashes() -> [u64; 8] {
58 | let mut result = [0; 8];
59 | let mut seed = 13494315632332173397;
60 |
61 | let mut en_passant_index = 0;
62 | while en_passant_index < 8 {
63 | let (value, new_seed) = rand::rand(seed);
64 | result[en_passant_index] = value;
65 | seed = new_seed;
66 | en_passant_index += 1;
67 | }
68 |
69 | result
70 | }
71 |
72 | /// Generates a constant array of side to move hashes.
73 | pub const fn generate_stm_hash() -> u64 {
74 | let seed = 13914115299070061278;
75 | let (value, _) = rand::rand(seed);
76 | value
77 | }
78 |
79 | /// Gets `piece` hash with the `color` for the square specified by `square`.
80 | pub fn get_piece_hash(color: usize, piece: usize, square: usize) -> u64 {
81 | assert_fast!(color < 2);
82 | assert_fast!(piece < 6);
83 | assert_fast!(square < 64);
84 |
85 | PIECE_HASHES[color][piece][square]
86 | }
87 |
88 | /// Gets castling right hash based on the `current` ones and the desired change specified by `right`.
89 | pub fn get_castling_right_hash(current: u8, right: u8) -> u64 {
90 | if !current.contains(right) {
91 | return 0;
92 | }
93 |
94 | assert_fast!(right.bit_scan() < 4);
95 | CASTLING_HASHES[right.bit_scan()]
96 | }
97 |
98 | /// Gets en passant hash for the `file`.
99 | pub fn get_en_passant_hash(file: usize) -> u64 {
100 | assert_fast!(file < 8);
101 | EN_PASSANT_HASHES[file]
102 | }
103 |
104 | /// Gets active color hash.
105 | pub fn get_stm_hash() -> u64 {
106 | STM_HASH
107 | }
108 |
109 | /// Recalculates board's hash entirely.
110 | pub fn recalculate_hash(board: &mut Board) {
111 | let mut hash = 0u64;
112 |
113 | for color in ALL_COLORS {
114 | for piece_index in ALL_PIECES {
115 | let mut pieces_bb = board.pieces[color][piece_index];
116 | while pieces_bb != 0 {
117 | let square_bb = pieces_bb.get_lsb();
118 | let square = square_bb.bit_scan();
119 | pieces_bb = pieces_bb.pop_lsb();
120 |
121 | hash ^= zobrist::get_piece_hash(color, piece_index, square);
122 | }
123 | }
124 | }
125 |
126 | if board.state.castling_rights.contains(CastlingRights::WHITE_SHORT_CASTLING) {
127 | hash ^= zobrist::get_castling_right_hash(board.state.castling_rights, CastlingRights::WHITE_SHORT_CASTLING);
128 | }
129 | if board.state.castling_rights.contains(CastlingRights::WHITE_LONG_CASTLING) {
130 | hash ^= zobrist::get_castling_right_hash(board.state.castling_rights, CastlingRights::WHITE_LONG_CASTLING);
131 | }
132 | if board.state.castling_rights.contains(CastlingRights::BLACK_SHORT_CASTLING) {
133 | hash ^= zobrist::get_castling_right_hash(board.state.castling_rights, CastlingRights::BLACK_SHORT_CASTLING);
134 | }
135 | if board.state.castling_rights.contains(CastlingRights::BLACK_LONG_CASTLING) {
136 | hash ^= zobrist::get_castling_right_hash(board.state.castling_rights, CastlingRights::BLACK_LONG_CASTLING);
137 | }
138 |
139 | if board.state.en_passant != 0 {
140 | hash ^= zobrist::get_en_passant_hash(board.state.en_passant.bit_scan() & 7);
141 | }
142 |
143 | if board.stm == BLACK {
144 | hash ^= zobrist::get_stm_hash();
145 | }
146 |
147 | board.state.hash = hash;
148 | }
149 |
150 | /// Recalculates board's pawn hash entirely.
151 | pub fn recalculate_pawn_hash(board: &mut Board) {
152 | let mut hash = 0u64;
153 |
154 | for color in ALL_COLORS {
155 | for piece in [PAWN, KING] {
156 | let mut pieces_bb = board.pieces[color][piece];
157 | while pieces_bb != 0 {
158 | let square_bb = pieces_bb.get_lsb();
159 | let square = square_bb.bit_scan();
160 | pieces_bb = pieces_bb.pop_lsb();
161 |
162 | hash ^= zobrist::get_piece_hash(color, piece, square);
163 | }
164 | }
165 | }
166 |
167 | board.state.pawn_hash = hash;
168 | }
169 |
--------------------------------------------------------------------------------
/src/testing/testset.rs:
--------------------------------------------------------------------------------
1 | use crate::cache::pawns::PHTable;
2 | use crate::cache::search::TTable;
3 | use crate::engine::context::SearchContext;
4 | use crate::state::movescan::Move;
5 | use crate::state::representation::Board;
6 | use crate::state::text::fen;
7 | use std::fs::File;
8 | use std::io::BufRead;
9 | use std::io::BufReader;
10 | use std::sync::atomic::AtomicBool;
11 | use std::sync::atomic::AtomicU32;
12 | use std::sync::atomic::Ordering;
13 | use std::sync::Arc;
14 | use std::thread;
15 | use std::time::SystemTime;
16 |
17 | pub struct TestPosition {
18 | id: String,
19 | board: Board,
20 | best_move: Move,
21 | }
22 |
23 | impl TestPosition {
24 | /// Constructs a new instance of [TestPosition] with stored `id`, `board` and `best_move`.
25 | pub fn new(id: String, board: Board, best_move: Move) -> Self {
26 | Self { id, board, best_move }
27 | }
28 | }
29 |
30 | /// Runs a test by performing a fixed-`depth` search for the positions loaded from the `epd_filename` file, using hashtable with
31 | /// size specified in `ttable_size`. To classify the test as successful, the last iteration has to return the correct best move.
32 | /// Multithreading is supported by `threads_count`.
33 | pub fn run(epd_filename: &str, depth: i8, ttable_size: usize, threads_count: usize) {
34 | println!("Loading EPD file...");
35 | let mut positions = match load_positions(epd_filename) {
36 | Ok(value) => value,
37 | Err(error) => {
38 | println!("Invalid PGN: {}", error);
39 | return;
40 | }
41 | };
42 | println!("Loaded {} positions, starting test", positions.len());
43 |
44 | let index = Arc::new(AtomicU32::new(0));
45 | let passed_tests = Arc::new(AtomicU32::new(0));
46 | let failed_tests = Arc::new(AtomicU32::new(0));
47 | let recognition_depths_sum = Arc::new(AtomicU32::new(0));
48 | let start_time = SystemTime::now();
49 | let positions_count = positions.len();
50 |
51 | thread::scope(|scope| {
52 | for chunk in positions.chunks_mut(positions_count / threads_count) {
53 | let index_arc = index.clone();
54 | let passed_tests_arc = passed_tests.clone();
55 | let failed_tests_arc = failed_tests.clone();
56 | let recognition_depths_sum_arc = recognition_depths_sum.clone();
57 |
58 | scope.spawn(move || {
59 | for position in chunk {
60 | let ttable = Arc::new(TTable::new(ttable_size));
61 | let phtable = Arc::new(PHTable::new(1 * 1024 * 1024));
62 | let abort_flag = Arc::new(AtomicBool::new(false));
63 | let ponder_flag = Arc::new(AtomicBool::new(false));
64 |
65 | let board_clone = position.board.clone();
66 | let mut context = SearchContext::new(board_clone, ttable, phtable, abort_flag, ponder_flag);
67 | context.forced_depth = depth;
68 |
69 | let mut last_best_move = Move::default();
70 | let mut best_moves_count = 0;
71 | let mut recognition_depth = 0;
72 |
73 | while let Some(result) = context.next() {
74 | last_best_move = context.lines[0].pv_line[0];
75 | if last_best_move == position.best_move {
76 | if best_moves_count == 0 {
77 | recognition_depth = result.depth;
78 | }
79 |
80 | best_moves_count += 1;
81 | } else {
82 | best_moves_count = 0;
83 | }
84 | }
85 |
86 | let index_to_display = index_arc.fetch_add(1, Ordering::Relaxed);
87 | if last_best_move == position.best_move {
88 | println!("{}/{}. Test {} PASSED (depth: {})", index_to_display + 1, positions_count, position.id, recognition_depth);
89 | recognition_depths_sum_arc.fetch_add(recognition_depth as u32, Ordering::Relaxed);
90 | passed_tests_arc.fetch_add(1, Ordering::Relaxed);
91 | } else {
92 | println!(
93 | "{}/{}. Test {} FAILED (expected {}, got {})",
94 | index_to_display + 1,
95 | positions_count,
96 | position.id,
97 | position.best_move,
98 | last_best_move
99 | );
100 | failed_tests_arc.fetch_add(1, Ordering::Relaxed);
101 | }
102 | }
103 | });
104 | }
105 | });
106 |
107 | println!("-----------------------------------------------------------------------------");
108 | println!(
109 | "Tests done in {:.2} s: {} passed ({:.2}% with average depth {:.2}), {} failed",
110 | (start_time.elapsed().unwrap().as_millis() as f32) / 1000.0,
111 | passed_tests.load(Ordering::Relaxed),
112 | (passed_tests.load(Ordering::Relaxed) as f32) / (positions_count as f32) * 100.0,
113 | (recognition_depths_sum.load(Ordering::Relaxed) as f32) / (passed_tests.load(Ordering::Relaxed) as f32),
114 | failed_tests.load(Ordering::Relaxed)
115 | );
116 | }
117 |
118 | /// Loads positions from the `epd_filename` and parses them into a list of [TestPosition].
119 | /// Returns [Err] with a proper error message if the file couldn't be parsed.
120 | fn load_positions(epd_filename: &str) -> Result, String> {
121 | let mut positions = Vec::new();
122 | let file = match File::open(epd_filename) {
123 | Ok(value) => value,
124 | Err(error) => return Err(format!("Invalid EPD file: {}", error)),
125 | };
126 |
127 | for line in BufReader::new(file).lines() {
128 | let position = line.unwrap();
129 | if position.is_empty() {
130 | continue;
131 | }
132 |
133 | let mut parsed_epd = fen::epd_to_board(position.as_str())?;
134 | let parsed_best_move = Move::from_short_notation(&parsed_epd.best_move.unwrap(), &mut parsed_epd.board)?;
135 | positions.push(TestPosition::new(parsed_epd.id.unwrap(), parsed_epd.board, parsed_best_move));
136 | }
137 |
138 | Ok(positions)
139 | }
140 |
--------------------------------------------------------------------------------
/src/engine/params.rs:
--------------------------------------------------------------------------------
1 | #[derive(Clone)]
2 | pub struct SearchParams {
3 | pub time_a: i8,
4 | pub time_b: i8,
5 | pub time_c: i8,
6 | pub time_d: i8,
7 | pub time_e: i8,
8 | pub time_soft_bound: i16,
9 | pub time_hard_bound: i16,
10 |
11 | pub aspwin_delta: i16,
12 | pub aspwin_multiplier: i16,
13 | pub aspwin_min_depth: i8,
14 | pub aspwin_max_delta: i16,
15 |
16 | pub iir_min_depth: i8,
17 | pub iir_reduction_base: i8,
18 | pub iir_reduction_step: i8,
19 | pub iir_max_reduction: i8,
20 |
21 | pub razoring_min_depth: i8,
22 | pub razoring_depth_margin_base: i16,
23 | pub razoring_depth_margin_multiplier: i16,
24 |
25 | pub snmp_min_depth: i8,
26 | pub snmp_depth_margin_base: i16,
27 | pub snmp_depth_margin_multiplier: i16,
28 |
29 | pub nmp_min_depth: i8,
30 | pub nmp_margin: i16,
31 | pub nmp_depth_base: i8,
32 | pub nmp_depth_divider: i8,
33 |
34 | pub lmp_min_depth: i8,
35 | pub lmp_max_depth: i8,
36 | pub lmp_move_index_margin_base: usize,
37 | pub lmp_move_index_margin_multiplier: usize,
38 | pub lmp_max_score: i16,
39 |
40 | pub lmr_min_depth: i8,
41 | pub lmr_max_score: i16,
42 | pub lmr_min_move_index: usize,
43 | pub lmr_reduction_base: usize,
44 | pub lmr_reduction_step: usize,
45 | pub lmr_max_reduction: i8,
46 | pub lmr_pv_min_move_index: usize,
47 | pub lmr_pv_reduction_base: usize,
48 | pub lmr_pv_reduction_step: usize,
49 | pub lmr_pv_max_reduction: i8,
50 |
51 | pub q_score_pruning_treshold: i16,
52 | pub q_futility_pruning_margin: i16,
53 | }
54 |
55 | #[allow(non_upper_case_globals)]
56 | impl SearchParams {
57 | pub const time_a: i8 = 43;
58 | pub const time_b: i8 = -28;
59 | pub const time_c: i8 = 21;
60 | pub const time_d: i8 = -8;
61 | pub const time_e: i8 = 15;
62 | pub const time_soft_bound: i16 = 70;
63 | pub const time_hard_bound: i16 = 400;
64 |
65 | pub const aspwin_delta: i16 = 15;
66 | pub const aspwin_multiplier: i16 = 200;
67 | pub const aspwin_min_depth: i8 = 5;
68 | pub const aspwin_max_delta: i16 = 200;
69 |
70 | pub const iir_min_depth: i8 = 4;
71 | pub const iir_reduction_base: i8 = 1;
72 | pub const iir_reduction_step: i8 = 99;
73 | pub const iir_max_reduction: i8 = 3;
74 |
75 | pub const razoring_min_depth: i8 = 1;
76 | pub const razoring_depth_margin_base: i16 = 300;
77 | pub const razoring_depth_margin_multiplier: i16 = 240;
78 |
79 | pub const snmp_min_depth: i8 = 1;
80 | pub const snmp_depth_margin_base: i16 = 85;
81 | pub const snmp_depth_margin_multiplier: i16 = 65;
82 |
83 | pub const nmp_min_depth: i8 = 2;
84 | pub const nmp_margin: i16 = 55;
85 | pub const nmp_depth_base: i8 = 2;
86 | pub const nmp_depth_divider: i8 = 5;
87 |
88 | pub const lmp_min_depth: i8 = 1;
89 | pub const lmp_max_depth: i8 = 3;
90 | pub const lmp_move_index_margin_base: usize = 2;
91 | pub const lmp_move_index_margin_multiplier: usize = 5;
92 | pub const lmp_max_score: i16 = -65;
93 |
94 | pub const lmr_min_depth: i8 = 2;
95 | pub const lmr_max_score: i16 = 90;
96 | pub const lmr_min_move_index: usize = 2;
97 | pub const lmr_reduction_base: usize = 1;
98 | pub const lmr_reduction_step: usize = 4;
99 | pub const lmr_max_reduction: i8 = 3;
100 | pub const lmr_pv_min_move_index: usize = 2;
101 | pub const lmr_pv_reduction_base: usize = 1;
102 | pub const lmr_pv_reduction_step: usize = 8;
103 | pub const lmr_pv_max_reduction: i8 = 2;
104 |
105 | pub const q_score_pruning_treshold: i16 = 0;
106 | pub const q_futility_pruning_margin: i16 = 75;
107 | }
108 |
109 | impl Default for SearchParams {
110 | /// Constructs a default instance of [SearchParams] with default elements.
111 | fn default() -> Self {
112 | Self {
113 | time_a: Self::time_a,
114 | time_b: Self::time_b,
115 | time_c: Self::time_c,
116 | time_d: Self::time_d,
117 | time_e: Self::time_e,
118 | time_soft_bound: Self::time_soft_bound,
119 | time_hard_bound: Self::time_hard_bound,
120 |
121 | aspwin_delta: Self::aspwin_delta,
122 | aspwin_multiplier: Self::aspwin_multiplier,
123 | aspwin_min_depth: Self::aspwin_min_depth,
124 | aspwin_max_delta: Self::aspwin_max_delta,
125 |
126 | iir_min_depth: Self::iir_min_depth,
127 | iir_reduction_base: Self::iir_reduction_base,
128 | iir_reduction_step: Self::iir_reduction_step,
129 | iir_max_reduction: Self::iir_max_reduction,
130 |
131 | razoring_min_depth: Self::razoring_min_depth,
132 | razoring_depth_margin_base: Self::razoring_depth_margin_base,
133 | razoring_depth_margin_multiplier: Self::razoring_depth_margin_multiplier,
134 |
135 | snmp_min_depth: Self::snmp_min_depth,
136 | snmp_depth_margin_base: Self::snmp_depth_margin_base,
137 | snmp_depth_margin_multiplier: Self::snmp_depth_margin_multiplier,
138 |
139 | nmp_min_depth: Self::nmp_min_depth,
140 | nmp_margin: Self::nmp_margin,
141 | nmp_depth_base: Self::nmp_depth_base,
142 | nmp_depth_divider: Self::nmp_depth_divider,
143 |
144 | lmp_min_depth: Self::lmp_min_depth,
145 | lmp_max_depth: Self::lmp_max_depth,
146 | lmp_move_index_margin_base: Self::lmp_move_index_margin_base,
147 | lmp_move_index_margin_multiplier: Self::lmp_move_index_margin_multiplier,
148 | lmp_max_score: Self::lmp_max_score,
149 |
150 | lmr_min_depth: Self::lmr_min_depth,
151 | lmr_max_score: Self::lmr_max_score,
152 | lmr_min_move_index: Self::lmr_min_move_index,
153 | lmr_reduction_base: Self::lmr_reduction_base,
154 | lmr_reduction_step: Self::lmr_reduction_step,
155 | lmr_max_reduction: Self::lmr_max_reduction,
156 | lmr_pv_min_move_index: Self::lmr_pv_min_move_index,
157 | lmr_pv_reduction_base: Self::lmr_pv_reduction_base,
158 | lmr_pv_reduction_step: Self::lmr_pv_reduction_step,
159 | lmr_pv_max_reduction: Self::lmr_pv_max_reduction,
160 |
161 | q_score_pruning_treshold: Self::q_score_pruning_treshold,
162 | q_futility_pruning_margin: Self::q_futility_pruning_margin,
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/deps/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 | Programming API
16 | ---------------
17 |
18 | Fathom provides a simple API. Following are the main function calls:
19 |
20 | * `tb_init` initializes the tablebases.
21 | * `tb_free` releases any resources allocated by Fathom.
22 | * `tb_probe_wdl` probes the Win-Draw-Loss (WDL) table for a given position.
23 | * `tb_probe_root` probes the Distance-To-Zero (DTZ) table for the given
24 | position. It returns a recommended move, and also a list of unsigned
25 | integers, each one encoding a possible move and its DTZ and WDL values.
26 | * `tb_probe_root_dtz` probes the Distance-To-Zero (DTZ) at the root position.
27 | It returns a score and a rank for each possible move.
28 | * `tb_probe_root_wdl` probes the Win-Draw-Loss (WDL) at the root position.
29 | it returns a score and a rank for each possible move.
30 |
31 | Fathom does not require the callee to provide any additional functionality
32 | (e.g. move generation). A simple set of chess-related functions including move
33 | generation is provided in file `tbchess.c`. However, chess engines can opt to
34 | replace some of this functionality for better performance (see below).
35 |
36 | Chess engines
37 | -------------
38 |
39 | Chess engines can use `tb_probe_wdl` to get the WDL value during
40 | search. This function is thread safe (unless TB_NO_THREADS is
41 | set). The various "probe_root" functions are intended for probing only
42 | at the root node and are not thread-safe.
43 |
44 | Chess engines and other clients can modify some features of Fathom and
45 | override some of its internal functions by configuring
46 | `tbconfig.h`. `tbconfig.h` is included in Fathom's code with angle
47 | brackets. This allows a client of Fathom to override tbconfig.h by
48 | placing its own modified copy in its include path before the Fathom
49 | source directory.
50 |
51 | One option provided by `tbconfig.h` is to define macros that replace
52 | some aspects of Fathom's functionality, such as calculating piece
53 | attacks, avoiding duplication of functionality. If doing this,
54 | however, be careful with including typedefs or defines from your own
55 | code into `tbconfig.h`, since these may clash with internal definitions
56 | used by Fathom. I recommend instead interfacing to external
57 | functions via a small module, with an interface something like this:
58 |
59 | ```
60 | #ifndef _TB_ATTACK_INTERFACE
61 | #define _TB_ATTACK_INTERFACE
62 |
63 | #ifdef __cplusplus
64 | #include
65 | #else
66 | #include
67 | #endif
68 |
69 | extern tb_knight_attacks(unsigned square);
70 | extern tb_king_attacks(unsigned square);
71 | extern tb_root_attacks(unsigned square, uint64_t occ);
72 | extern tb_bishop_attacks(unsigned square, uint64_t occ);
73 | extern tb_queen_attacks(unsigned square, uint64_t occ);
74 | extern tb_pawn_attacks(unsigned square, uint64_t occ);
75 |
76 | #endif
77 | ```
78 |
79 | You can add if wanted other function definitions such as a popcnt
80 | function based on the chess engine's native popcnt support.
81 |
82 | `tbconfig.h` can then reference these functions safety because the
83 | interface depends only on types defined in standard headers. The
84 | implementation, however, can use any types from the chess engine or
85 | other client that are necessary. (A good optimizer with link-time
86 | optimization will inline the implementation code even though it is not
87 | visible in the interface).
88 |
89 | History and Credits
90 | -------------------
91 |
92 | The Syzygy tablebases were created by Ronald de Man. The original version of Fathom
93 | (https://github.com/basil00/Fathom) combined probing code from Ronald de Man, originally written for
94 | Stockfish, with chess-related functions and other support code from Basil Falcinelli.
95 | That codebase is no longer being maintained. This repository was originaly a fork of
96 | that codebase, with additional modifications by Jon Dart.
97 |
98 | However, the current Fathom code in this repository is no longer
99 | derived directly from the probing code written for Stockfish, but
100 | instead derives from tbprobe.c, which is a component of the Cfish
101 | chess engine (https://github.com/syzygy1/Cfish), a Stockfish
102 | derivative. tbprobe.c includes 7-man tablebase support. It was written
103 | by Ronald de Man and released for unrestricted distribution and use.
104 |
105 | This fork of Fathom replaces the Cfish board representation and move
106 | generation code used in tbprobe.c with simpler, MIT-licensed code from the original
107 | Fathom source by Basil. The code has been reorganized so that
108 | `tbchess.c` contains all move generation and most chess-related typedefs
109 | and functions, while `tbprobe.c` contains all the tablebase probing
110 | code. The code replacement and reorganization was done by Jon Dart.
111 |
112 | License
113 | -------
114 |
115 | (C) 2013-2015 Ronald de Man (original code)
116 | (C) 2015 basil (new modifications)
117 | (C) 2016-2020 Jon Dart (additional modifications)
118 |
119 | This version of Fathom is released under the MIT License:
120 |
121 | Permission is hereby granted, free of charge, to any person obtaining a copy of
122 | this software and associated documentation files (the "Software"), to deal in
123 | the Software without restriction, including without limitation the rights to
124 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
125 | of the Software, and to permit persons to whom the Software is furnished to do
126 | so, subject to the following conditions:
127 |
128 | The above copyright notice and this permission notice shall be included in all
129 | copies or substantial portions of the Software.
130 |
131 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
132 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
133 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
134 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
135 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
136 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
137 | SOFTWARE.
138 |
--------------------------------------------------------------------------------
/src/state/mod.rs:
--------------------------------------------------------------------------------
1 | use std::ops::RangeInclusive;
2 |
3 | pub mod movegen;
4 | pub mod movescan;
5 | pub mod patterns;
6 | pub mod representation;
7 | pub mod text;
8 | pub mod zobrist;
9 |
10 | pub const WHITE: usize = 0;
11 | pub const BLACK: usize = 1;
12 |
13 | pub const US: usize = 0;
14 | pub const THEM: usize = 1;
15 |
16 | pub const OPENING: usize = 0;
17 | pub const ENDING: usize = 1;
18 |
19 | pub const PAWN: usize = 0;
20 | pub const KNIGHT: usize = 1;
21 | pub const BISHOP: usize = 2;
22 | pub const ROOK: usize = 3;
23 | pub const QUEEN: usize = 4;
24 | pub const KING: usize = 5;
25 |
26 | pub const LEFT: usize = 0;
27 | pub const RIGHT: usize = 1;
28 |
29 | pub const CENTER_BB: u64 = 0x3c3c3c3c0000;
30 | pub const OUTSIDE_BB: u64 = 0xffffc3c3c3c3ffff;
31 | pub const EDGE_BB: u64 = 0xff818181818181ff;
32 |
33 | pub const WHITE_SQUARES_BB: u64 = 0xaa55aa55aa55aa55;
34 | pub const BLACK_SQUARES_BB: u64 = 0x55aa55aa55aa55aa;
35 |
36 | pub const FILE_A_BB: u64 = 0x0101010101010101 << 7;
37 | pub const FILE_B_BB: u64 = 0x0101010101010101 << 6;
38 | pub const FILE_C_BB: u64 = 0x0101010101010101 << 5;
39 | pub const FILE_D_BB: u64 = 0x0101010101010101 << 4;
40 | pub const FILE_E_BB: u64 = 0x0101010101010101 << 3;
41 | pub const FILE_F_BB: u64 = 0x0101010101010101 << 2;
42 | pub const FILE_G_BB: u64 = 0x0101010101010101 << 1;
43 | pub const FILE_H_BB: u64 = 0x0101010101010101 << 0;
44 |
45 | pub const RANK_1_BB: u64 = 0x00000000000000ff << 0;
46 | pub const RANK_2_BB: u64 = 0x00000000000000ff << 8;
47 | pub const RANK_3_BB: u64 = 0x00000000000000ff << 16;
48 | pub const RANK_4_BB: u64 = 0x00000000000000ff << 24;
49 | pub const RANK_5_BB: u64 = 0x00000000000000ff << 32;
50 | pub const RANK_6_BB: u64 = 0x00000000000000ff << 40;
51 | pub const RANK_7_BB: u64 = 0x00000000000000ff << 48;
52 | pub const RANK_8_BB: u64 = 0x00000000000000ff << 56;
53 |
54 | pub const A1: usize = 7;
55 | pub const B1: usize = 6;
56 | pub const C1: usize = 5;
57 | pub const D1: usize = 4;
58 | pub const E1: usize = 3;
59 | pub const F1: usize = 2;
60 | pub const G1: usize = 1;
61 | pub const H1: usize = 0;
62 | pub const A1_BB: u64 = 1 << A1;
63 | pub const B1_BB: u64 = 1 << B1;
64 | pub const C1_BB: u64 = 1 << C1;
65 | pub const D1_BB: u64 = 1 << D1;
66 | pub const E1_BB: u64 = 1 << E1;
67 | pub const F1_BB: u64 = 1 << F1;
68 | pub const G1_BB: u64 = 1 << G1;
69 | pub const H1_BB: u64 = 1 << H1;
70 |
71 | pub const A2: usize = 15;
72 | pub const B2: usize = 14;
73 | pub const C2: usize = 13;
74 | pub const D2: usize = 12;
75 | pub const E2: usize = 11;
76 | pub const F2: usize = 10;
77 | pub const G2: usize = 9;
78 | pub const H2: usize = 8;
79 | pub const A2_BB: u64 = 1 << A2;
80 | pub const B2_BB: u64 = 1 << B2;
81 | pub const C2_BB: u64 = 1 << C2;
82 | pub const D2_BB: u64 = 1 << D2;
83 | pub const E2_BB: u64 = 1 << E2;
84 | pub const F2_BB: u64 = 1 << F2;
85 | pub const G2_BB: u64 = 1 << G2;
86 | pub const H2_BB: u64 = 1 << H2;
87 |
88 | pub const A3: usize = 23;
89 | pub const B3: usize = 22;
90 | pub const C3: usize = 21;
91 | pub const D3: usize = 20;
92 | pub const E3: usize = 19;
93 | pub const F3: usize = 18;
94 | pub const G3: usize = 17;
95 | pub const H3: usize = 16;
96 | pub const A3_BB: u64 = 1 << A3;
97 | pub const B3_BB: u64 = 1 << B3;
98 | pub const C3_BB: u64 = 1 << C3;
99 | pub const D3_BB: u64 = 1 << D3;
100 | pub const E3_BB: u64 = 1 << E3;
101 | pub const F3_BB: u64 = 1 << F3;
102 | pub const G3_BB: u64 = 1 << G3;
103 | pub const H3_BB: u64 = 1 << H3;
104 |
105 | pub const A4: usize = 31;
106 | pub const B4: usize = 30;
107 | pub const C4: usize = 29;
108 | pub const D4: usize = 28;
109 | pub const E4: usize = 27;
110 | pub const F4: usize = 26;
111 | pub const G4: usize = 25;
112 | pub const H4: usize = 24;
113 | pub const A4_BB: u64 = 1 << A4;
114 | pub const B4_BB: u64 = 1 << B4;
115 | pub const C4_BB: u64 = 1 << C4;
116 | pub const D4_BB: u64 = 1 << D4;
117 | pub const E4_BB: u64 = 1 << E4;
118 | pub const F4_BB: u64 = 1 << F4;
119 | pub const G4_BB: u64 = 1 << G4;
120 | pub const H4_BB: u64 = 1 << H4;
121 |
122 | pub const A5: usize = 39;
123 | pub const B5: usize = 38;
124 | pub const C5: usize = 37;
125 | pub const D5: usize = 36;
126 | pub const E5: usize = 35;
127 | pub const F5: usize = 34;
128 | pub const G5: usize = 33;
129 | pub const H5: usize = 32;
130 | pub const A5_BB: u64 = 1 << A5;
131 | pub const B5_BB: u64 = 1 << B5;
132 | pub const C5_BB: u64 = 1 << C5;
133 | pub const D5_BB: u64 = 1 << D5;
134 | pub const E5_BB: u64 = 1 << E5;
135 | pub const F5_BB: u64 = 1 << F5;
136 | pub const G5_BB: u64 = 1 << G5;
137 | pub const H5_BB: u64 = 1 << H5;
138 |
139 | pub const A6: usize = 47;
140 | pub const B6: usize = 46;
141 | pub const C6: usize = 45;
142 | pub const D6: usize = 44;
143 | pub const E6: usize = 43;
144 | pub const F6: usize = 42;
145 | pub const G6: usize = 41;
146 | pub const H6: usize = 40;
147 | pub const A6_BB: u64 = 1 << A6;
148 | pub const B6_BB: u64 = 1 << B6;
149 | pub const C6_BB: u64 = 1 << C6;
150 | pub const D6_BB: u64 = 1 << D6;
151 | pub const E6_BB: u64 = 1 << E6;
152 | pub const F6_BB: u64 = 1 << F6;
153 | pub const G6_BB: u64 = 1 << G6;
154 | pub const H6_BB: u64 = 1 << H6;
155 |
156 | pub const A7: usize = 55;
157 | pub const B7: usize = 54;
158 | pub const C7: usize = 53;
159 | pub const D7: usize = 52;
160 | pub const E7: usize = 51;
161 | pub const F7: usize = 50;
162 | pub const G7: usize = 49;
163 | pub const H7: usize = 48;
164 | pub const A7_BB: u64 = 1 << A7;
165 | pub const B7_BB: u64 = 1 << B7;
166 | pub const C7_BB: u64 = 1 << C7;
167 | pub const D7_BB: u64 = 1 << D7;
168 | pub const E7_BB: u64 = 1 << E7;
169 | pub const F7_BB: u64 = 1 << F7;
170 | pub const G7_BB: u64 = 1 << G7;
171 | pub const H7_BB: u64 = 1 << H7;
172 |
173 | pub const A8: usize = 63;
174 | pub const B8: usize = 62;
175 | pub const C8: usize = 61;
176 | pub const D8: usize = 60;
177 | pub const E8: usize = 59;
178 | pub const F8: usize = 58;
179 | pub const G8: usize = 57;
180 | pub const H8: usize = 56;
181 | pub const A8_BB: u64 = 1 << A8;
182 | pub const B8_BB: u64 = 1 << B8;
183 | pub const C8_BB: u64 = 1 << C8;
184 | pub const D8_BB: u64 = 1 << D8;
185 | pub const E8_BB: u64 = 1 << E8;
186 | pub const F8_BB: u64 = 1 << F8;
187 | pub const G8_BB: u64 = 1 << G8;
188 | pub const H8_BB: u64 = 1 << H8;
189 |
190 | pub const ALL_COLORS: RangeInclusive = 0..=1;
191 | pub const ALL_POVS: RangeInclusive = 0..=1;
192 | pub const ALL_PHASES: RangeInclusive = 0..=1;
193 | pub const ALL_PIECES: RangeInclusive = 0..=5;
194 | pub const ALL_FILES: RangeInclusive = 0..=7;
195 | pub const ALL_RANKS: RangeInclusive = 0..=7;
196 | pub const ALL_SQUARES: RangeInclusive = 0..=63;
197 |
--------------------------------------------------------------------------------
/src/tuning/dataset.rs:
--------------------------------------------------------------------------------
1 | use crate::cache::pawns::PHTable;
2 | use crate::cache::search::TTable;
3 | use crate::engine::context::SearchContext;
4 | use crate::engine::qsearch;
5 | use crate::engine::*;
6 | use crate::evaluation::material;
7 | use crate::evaluation::*;
8 | use crate::state::representation::Board;
9 | use crate::state::text::pgn::PGNLoader;
10 | use crate::utils::rand;
11 | use std::collections::HashSet;
12 | use std::fs::File;
13 | use std::io::BufRead;
14 | use std::io::BufReader;
15 | use std::io::LineWriter;
16 | use std::io::Write;
17 | use std::sync::atomic::AtomicBool;
18 | use std::sync::Arc;
19 | use std::time::SystemTime;
20 |
21 | /// Runs generator of the dataset for the tuner. It works by parsing `pgn_filename`, and then picking random positions based on the
22 | /// provided restrictions like `min_ply`, `max_score`, `max_diff` and `density`. Output positions are then stored in the `output_file`.
23 | pub fn run(pgn_filename: &str, output_file: &str, min_ply: usize, max_score: i16, max_diff: u16, density: usize) {
24 | println!("Loading PGN file...");
25 |
26 | let start_time = SystemTime::now();
27 | let file = match File::open(pgn_filename) {
28 | Ok(value) => value,
29 | Err(error) => {
30 | println!("Invalid PGN file: {}", error);
31 | return;
32 | }
33 | };
34 |
35 | let pgn_loader = PGNLoader::new(BufReader::new(file).lines());
36 | let mut output_positions = HashSet::new();
37 | let mut parsed_pgns = 0;
38 |
39 | let ttable = Arc::new(TTable::new(1 * 1024 * 1024));
40 | let phtable = Arc::new(PHTable::new(1 * 1024 * 1024));
41 | let abort_flag = Arc::new(AtomicBool::new(false));
42 | let ponder_flag = Arc::new(AtomicBool::new(false));
43 |
44 | let mut total_viable_positions = 0;
45 | let mut ignored_positions = 0;
46 | let mut duplicates = 0;
47 | let mut sum_of_game_phases = 0.0;
48 |
49 | for pgn in pgn_loader {
50 | let pgn = match pgn {
51 | Ok(value) => value,
52 | Err(error) => {
53 | println!("Invalid PGN file: {}", error);
54 | return;
55 | }
56 | };
57 |
58 | if pgn.result == "*" {
59 | continue;
60 | }
61 |
62 | let board = match pgn.fen {
63 | Some(fen) => {
64 | let fen_result = Board::new_from_fen(&fen);
65 | match fen_result {
66 | Ok(board) => board,
67 | Err(error) => {
68 | println!("Invalid PGN file: {}", error);
69 | return;
70 | }
71 | }
72 | }
73 | None => Board::new_initial_position(),
74 | };
75 |
76 | let mut context = SearchContext::new(board, ttable.clone(), phtable.clone(), abort_flag.clone(), ponder_flag.clone());
77 | let mut viable_positions = Vec::new();
78 |
79 | for (index, data) in pgn.data.iter().enumerate() {
80 | context.board.make_move(data.r#move);
81 |
82 | if index < min_ply {
83 | ignored_positions += 1;
84 | continue;
85 | }
86 |
87 | if data.r#move.is_capture() || data.r#move.is_castling() || data.r#move.is_promotion() {
88 | ignored_positions += 1;
89 | continue;
90 | }
91 |
92 | if context.board.is_king_checked(context.board.stm) {
93 | ignored_positions += 1;
94 | continue;
95 | }
96 |
97 | let material_evaluation = material::evaluate(&context.board);
98 | if material_evaluation.taper_score(context.board.game_phase).abs() > max_score {
99 | ignored_positions += 1;
100 | continue;
101 | }
102 |
103 | let score = context.board.evaluate_without_cache(context.board.stm);
104 | let q_score = qsearch::run(&mut context, 0, MIN_ALPHA, MIN_BETA);
105 |
106 | if score.abs_diff(q_score) > max_diff {
107 | ignored_positions += 1;
108 | continue;
109 | }
110 |
111 | let epd = format!("{} c9 \"{:.2}|{}\";", context.board.to_epd(), data.eval, pgn.result);
112 | let game_phase = (context.board.game_phase as f32) / (INITIAL_GAME_PHASE as f32);
113 |
114 | viable_positions.push((epd, game_phase));
115 | total_viable_positions += 1;
116 | }
117 |
118 | let mut picked_positions = 0;
119 |
120 | while picked_positions < density {
121 | if viable_positions.is_empty() {
122 | break;
123 | }
124 |
125 | let index = rand::usize(0..viable_positions.len());
126 | let (position, game_phase) = viable_positions[index].to_owned();
127 |
128 | if output_positions.contains(&position) {
129 | viable_positions.remove(index);
130 | duplicates += 1;
131 |
132 | continue;
133 | }
134 |
135 | output_positions.insert(position);
136 | viable_positions.remove(index);
137 | picked_positions += 1;
138 | sum_of_game_phases += game_phase;
139 | }
140 |
141 | parsed_pgns += 1;
142 |
143 | if parsed_pgns % 1000 == 0 {
144 | println!(
145 | "Parsed PGNs: {} ({} viable positions, {} ignored positions, {} output positions, {} duplicates)",
146 | parsed_pgns,
147 | total_viable_positions,
148 | ignored_positions,
149 | output_positions.len(),
150 | duplicates
151 | );
152 | }
153 | }
154 |
155 | println!("-----------------------------------------------------------------------------");
156 | println!("Saving output...");
157 |
158 | let output_file = match File::create(output_file) {
159 | Ok(value) => value,
160 | Err(error) => {
161 | println!("Error while saving output: {}", error);
162 | return;
163 | }
164 | };
165 | let mut output_file_line_writer = LineWriter::new(output_file);
166 | let positions_count = output_positions.len();
167 |
168 | for fen in output_positions {
169 | output_file_line_writer.write_all((fen + "\n").as_bytes()).unwrap();
170 | }
171 |
172 | println!(
173 | "Tuner dataset generation done in {:.2} s, average game phase: {:.2}",
174 | (start_time.elapsed().unwrap().as_millis() as f32) / 1000.0,
175 | sum_of_game_phases / (positions_count as f32)
176 | );
177 | }
178 |
--------------------------------------------------------------------------------
/deps/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 | #include
29 |
30 | const int index64[64] =
31 | {
32 | 0, 1, 48, 2, 57, 49, 28, 3,
33 | 61, 58, 50, 42, 38, 29, 17, 4,
34 | 62, 55, 59, 36, 53, 51, 43, 22,
35 | 45, 39, 33, 30, 24, 18, 12, 5,
36 | 63, 47, 56, 27, 60, 41, 37, 16,
37 | 54, 35, 52, 21, 44, 32, 23, 11,
38 | 46, 26, 40, 15, 34, 20, 31, 10,
39 | 25, 14, 19, 9, 13, 8, 7, 6
40 | };
41 |
42 | /****************************************************************************/
43 | /* BUILD CONFIG: */
44 | /****************************************************************************/
45 |
46 | /*
47 | * Define TB_CUSTOM_POP_COUNT to override the internal popcount
48 | * implementation. To do this supply a macro or function definition
49 | * here:
50 | */
51 | static inline unsigned tb_software_popcount(uint64_t x)
52 | {
53 | x = x - ((x >> 1) & 0x5555555555555555ull);
54 | x = (x & 0x3333333333333333ull) + ((x >> 2) & 0x3333333333333333ull);
55 | x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0full;
56 | return (x * 0x0101010101010101ull) >> 56;
57 | }
58 |
59 | #define TB_CUSTOM_POP_COUNT(x) tb_software_popcount(x)
60 |
61 | /*
62 | * Define TB_CUSTOM_LSB to override the internal lsb
63 | * implementation. To do this supply a macro or function definition
64 | * here:
65 | */
66 | static inline unsigned lsb(uint64_t x)
67 | {
68 | const uint64_t debruijn64 = 0x03f79d71b4cb0a89;
69 | return index64[((x & -x) * debruijn64) >> 58];
70 | }
71 |
72 | #define TB_CUSTOM_LSB(x) lsb(x)
73 |
74 | /*
75 | * Define TB_NO_STDINT if you do not want to use or it is not
76 | * available.
77 | */
78 | /* #define TB_NO_STDINT */
79 |
80 | /*
81 | * Define TB_NO_STDBOOL if you do not want to use or it is not
82 | * available or unnecessary (e.g. C++).
83 | */
84 | /* #define TB_NO_STDBOOL */
85 |
86 | /*
87 | * Define TB_NO_THREADS if your program is not multi-threaded.
88 | */
89 | /* #define TB_NO_THREADS */
90 |
91 | /*
92 | * Define TB_NO_HELPER_API if you do not need the helper API.
93 | */
94 | #define TB_NO_HELPER_API
95 |
96 | /*
97 | * Define TB_NO_HW_POP_COUNT if there is no hardware popcount instruction.
98 | *
99 | * Note: if defined, TB_CUSTOM_POP_COUNT is always used in preference
100 | * to any built-in popcount functions.
101 | *
102 | * If no custom popcount function is defined, and if the following
103 | * define is not set, the code will attempt to use an available hardware
104 | * popcnt (currently supported on x86_64 architecture only) and otherwise
105 | * will fall back to a software implementation.
106 | */
107 | /* #define TB_NO_HW_POP_COUNT */
108 |
109 | /***************************************************************************/
110 | /* SCORING CONSTANTS */
111 | /***************************************************************************/
112 | /*
113 | * Fathom can produce scores for tablebase moves. These depend on the
114 | * value of a pawn, and the magnitude of mate scores. The following
115 | * constants are representative values but will likely need
116 | * modification to adapt to an engine's own internal score values.
117 | */
118 | #define TB_VALUE_PAWN 100 /* value of pawn in endgame */
119 | #define TB_VALUE_MATE 32000
120 | #define TB_VALUE_INFINITE 32767 /* value above all normal score values */
121 | #define TB_VALUE_DRAW 0
122 | #define TB_MAX_MATE_PLY 255
123 |
124 | /***************************************************************************/
125 | /* ENGINE INTEGRATION CONFIG */
126 | /***************************************************************************/
127 |
128 | /*
129 | * If you are integrating tbprobe into an engine, you can replace some of
130 | * tbprobe's built-in functionality with that already provided by the engine.
131 | * This is OPTIONAL. If no definition are provided then tbprobe will use its
132 | * own internal defaults. That said, for engines it is generally a good idea
133 | * to avoid redundancy.
134 | */
135 |
136 | /*
137 | * Define TB_KING_ATTACKS(square) to return the king attacks bitboard for a
138 | * king at `square'.
139 | */
140 | /* #define TB_KING_ATTACKS(square) */
141 |
142 | /*
143 | * Define TB_KNIGHT_ATTACKS(square) to return the knight attacks bitboard for
144 | * a knight at `square'.
145 | */
146 | /* #define TB_KNIGHT_ATTACKS(square) */
147 |
148 | /*
149 | * Define TB_ROOK_ATTACKS(square, occ) to return the rook attacks bitboard
150 | * for a rook at `square' assuming the given `occ' occupancy bitboard.
151 | */
152 | /* #define TB_ROOK_ATTACKS(square, occ) */
153 |
154 | /*
155 | * Define TB_BISHOP_ATTACKS(square, occ) to return the bishop attacks bitboard
156 | * for a bishop at `square' assuming the given `occ' occupancy bitboard.
157 | */
158 | /* #define TB_BISHOP_ATTACKS(square, occ) */
159 |
160 | /*
161 | * Define TB_QUEEN_ATTACKS(square, occ) to return the queen attacks bitboard
162 | * for a queen at `square' assuming the given `occ' occupancy bitboard.
163 | * NOTE: If no definition is provided then tbprobe will use:
164 | * TB_ROOK_ATTACKS(square, occ) | TB_BISHOP_ATTACKS(square, occ)
165 | */
166 | /* #define TB_QUEEN_ATTACKS(square, occ) */
167 |
168 | /*
169 | * Define TB_PAWN_ATTACKS(square, color) to return the pawn attacks bitboard
170 | * for a `color' pawn at `square'.
171 | * NOTE: This definition must work for pawns on ranks 1 and 8. For example,
172 | * a white pawn on e1 attacks d2 and f2. A black pawn on e1 attacks
173 | * nothing. Etc.
174 | * NOTE: This definition must not include en passant captures.
175 | */
176 | /* #define TB_PAWN_ATTACKS(square, color) */
177 |
178 | #endif
179 |
--------------------------------------------------------------------------------
/src/state/text/pgn.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use crate::state::movescan::Move;
3 | use crate::state::representation::Board;
4 | use std::fs::File;
5 | use std::io::BufReader;
6 | use std::io::Lines;
7 |
8 | pub struct PGNLoader {
9 | pub file_iterator: Lines>,
10 | }
11 |
12 | pub struct ParsedPGN {
13 | pub result: String,
14 | pub fen: Option,
15 | pub data: Vec,
16 | }
17 |
18 | pub struct ParsedPGNMove {
19 | pub r#move: Move,
20 | pub eval: f32,
21 | }
22 |
23 | impl PGNLoader {
24 | /// Constructs a new instance of [PGNLoader] with the specified `file_iterator`, which will be used to read input PGN file.
25 | pub fn new(file_iterator: Lines>) -> PGNLoader {
26 | PGNLoader { file_iterator }
27 | }
28 |
29 | /// Parses a single `pgn` and returns [Some] if it has been done with success, otherwise [Err].
30 | fn parse(&self, pgn: String) -> Result {
31 | let mut result = None;
32 | let mut fen = None;
33 | let mut moves = Vec::new();
34 |
35 | for line in pgn.lines() {
36 | if line.is_empty() {
37 | continue;
38 | } else if line.starts_with('[') {
39 | let name_start_index = match line.find(char::is_alphabetic) {
40 | Some(value) => value,
41 | None => return Err(format!("Invalid property: line={}", line)),
42 | };
43 |
44 | let name_end_index = match line[name_start_index..].find(' ') {
45 | Some(value) => name_start_index + value,
46 | None => return Err(format!("Invalid property: line={}", line)),
47 | };
48 |
49 | let value_start_index = match line.find('\"') {
50 | Some(value) => value + 1,
51 | None => return Err(format!("Invalid property: line={}", line)),
52 | };
53 |
54 | let value_end_index = match line[value_start_index..].find('\"') {
55 | Some(value) => value_start_index + value,
56 | None => return Err(format!("Invalid property: line={}", line)),
57 | };
58 |
59 | let name = line[name_start_index..name_end_index].to_string();
60 | let value = line[value_start_index..value_end_index].to_string();
61 |
62 | match name.as_str() {
63 | "Result" => result = Some(value),
64 | "FEN" => fen = Some(value),
65 | _ => {}
66 | }
67 | } else if line.starts_with('1') {
68 | let mut board = match fen.clone() {
69 | Some(value) => {
70 | let fen_result = Board::new_from_fen(&value);
71 |
72 | match fen_result {
73 | Ok(board) => board,
74 | Err(error) => return Err(format!("Invalid initial FEN position: {}", error)),
75 | }
76 | }
77 | None => Board::new_initial_position(),
78 | };
79 |
80 | let mut comment = false;
81 | let mut pgn_move = None;
82 | let mut pgn_eval: Option<&str> = None;
83 |
84 | for token in line.split_ascii_whitespace() {
85 | if token.ends_with('}') {
86 | comment = false;
87 | continue;
88 | }
89 |
90 | if token.as_bytes()[0].is_ascii_digit() {
91 | continue;
92 | }
93 |
94 | if let Some(r#move) = pgn_move {
95 | if let Some(eval) = pgn_eval {
96 | let mut eval = if eval.starts_with("+M") {
97 | 100.0
98 | } else if eval.starts_with("-M") {
99 | -100.0
100 | } else {
101 | eval.parse::().unwrap()
102 | };
103 |
104 | if board.stm == BLACK {
105 | eval = -eval;
106 | }
107 |
108 | moves.push(ParsedPGNMove::new(r#move, eval));
109 | board.make_move(r#move);
110 |
111 | pgn_move = None;
112 | pgn_eval = None;
113 | }
114 | }
115 |
116 | if let Some(token) = token.strip_prefix('{') {
117 | pgn_eval = Some(token.split('/').collect::>()[0]);
118 | comment = true;
119 | continue;
120 | }
121 |
122 | if comment {
123 | continue;
124 | }
125 |
126 | if token == "*" || token == "h1h1" {
127 | break;
128 | }
129 |
130 | pgn_move = match Move::from_short_notation(token, &mut board) {
131 | Ok(r#move) => Some(r#move),
132 | Err(error) => return Err(format!("Invalid move: {}", error)),
133 | };
134 | }
135 | }
136 | }
137 |
138 | let result = match result {
139 | Some(value) => value,
140 | None => return Err("No Result property".to_string()),
141 | };
142 |
143 | Ok(ParsedPGN::new(result, fen, moves))
144 | }
145 | }
146 |
147 | impl Iterator for PGNLoader {
148 | type Item = Result;
149 |
150 | /// Performs the next iteration by parsing the following PGN from the input file. If there are none left, returns [None].
151 | fn next(&mut self) -> Option {
152 | let mut pgn = String::new();
153 |
154 | while let Some(Ok(line)) = self.file_iterator.next() {
155 | if line.starts_with("[Event") && !pgn.is_empty() {
156 | break;
157 | }
158 |
159 | let trimmed_line = line.trim();
160 | if trimmed_line.is_empty() {
161 | continue;
162 | }
163 |
164 | pgn.push_str(trimmed_line);
165 |
166 | if line.starts_with('[') {
167 | pgn.push('\n');
168 | } else {
169 | pgn.push(' ');
170 | }
171 | }
172 |
173 | if !pgn.is_empty() {
174 | return Some(self.parse(pgn));
175 | }
176 |
177 | None
178 | }
179 | }
180 |
181 | impl ParsedPGN {
182 | /// Constructs a new instance of [ParsedPGN] with stored `result`, `fen` and `moves`.
183 | pub fn new(result: String, fen: Option, moves: Vec) -> ParsedPGN {
184 | ParsedPGN { result, fen, data: moves }
185 | }
186 | }
187 |
188 | impl ParsedPGNMove {
189 | pub fn new(r#move: Move, eval: f32) -> Self {
190 | Self { r#move, eval }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/evaluation/mobility.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use crate::state::movescan;
3 | use crate::state::representation::Board;
4 | use crate::utils::assert_fast;
5 | use crate::utils::bithelpers::BitHelpers;
6 |
7 | #[cfg(feature = "dev")]
8 | use crate::tuning::tuner::TunerCoeff;
9 |
10 | pub struct MobilityData {
11 | rook_open_file: i8,
12 | rook_semi_open_file: i8,
13 | knight_mobility: PieceMobility,
14 | bishop_mobility: PieceMobility,
15 | rook_mobility: PieceMobility,
16 | queen_mobility: PieceMobility,
17 | }
18 |
19 | #[derive(Default)]
20 | pub struct EvalAux {
21 | pub king_area_threats: i8,
22 | pub knight_threats: u64,
23 | pub bishop_threats: u64,
24 | pub rook_threats: u64,
25 | pub queen_threats: u64,
26 | }
27 |
28 | pub struct PieceMobility {
29 | pub inner: i8,
30 | pub outer: i8,
31 | }
32 |
33 | /// Evaluates mobility and part of the king safety on the `board` and returns score from the white color perspective (more than 0 when advantage,
34 | /// less than 0 when disadvantage). This evaluator does two things at once: first, counts all possible moves of knight, bishop, rook, queen
35 | /// (pawns and king are too slow and not very important), and second, fills `white_aux` and `black_aux` with additional data used in other evaluators.
36 | pub fn evaluate(board: &Board, white_aux: &mut EvalAux, black_aux: &mut EvalAux) -> PackedEval {
37 | let mut result = PackedEval::default();
38 | let white_data = get_mobility_data(board, WHITE, white_aux);
39 | let black_data = get_mobility_data(board, BLACK, black_aux);
40 |
41 | result += (white_data.rook_open_file - black_data.rook_open_file) * params::ROOK_OPEN_FILE;
42 | result += (white_data.rook_semi_open_file - black_data.rook_semi_open_file) * params::ROOK_SEMI_OPEN_FILE;
43 |
44 | result += (white_data.knight_mobility.inner - black_data.knight_mobility.inner) * params::MOBILITY_INNER[KNIGHT];
45 | result += (white_data.bishop_mobility.inner - black_data.bishop_mobility.inner) * params::MOBILITY_INNER[BISHOP];
46 | result += (white_data.rook_mobility.inner - black_data.rook_mobility.inner) * params::MOBILITY_INNER[ROOK];
47 | result += (white_data.queen_mobility.inner - black_data.queen_mobility.inner) * params::MOBILITY_INNER[QUEEN];
48 |
49 | result += (white_data.knight_mobility.outer - black_data.knight_mobility.outer) * params::MOBILITY_OUTER[KNIGHT];
50 | result += (white_data.bishop_mobility.outer - black_data.bishop_mobility.outer) * params::MOBILITY_OUTER[BISHOP];
51 | result += (white_data.rook_mobility.outer - black_data.rook_mobility.outer) * params::MOBILITY_OUTER[ROOK];
52 | result += (white_data.queen_mobility.outer - black_data.queen_mobility.outer) * params::MOBILITY_OUTER[QUEEN];
53 |
54 | result
55 | }
56 |
57 | /// Gets mobility data for `board`, `color` and fills `aux` with additional data used in other evaluators.
58 | fn get_mobility_data(board: &Board, color: usize, aux: &mut EvalAux) -> MobilityData {
59 | assert_fast!(color < 2);
60 |
61 | let mut rook_open_file = 0;
62 | let mut rook_semi_open_file = 0;
63 | let mut rooks_bb = board.pieces[color][ROOK];
64 |
65 | while rooks_bb != 0 {
66 | let square_bb = rooks_bb.get_lsb();
67 | let square = square_bb.bit_scan();
68 | rooks_bb = rooks_bb.pop_lsb();
69 |
70 | let file = patterns::get_file(square);
71 | if (file & board.pieces[color][PAWN]) == 0 {
72 | if (file & board.pieces[color ^ 1][PAWN]) == 0 {
73 | rook_open_file += 1;
74 | } else {
75 | rook_semi_open_file += 1;
76 | }
77 | }
78 | }
79 |
80 | MobilityData {
81 | rook_open_file,
82 | rook_semi_open_file,
83 |
84 | knight_mobility: movescan::get_piece_mobility::(board, color, aux),
85 | bishop_mobility: movescan::get_piece_mobility::(board, color, aux),
86 | rook_mobility: movescan::get_piece_mobility::(board, color, aux),
87 | queen_mobility: movescan::get_piece_mobility::(board, color, aux),
88 | }
89 | }
90 |
91 | /// Gets coefficients of mobility for `board` and inserts them into `coeffs`. Similarly, their indices (starting from `index`) are inserted into `indices`.
92 | /// Some additional data is also saved in `white_aux` and `black_aux` for further processing.
93 | #[cfg(feature = "dev")]
94 | pub fn get_coeffs(board: &Board, white_aux: &mut EvalAux, black_aux: &mut EvalAux, index: &mut u16, coeffs: &mut Vec, indices: &mut Vec) {
95 | let white_data = get_mobility_data(board, WHITE, white_aux);
96 | let black_data = get_mobility_data(board, BLACK, black_aux);
97 |
98 | let mut data = [
99 | TunerCoeff::new(white_data.rook_open_file - black_data.rook_open_file, OPENING),
100 | TunerCoeff::new(white_data.rook_open_file - black_data.rook_open_file, ENDING),
101 | TunerCoeff::new(white_data.rook_semi_open_file - black_data.rook_semi_open_file, OPENING),
102 | TunerCoeff::new(white_data.rook_semi_open_file - black_data.rook_semi_open_file, ENDING),
103 | //
104 | TunerCoeff::new(0, OPENING),
105 | TunerCoeff::new(0, ENDING),
106 | TunerCoeff::new(white_data.knight_mobility.inner - black_data.knight_mobility.inner, OPENING),
107 | TunerCoeff::new(white_data.knight_mobility.inner - black_data.knight_mobility.inner, ENDING),
108 | TunerCoeff::new(white_data.bishop_mobility.inner - black_data.bishop_mobility.inner, OPENING),
109 | TunerCoeff::new(white_data.bishop_mobility.inner - black_data.bishop_mobility.inner, ENDING),
110 | TunerCoeff::new(white_data.rook_mobility.inner - black_data.rook_mobility.inner, OPENING),
111 | TunerCoeff::new(white_data.rook_mobility.inner - black_data.rook_mobility.inner, ENDING),
112 | TunerCoeff::new(white_data.queen_mobility.inner - black_data.queen_mobility.inner, OPENING),
113 | TunerCoeff::new(white_data.queen_mobility.inner - black_data.queen_mobility.inner, ENDING),
114 | TunerCoeff::new(0, OPENING),
115 | TunerCoeff::new(0, ENDING),
116 | //
117 | TunerCoeff::new(0, OPENING),
118 | TunerCoeff::new(0, ENDING),
119 | TunerCoeff::new(white_data.knight_mobility.outer - black_data.knight_mobility.outer, OPENING),
120 | TunerCoeff::new(white_data.knight_mobility.outer - black_data.knight_mobility.outer, ENDING),
121 | TunerCoeff::new(white_data.bishop_mobility.outer - black_data.bishop_mobility.outer, OPENING),
122 | TunerCoeff::new(white_data.bishop_mobility.outer - black_data.bishop_mobility.outer, ENDING),
123 | TunerCoeff::new(white_data.rook_mobility.outer - black_data.rook_mobility.outer, OPENING),
124 | TunerCoeff::new(white_data.rook_mobility.outer - black_data.rook_mobility.outer, ENDING),
125 | TunerCoeff::new(white_data.queen_mobility.outer - black_data.queen_mobility.outer, OPENING),
126 | TunerCoeff::new(white_data.queen_mobility.outer - black_data.queen_mobility.outer, ENDING),
127 | TunerCoeff::new(0, OPENING),
128 | TunerCoeff::new(0, ENDING),
129 | ];
130 |
131 | for coeff in &mut data {
132 | let (value, _) = coeff.get_data();
133 | if value != 0 {
134 | indices.push(*index);
135 | coeffs.push(coeff.clone());
136 | }
137 |
138 | *index += 1;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/state/patterns.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use crate::utils::assert_fast;
3 |
4 | pub const DIAGONAL_PATTERNS: [u64; 64] = generate_diagonals();
5 | pub const JUMP_PATTERNS: [u64; 64] = generate_jumps();
6 | pub const BOX_PATTERNS: [u64; 64] = generate_boxes();
7 | pub const RAIL_PATTERNS: [u64; 8] = generate_rails();
8 | pub const FRONT_PATTERNS: [[u64; 64]; 2] = generate_fronts();
9 |
10 | /// Gets a file pattern for the square specified by `square`.
11 | /// ```
12 | /// . . . x . . . .
13 | /// . . . x . . . .
14 | /// . . . x . . . .
15 | /// . . . x . . . .
16 | /// . . . x . . . .
17 | /// . . . x . . . .
18 | /// . . . x . . . .
19 | /// . . . x . . . .
20 | /// ```
21 | pub fn get_file(square: usize) -> u64 {
22 | assert_fast!(square < 64);
23 | FILE_H_BB << (square % 8)
24 | }
25 |
26 | /// Gets a rank pattern for the square specified by `square`.
27 | /// ```
28 | /// . . . . . . . .
29 | /// . . . . . . . .
30 | /// . . . . . . . .
31 | /// . . . . . . . .
32 | /// x x x x x x x x
33 | /// . . . . . . . .
34 | /// . . . . . . . .
35 | /// . . . . . . . .
36 | /// ```
37 | pub fn get_rank(square: usize) -> u64 {
38 | assert_fast!(square < 64);
39 | RANK_1_BB << ((square / 8) * 8)
40 | }
41 |
42 | /// Gets a diagonal pattern for the square specified by `square_`.
43 | /// ```
44 | /// . . . . . . . x
45 | /// x . . . . . x .
46 | /// . x . . . x . .
47 | /// . . x . x . . .
48 | /// . . . x . . . .
49 | /// . . x . x . . .
50 | /// . x . . . x . .
51 | /// x . . . . . x .
52 | /// ```
53 | pub fn get_diagonals(square: usize) -> u64 {
54 | assert_fast!(square < 64);
55 | DIAGONAL_PATTERNS[square]
56 | }
57 |
58 | /// Get a jumps pattern for the square specified by `square`.
59 | /// ```
60 | /// . . . . . . . .
61 | /// . . . . . . . .
62 | /// . . x . x . . .
63 | /// . x . . . x . .
64 | /// . . . . . . . .
65 | /// . x . . . x . .
66 | /// . . x . x . . .
67 | /// . . . . . . . .
68 | /// ```
69 | pub fn get_jumps(square: usize) -> u64 {
70 | assert_fast!(square < 64);
71 | JUMP_PATTERNS[square]
72 | }
73 |
74 | /// Get a box pattern for the square specified by `square`.
75 | /// ```
76 | /// . . . . . . . .
77 | /// . . . . . . . .
78 | /// . . . . . . . .
79 | /// . . x x x . . .
80 | /// . . x x x . . .
81 | /// . . x x x . . .
82 | /// . . . . . . . .
83 | /// . . . . . . . .
84 | /// ```
85 | pub fn get_box(square: usize) -> u64 {
86 | assert_fast!(square < 64);
87 | BOX_PATTERNS[square]
88 | }
89 |
90 | /// Get a rail pattern for the square specified by `file`.
91 | /// ```
92 | /// . . x . x . . .
93 | /// . . x . x . . .
94 | /// . . x . x . . .
95 | /// . . x . x . . .
96 | /// . . x . x . . .
97 | /// . . x . x . . .
98 | /// . . x . x . . .
99 | /// . . x . x . . .
100 | /// ```
101 | pub fn get_rail(file: usize) -> u64 {
102 | assert_fast!(file < 8);
103 | RAIL_PATTERNS[file]
104 | }
105 |
106 | /// Get a front pattern for the square specified by `square`, from the `color` perspective.
107 | /// ```
108 | /// . . x x x . . .
109 | /// . . x x x . . .
110 | /// . . x x x . . .
111 | /// . . x x x . . .
112 | /// . . . . . . . .
113 | /// . . . . . . . .
114 | /// . . . . . . . .
115 | /// . . . . . . . .
116 | /// ```
117 | pub fn get_front(color: usize, square: usize) -> u64 {
118 | assert_fast!(color < 2);
119 | assert_fast!(square < 64);
120 | FRONT_PATTERNS[color][square]
121 | }
122 |
123 | /// Generates diagonal patterns for all squares.
124 | pub const fn generate_diagonals() -> [u64; 64] {
125 | let mut square = 0;
126 | let mut result = [0; 64];
127 |
128 | while square < 64 {
129 | let mut index = 0;
130 | while index < 4 {
131 | let offset = ((index % 2) * 2 - 1, (index / 2) * 2 - 1);
132 | let mut current = ((square as isize) % 8 + offset.0, (square as isize) / 8 + offset.1);
133 |
134 | while current.0 >= 0 && current.0 <= 7 && current.1 >= 0 && current.1 <= 7 {
135 | result[square] |= 1u64 << (current.0 + current.1 * 8);
136 | current = (current.0 + offset.0, current.1 + offset.1);
137 | }
138 |
139 | index += 1;
140 | }
141 |
142 | result[square] |= 1u64 << square;
143 | square += 1;
144 | }
145 |
146 | result
147 | }
148 |
149 | /// Generates jump patterns for all fields.
150 | pub const fn generate_jumps() -> [u64; 64] {
151 | let mut square = 0;
152 | let mut result = [0; 64];
153 |
154 | while square < 64 {
155 | let square_bb = 1u64 << square;
156 | result[square] = 0
157 | | ((square_bb & !FILE_G_BB & !FILE_H_BB) << 6)
158 | | ((square_bb & !FILE_A_BB & !FILE_B_BB) >> 6)
159 | | ((square_bb & !FILE_A_BB & !FILE_B_BB) << 10)
160 | | ((square_bb & !FILE_G_BB & !FILE_H_BB) >> 10)
161 | | ((square_bb & !FILE_H_BB) << 15)
162 | | ((square_bb & !FILE_A_BB) >> 15)
163 | | ((square_bb & !FILE_A_BB) << 17)
164 | | ((square_bb & !FILE_H_BB) >> 17);
165 |
166 | square += 1;
167 | }
168 |
169 | result
170 | }
171 |
172 | /// Generates box patterns for all squares.
173 | pub const fn generate_boxes() -> [u64; 64] {
174 | let mut square = 0;
175 | let mut result = [0; 64];
176 |
177 | while square < 64 {
178 | let square_bb = 1u64 << square;
179 | result[square] = 0
180 | | ((square_bb & !FILE_A_BB) << 1)
181 | | ((square_bb & !FILE_H_BB) >> 1)
182 | | ((square_bb & !FILE_H_BB) << 7)
183 | | ((square_bb & !FILE_A_BB) >> 7)
184 | | ((square_bb & !RANK_8_BB) << 8)
185 | | ((square_bb & !RANK_1_BB) >> 8)
186 | | ((square_bb & !FILE_A_BB) << 9)
187 | | ((square_bb & !FILE_H_BB) >> 9);
188 |
189 | square += 1;
190 | }
191 |
192 | result
193 | }
194 |
195 | /// Generates rail patterns for all squares.
196 | pub const fn generate_rails() -> [u64; 8] {
197 | let mut file = 0;
198 | let mut result = [0; 8];
199 |
200 | while file < 8 {
201 | let left_file_bb = if file > 0 { FILE_H_BB << (file - 1) } else { 0 };
202 | let right_file_bb = if file < 7 { FILE_H_BB << (file + 1) } else { 0 };
203 |
204 | result[file] = left_file_bb | right_file_bb;
205 | file += 1;
206 | }
207 |
208 | result
209 | }
210 |
211 | /// Generates front patterns for all squares.
212 | pub const fn generate_fronts() -> [[u64; 64]; 2] {
213 | let mut color = 0;
214 | let mut result = [[0; 64]; 2];
215 |
216 | while color < 2 {
217 | let mut square = 0;
218 | while square < 64 {
219 | let file = square % 8;
220 | let rank = square / 8;
221 |
222 | let center_file_bb = FILE_H_BB << file;
223 | let left_file_bb = if file > 0 { FILE_H_BB << (file - 1) } else { 0 };
224 | let right_file_bb = if file < 7 { FILE_H_BB << (file + 1) } else { 0 };
225 |
226 | let mut current_rank = rank as i8;
227 | let mut forbidden_area = 0;
228 | while current_rank >= 0 && current_rank < 8 {
229 | forbidden_area |= 255 << (current_rank * 8);
230 | current_rank += (color as i8) * 2 - 1;
231 | }
232 |
233 | result[color][square] = (left_file_bb | center_file_bb | right_file_bb) & !forbidden_area;
234 | square += 1;
235 | }
236 |
237 | color += 1;
238 | }
239 |
240 | result
241 | }
242 |
--------------------------------------------------------------------------------
/src/tablebases/syzygy/bindings.rs:
--------------------------------------------------------------------------------
1 | /* automatically generated by rust-bindgen 0.69.4 */
2 |
3 | pub const _VCRT_COMPILER_PREPROCESSOR: u32 = 1;
4 | pub const _SAL_VERSION: u32 = 20;
5 | pub const __SAL_H_VERSION: u32 = 180000000;
6 | pub const _USE_DECLSPECS_FOR_SAL: u32 = 0;
7 | pub const _USE_ATTRIBUTES_FOR_SAL: u32 = 0;
8 | pub const _CRT_PACKING: u32 = 8;
9 | pub const _HAS_EXCEPTIONS: u32 = 1;
10 | pub const _STL_LANG: u32 = 0;
11 | pub const _HAS_CXX17: u32 = 0;
12 | pub const _HAS_CXX20: u32 = 0;
13 | pub const _HAS_CXX23: u32 = 0;
14 | pub const _HAS_NODISCARD: u32 = 0;
15 | pub const WCHAR_MIN: u32 = 0;
16 | pub const WCHAR_MAX: u32 = 65535;
17 | pub const WINT_MIN: u32 = 0;
18 | pub const WINT_MAX: u32 = 65535;
19 | pub const TB_VALUE_PAWN: u32 = 100;
20 | pub const TB_VALUE_MATE: u32 = 32000;
21 | pub const TB_VALUE_INFINITE: u32 = 32767;
22 | pub const TB_VALUE_DRAW: u32 = 0;
23 | pub const TB_MAX_MATE_PLY: u32 = 255;
24 | pub const true_: u32 = 1;
25 | pub const false_: u32 = 0;
26 | pub const __bool_true_false_are_defined: u32 = 1;
27 | pub const TB_MAX_MOVES: u32 = 193;
28 | pub const TB_MAX_CAPTURES: u32 = 64;
29 | pub const TB_MAX_PLY: u32 = 256;
30 | pub const TB_CASTLING_K: u32 = 1;
31 | pub const TB_CASTLING_Q: u32 = 2;
32 | pub const TB_CASTLING_k: u32 = 4;
33 | pub const TB_CASTLING_q: u32 = 8;
34 | pub const TB_LOSS: u32 = 0;
35 | pub const TB_BLESSED_LOSS: u32 = 1;
36 | pub const TB_DRAW: u32 = 2;
37 | pub const TB_CURSED_WIN: u32 = 3;
38 | pub const TB_WIN: u32 = 4;
39 | pub const TB_PROMOTES_NONE: u32 = 0;
40 | pub const TB_PROMOTES_QUEEN: u32 = 1;
41 | pub const TB_PROMOTES_ROOK: u32 = 2;
42 | pub const TB_PROMOTES_BISHOP: u32 = 3;
43 | pub const TB_PROMOTES_KNIGHT: u32 = 4;
44 | pub const TB_RESULT_WDL_MASK: u32 = 15;
45 | pub const TB_RESULT_TO_MASK: u32 = 1008;
46 | pub const TB_RESULT_FROM_MASK: u32 = 64512;
47 | pub const TB_RESULT_PROMOTES_MASK: u32 = 458752;
48 | pub const TB_RESULT_EP_MASK: u32 = 524288;
49 | pub const TB_RESULT_DTZ_MASK: u32 = 4293918720;
50 | pub const TB_RESULT_WDL_SHIFT: u32 = 0;
51 | pub const TB_RESULT_TO_SHIFT: u32 = 4;
52 | pub const TB_RESULT_FROM_SHIFT: u32 = 10;
53 | pub const TB_RESULT_PROMOTES_SHIFT: u32 = 16;
54 | pub const TB_RESULT_EP_SHIFT: u32 = 19;
55 | pub const TB_RESULT_DTZ_SHIFT: u32 = 20;
56 | pub const TB_RESULT_FAILED: u32 = 4294967295;
57 | pub type va_list = *mut ::std::os::raw::c_char;
58 | extern "C" {
59 | pub fn __va_start(arg1: *mut *mut ::std::os::raw::c_char, ...);
60 | }
61 | pub type __vcrt_bool = bool;
62 | pub type wchar_t = ::std::os::raw::c_ushort;
63 | extern "C" {
64 | pub fn __security_init_cookie();
65 | }
66 | extern "C" {
67 | pub fn __security_check_cookie(_StackCookie: usize);
68 | }
69 | extern "C" {
70 | pub fn __report_gsfailure(_StackCookie: usize) -> !;
71 | }
72 | extern "C" {
73 | pub static mut __security_cookie: usize;
74 | }
75 | pub type int_least8_t = ::std::os::raw::c_schar;
76 | pub type int_least16_t = ::std::os::raw::c_short;
77 | pub type int_least32_t = ::std::os::raw::c_int;
78 | pub type int_least64_t = ::std::os::raw::c_longlong;
79 | pub type uint_least8_t = ::std::os::raw::c_uchar;
80 | pub type uint_least16_t = ::std::os::raw::c_ushort;
81 | pub type uint_least32_t = ::std::os::raw::c_uint;
82 | pub type uint_least64_t = ::std::os::raw::c_ulonglong;
83 | pub type int_fast8_t = ::std::os::raw::c_schar;
84 | pub type int_fast16_t = ::std::os::raw::c_int;
85 | pub type int_fast32_t = ::std::os::raw::c_int;
86 | pub type int_fast64_t = ::std::os::raw::c_longlong;
87 | pub type uint_fast8_t = ::std::os::raw::c_uchar;
88 | pub type uint_fast16_t = ::std::os::raw::c_uint;
89 | pub type uint_fast32_t = ::std::os::raw::c_uint;
90 | pub type uint_fast64_t = ::std::os::raw::c_ulonglong;
91 | pub type intmax_t = ::std::os::raw::c_longlong;
92 | pub type uintmax_t = ::std::os::raw::c_ulonglong;
93 | extern "C" {
94 | pub static index64: [::std::os::raw::c_int; 64usize];
95 | }
96 | extern "C" {
97 | pub fn tb_init_impl(_path: *const ::std::os::raw::c_char) -> bool;
98 | }
99 | extern "C" {
100 | pub fn tb_probe_wdl_impl(
101 | _white: u64,
102 | _black: u64,
103 | _kings: u64,
104 | _queens: u64,
105 | _rooks: u64,
106 | _bishops: u64,
107 | _knights: u64,
108 | _pawns: u64,
109 | _ep: ::std::os::raw::c_uint,
110 | _turn: bool,
111 | ) -> ::std::os::raw::c_uint;
112 | }
113 | extern "C" {
114 | pub fn tb_probe_root_impl(
115 | _white: u64,
116 | _black: u64,
117 | _kings: u64,
118 | _queens: u64,
119 | _rooks: u64,
120 | _bishops: u64,
121 | _knights: u64,
122 | _pawns: u64,
123 | _rule50: ::std::os::raw::c_uint,
124 | _ep: ::std::os::raw::c_uint,
125 | _turn: bool,
126 | _results: *mut ::std::os::raw::c_uint,
127 | ) -> ::std::os::raw::c_uint;
128 | }
129 | extern "C" {
130 | pub static mut TB_LARGEST: ::std::os::raw::c_uint;
131 | }
132 | extern "C" {
133 | pub fn tb_init(_path: *const ::std::os::raw::c_char) -> bool;
134 | }
135 | extern "C" {
136 | pub fn tb_free();
137 | }
138 | extern "C" {
139 | pub fn tb_probe_wdl(
140 | _white: u64,
141 | _black: u64,
142 | _kings: u64,
143 | _queens: u64,
144 | _rooks: u64,
145 | _bishops: u64,
146 | _knights: u64,
147 | _pawns: u64,
148 | _rule50: ::std::os::raw::c_uint,
149 | _castling: ::std::os::raw::c_uint,
150 | _ep: ::std::os::raw::c_uint,
151 | _turn: bool,
152 | ) -> ::std::os::raw::c_uint;
153 | }
154 | extern "C" {
155 | pub fn tb_probe_root(
156 | _white: u64,
157 | _black: u64,
158 | _kings: u64,
159 | _queens: u64,
160 | _rooks: u64,
161 | _bishops: u64,
162 | _knights: u64,
163 | _pawns: u64,
164 | _rule50: ::std::os::raw::c_uint,
165 | _castling: ::std::os::raw::c_uint,
166 | _ep: ::std::os::raw::c_uint,
167 | _turn: bool,
168 | _results: *mut ::std::os::raw::c_uint,
169 | ) -> ::std::os::raw::c_uint;
170 | }
171 | pub type TbMove = u16;
172 | #[repr(C)]
173 | #[derive(Debug, Copy, Clone)]
174 | pub struct TbRootMove {
175 | pub move_: TbMove,
176 | pub pv: [TbMove; 256usize],
177 | pub pvSize: ::std::os::raw::c_uint,
178 | pub tbScore: i32,
179 | pub tbRank: i32,
180 | }
181 | #[repr(C)]
182 | #[derive(Debug, Copy, Clone)]
183 | pub struct TbRootMoves {
184 | pub size: ::std::os::raw::c_uint,
185 | pub moves: [TbRootMove; 193usize],
186 | }
187 | extern "C" {
188 | pub fn tb_probe_root_dtz(
189 | _white: u64,
190 | _black: u64,
191 | _kings: u64,
192 | _queens: u64,
193 | _rooks: u64,
194 | _bishops: u64,
195 | _knights: u64,
196 | _pawns: u64,
197 | _rule50: ::std::os::raw::c_uint,
198 | _castling: ::std::os::raw::c_uint,
199 | _ep: ::std::os::raw::c_uint,
200 | _turn: bool,
201 | hasRepeated: bool,
202 | useRule50: bool,
203 | _results: *mut TbRootMoves,
204 | ) -> ::std::os::raw::c_int;
205 | }
206 | extern "C" {
207 | pub fn tb_probe_root_wdl(
208 | _white: u64,
209 | _black: u64,
210 | _kings: u64,
211 | _queens: u64,
212 | _rooks: u64,
213 | _bishops: u64,
214 | _knights: u64,
215 | _pawns: u64,
216 | _rule50: ::std::os::raw::c_uint,
217 | _castling: ::std::os::raw::c_uint,
218 | _ep: ::std::os::raw::c_uint,
219 | _turn: bool,
220 | useRule50: bool,
221 | _results: *mut TbRootMoves,
222 | ) -> ::std::os::raw::c_int;
223 | }
224 |
--------------------------------------------------------------------------------
/src/evaluation/pawns.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use crate::cache::pawns::PHTable;
3 | use crate::engine::stats::SearchStats;
4 | use crate::state::representation::Board;
5 | use crate::utils::assert_fast;
6 | use crate::utils::bithelpers::BitHelpers;
7 | use crate::utils::dev;
8 | use std::cmp;
9 |
10 | #[cfg(feature = "dev")]
11 | use crate::tuning::tuner::TunerCoeff;
12 |
13 | pub struct PawnsData {
14 | doubled_pawns: u8,
15 | isolated_pawns: u8,
16 | chained_pawns: u8,
17 | passed_pawns: u8,
18 | backward_pawns_open_file: u8,
19 | backward_pawns_closed_file: u8,
20 | opened_files: u8,
21 | pawn_shield: u8,
22 | }
23 |
24 | /// Evaluates structure of pawns on the `board` and returns score from the white color perspective (more than 0 when advantage,
25 | /// less than 0 when disadvantage). This evaluator considers:
26 | /// - doubled pawns
27 | /// - isolated pawns
28 | /// - chained pawns
29 | /// - passed pawns
30 | /// - backward pawns
31 | /// - open files next to the king
32 | /// - pawn shield next to the king
33 | ///
34 | /// To improve performance (using the fact that structure of pawns changes relatively rare), each evaluation is saved in the pawn hashtable,
35 | /// and used again if possible.
36 | pub fn evaluate(board: &Board, phtable: &PHTable, stats: &mut SearchStats) -> PackedEval {
37 | match phtable.get(board.state.pawn_hash) {
38 | Some(entry) => {
39 | dev!(stats.phtable_hits += 1);
40 | return PackedEval::new(entry.score_opening, entry.score_ending);
41 | }
42 | None => {
43 | dev!(stats.phtable_misses += 1);
44 | }
45 | }
46 |
47 | let white_eval = evaluate_color(board, WHITE);
48 | let black_eval = evaluate_color(board, BLACK);
49 | let eval = white_eval - black_eval;
50 |
51 | phtable.add(board.state.pawn_hash, eval.get_opening(), eval.get_ending());
52 | dev!(stats.phtable_added += 1);
53 |
54 | eval
55 | }
56 |
57 | /// Does the same thing as [evaluate], but without using pawn hashtable to save evalations.
58 | pub fn evaluate_without_cache(board: &Board) -> PackedEval {
59 | evaluate_color(board, WHITE) - evaluate_color(board, BLACK)
60 | }
61 |
62 | /// Evaluates pawn structure on the `board` for the specified `color`.
63 | fn evaluate_color(board: &Board, color: usize) -> PackedEval {
64 | assert_fast!(color < 2);
65 |
66 | let mut result = PackedEval::default();
67 | let pawns_data = get_pawns_data(board, color);
68 |
69 | result += params::DOUBLED_PAWN[pawns_data.doubled_pawns.min(7) as usize];
70 | result += params::ISOLATED_PAWN[pawns_data.isolated_pawns.min(7) as usize];
71 | result += params::CHAINED_PAWN[pawns_data.chained_pawns.min(7) as usize];
72 | result += params::PASSED_PAWN[pawns_data.passed_pawns.min(7) as usize];
73 | result += params::BACKWARD_PAWN_OPEN_FILE[pawns_data.backward_pawns_open_file.min(7) as usize];
74 | result += params::BACKWARD_PAWN_CLOSED_FILE[pawns_data.backward_pawns_closed_file.min(7) as usize];
75 | result += params::PAWN_SHIELD[pawns_data.pawn_shield.min(7) as usize];
76 | result += params::PAWN_SHIELD_OPEN_FILE[pawns_data.opened_files.min(7) as usize];
77 |
78 | result
79 | }
80 |
81 | /// Gets all pawn features on `board` for `color`.
82 | fn get_pawns_data(board: &Board, color: usize) -> PawnsData {
83 | assert_fast!(color < 2);
84 |
85 | let mut doubled_pawns = 0;
86 | let mut isolated_pawns = 0;
87 | let mut chained_pawns = 0;
88 | let mut passed_pawns = 0;
89 | let mut backward_pawns_open_file = 0;
90 | let mut backward_pawns_closed_file = 0;
91 | let mut pawn_shield = 0;
92 | let mut opened_files = 0;
93 |
94 | for file in ALL_FILES {
95 | let pawns_on_file = patterns::get_file(file) & board.pieces[color][PAWN];
96 | if pawns_on_file != 0 {
97 | let pawns_on_file_count = pawns_on_file.bit_count() as u8;
98 |
99 | if pawns_on_file_count > 1 {
100 | doubled_pawns += pawns_on_file_count - 1;
101 | }
102 |
103 | if (patterns::get_rail(file) & board.pieces[color][PAWN]) == 0 {
104 | isolated_pawns += 1;
105 | }
106 | }
107 | }
108 |
109 | let mut pawns_bb = board.pieces[color][PAWN];
110 | while pawns_bb != 0 {
111 | let square_bb = pawns_bb.get_lsb();
112 | let square = square_bb.bit_scan();
113 | pawns_bb = pawns_bb.pop_lsb();
114 |
115 | chained_pawns += ((patterns::get_front(color ^ 1, square) & patterns::get_diagonals(square) & board.pieces[color][PAWN]) != 0) as u8;
116 | passed_pawns += ((patterns::get_front(color, square) & board.pieces[color ^ 1][PAWN]) == 0) as u8;
117 |
118 | let offset = if color == WHITE { 8 } else { -8 };
119 | let stop_square_bb = if color == WHITE { square_bb << 8 } else { square_bb >> 8 };
120 | let front = patterns::get_front(color, square) & !patterns::get_file(square);
121 | let front_backward = patterns::get_front(color ^ 1, (square as i8 + offset) as usize) & !patterns::get_file((square as i8 + offset) as usize);
122 |
123 | let not_isolated = (front & board.pieces[color][PAWN]) != 0;
124 | let no_pawns_behind = (front_backward & board.pieces[color][PAWN]) == 0;
125 | let stop_square_attacked = (stop_square_bb & board.pawn_attacks[color ^ 1]) != 0;
126 | let open_file = (patterns::get_file(square) & board.pieces[color ^ 1][PAWN]) == 0;
127 |
128 | if not_isolated && no_pawns_behind && stop_square_attacked {
129 | if open_file {
130 | backward_pawns_open_file += 1;
131 | } else {
132 | backward_pawns_closed_file += 1;
133 | }
134 | }
135 | }
136 |
137 | let king_bb = board.pieces[color][KING];
138 | let king_square = king_bb.bit_scan();
139 | let king_square_file = (king_square & 7) as i8;
140 | pawn_shield = (patterns::get_box(king_square) & board.pieces[color][PAWN]).bit_count() as u8;
141 |
142 | for file in cmp::max(0, king_square_file - 1)..=(cmp::min(7, king_square_file + 1)) {
143 | if (patterns::get_file(file as usize) & board.pieces[color][PAWN]) == 0 {
144 | opened_files += 1;
145 | }
146 | }
147 |
148 | PawnsData { doubled_pawns, isolated_pawns, chained_pawns, passed_pawns, backward_pawns_open_file, backward_pawns_closed_file, pawn_shield, opened_files }
149 | }
150 |
151 | /// Gets coefficients of pawn structure for `board` and inserts them into `coeffs`. Similarly, their indices (starting from `index`) are inserted into `indices`.
152 | #[cfg(feature = "dev")]
153 | pub fn get_coeffs(board: &Board, index: &mut u16, coeffs: &mut Vec, indices: &mut Vec) {
154 | let white_pawns_data = get_pawns_data(board, WHITE);
155 | let black_pawns_data = get_pawns_data(board, BLACK);
156 |
157 | get_array_coeffs(white_pawns_data.doubled_pawns, black_pawns_data.doubled_pawns, 8, index, coeffs, indices);
158 | get_array_coeffs(white_pawns_data.isolated_pawns, black_pawns_data.isolated_pawns, 8, index, coeffs, indices);
159 | get_array_coeffs(white_pawns_data.chained_pawns, black_pawns_data.chained_pawns, 8, index, coeffs, indices);
160 | get_array_coeffs(white_pawns_data.passed_pawns, black_pawns_data.passed_pawns, 8, index, coeffs, indices);
161 | get_array_coeffs(white_pawns_data.backward_pawns_open_file, black_pawns_data.backward_pawns_open_file, 8, index, coeffs, indices);
162 | get_array_coeffs(white_pawns_data.backward_pawns_closed_file, black_pawns_data.backward_pawns_closed_file, 8, index, coeffs, indices);
163 | get_array_coeffs(white_pawns_data.pawn_shield, black_pawns_data.pawn_shield, 8, index, coeffs, indices);
164 | get_array_coeffs(white_pawns_data.opened_files, black_pawns_data.opened_files, 8, index, coeffs, indices);
165 | }
166 |
--------------------------------------------------------------------------------
/src/evaluation/pst/mod.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use crate::evaluation;
3 | use crate::state::representation::Board;
4 | use crate::utils::assert_fast;
5 | use crate::utils::bithelpers::BitHelpers;
6 |
7 | #[cfg(feature = "dev")]
8 | use crate::tuning::tuner::TunerCoeff;
9 |
10 | pub mod bishop;
11 | pub mod king;
12 | pub mod knight;
13 | pub mod pawn;
14 | pub mod queen;
15 | pub mod rook;
16 |
17 | pub use bishop::BISHOP_PST_PATTERN;
18 | pub use king::KING_PST_PATTERN;
19 | pub use knight::KNIGHT_PST_PATTERN;
20 | pub use pawn::PAWN_PST_PATTERN;
21 | pub use queen::QUEEN_PST_PATTERN;
22 | pub use rook::ROOK_PST_PATTERN;
23 |
24 | pub const KING_BUCKETS_COUNT: usize = 16;
25 |
26 | #[rustfmt::skip]
27 | pub const KING_BUCKETS: [usize; 64] = [
28 | 15, 14, 13, 12, 11, 10, 9, 8,
29 | 15, 14, 13, 12, 11, 10, 9, 8,
30 | 15, 14, 13, 12, 11, 10, 9, 8,
31 | 15, 14, 13, 12, 11, 10, 9, 8,
32 | 7, 6, 5, 4, 3, 2, 1, 0,
33 | 7, 6, 5, 4, 3, 2, 1, 0,
34 | 7, 6, 5, 4, 3, 2, 1, 0,
35 | 7, 6, 5, 4, 3, 2, 1, 0,
36 | ];
37 |
38 | /// Evaluates piece-square table value on the `board` and returns score from the white color perspective (more than 0 when advantage, less than 0 when disadvantage).
39 | pub fn evaluate(board: &Board) -> PackedEval {
40 | board.state.pst_score
41 | }
42 |
43 | /// Recalculates incremental counters on the `board`. This function should be called only if really necessary, as it's too slow in regular search.
44 | pub fn recalculate_incremental_values(board: &mut Board) {
45 | let mut score = PackedEval::default();
46 |
47 | for color in ALL_COLORS {
48 | let sign = -(color as i16 * 2 - 1);
49 |
50 | let king_bb = board.pieces[color][KING];
51 | let king_square = match color == WHITE {
52 | true => king_bb.bit_scan() % 64,
53 | false => king_bb.swap_bytes().bit_scan() % 64,
54 | };
55 |
56 | let enemy_king_bb = board.pieces[color ^ 1][KING];
57 | let enemy_king_square = match color == WHITE {
58 | true => enemy_king_bb.swap_bytes().bit_scan() % 64,
59 | false => enemy_king_bb.bit_scan() % 64,
60 | };
61 |
62 | for pov in ALL_POVS {
63 | let king_square = if pov == US { king_square } else { enemy_king_square };
64 | for piece_index in ALL_PIECES {
65 | let mut pieces_bb = board.pieces[color][piece_index];
66 | while pieces_bb != 0 {
67 | let square_bb = pieces_bb.get_lsb();
68 | let mut square = square_bb.bit_scan();
69 | pieces_bb = pieces_bb.pop_lsb();
70 |
71 | if color == BLACK {
72 | square = (1u64 << square).swap_bytes().bit_scan();
73 | }
74 |
75 | score += sign * evaluation::get_pst_value(piece_index, pov, king_square, square);
76 | }
77 | }
78 | }
79 | }
80 |
81 | board.state.pst_score = score;
82 | }
83 |
84 | /// Gets a PST value for the specified `piece`, `pov`, `king_square` and `square` (relative perspective).
85 | pub fn get_pst_value(piece: usize, pov: usize, king_square: usize, square: usize) -> PackedEval {
86 | assert_fast!(piece < 6);
87 | assert_fast!(pov < 2);
88 | assert_fast!(king_square < 64);
89 | assert_fast!(square < 64);
90 |
91 | let pst = match piece {
92 | PAWN => &pst::PAWN_PST_PATTERN,
93 | KNIGHT => &pst::KNIGHT_PST_PATTERN,
94 | BISHOP => &pst::BISHOP_PST_PATTERN,
95 | ROOK => &pst::ROOK_PST_PATTERN,
96 | QUEEN => &pst::QUEEN_PST_PATTERN,
97 | KING => &pst::KING_PST_PATTERN,
98 | _ => panic_fast!("Invalid value: piece={}", piece),
99 | };
100 |
101 | assert_fast!(KING_BUCKETS[63 - king_square] < KING_BUCKETS_COUNT);
102 | pst[pov][KING_BUCKETS[63 - king_square]][63 - square]
103 | }
104 |
105 | /// Gets coefficients of piece-square table for `piece` on `board` and inserts them into `coeffs`.
106 | /// Similarly, their indices (starting from `index`) are inserted into `indices`.
107 | #[cfg(feature = "dev")]
108 | pub fn get_coeffs(board: &Board, piece: usize, index: &mut u16, coeffs: &mut Vec, indices: &mut Vec) {
109 | assert_fast!(piece < 6);
110 |
111 | for pov in ALL_POVS {
112 | for bucket in 0..KING_BUCKETS_COUNT {
113 | let (valid_for_white, valid_for_black) = if pov == US {
114 | (
115 | bucket == KING_BUCKETS[63 - board.pieces[WHITE][KING].bit_scan()],
116 | bucket == KING_BUCKETS[63 - board.pieces[BLACK][KING].swap_bytes().bit_scan()],
117 | )
118 | } else {
119 | (
120 | bucket == KING_BUCKETS[63 - board.pieces[BLACK][KING].swap_bytes().bit_scan()],
121 | bucket == KING_BUCKETS[63 - board.pieces[WHITE][KING].bit_scan()],
122 | )
123 | };
124 |
125 | for square in ALL_SQUARES {
126 | let current_index = 63 - square;
127 | let opposite_index = (1u64 << current_index).swap_bytes().bit_scan();
128 |
129 | let current_piece = board.piece_table[current_index];
130 | let opposite_piece = board.piece_table[opposite_index];
131 |
132 | let current_color = if (board.occupancy[WHITE] & (1 << current_index)) != 0 { WHITE } else { BLACK };
133 | let opposite_color = if (board.occupancy[WHITE] & (1 << opposite_index)) != 0 { WHITE } else { BLACK };
134 |
135 | if valid_for_white && !valid_for_black {
136 | if current_piece == piece as u8 && current_color == WHITE {
137 | coeffs.push(TunerCoeff::new(1, OPENING));
138 | coeffs.push(TunerCoeff::new(1, ENDING));
139 | indices.push(*index);
140 | indices.push(*index + 1);
141 | }
142 | } else if !valid_for_white && valid_for_black {
143 | if opposite_piece == piece as u8 && opposite_color == BLACK {
144 | coeffs.push(TunerCoeff::new(-1, OPENING));
145 | coeffs.push(TunerCoeff::new(-1, ENDING));
146 | indices.push(*index);
147 | indices.push(*index + 1);
148 | }
149 | } else if valid_for_white && valid_for_black {
150 | if current_piece == piece as u8 && opposite_piece != piece as u8 && current_color == WHITE {
151 | coeffs.push(TunerCoeff::new(1, OPENING));
152 | coeffs.push(TunerCoeff::new(1, ENDING));
153 | indices.push(*index);
154 | indices.push(*index + 1);
155 | } else if opposite_piece == piece as u8 && current_piece != piece as u8 && opposite_color == BLACK {
156 | coeffs.push(TunerCoeff::new(-1, OPENING));
157 | coeffs.push(TunerCoeff::new(-1, ENDING));
158 | indices.push(*index);
159 | indices.push(*index + 1);
160 | }
161 | }
162 |
163 | *index += 2;
164 | }
165 | }
166 | }
167 | }
168 |
169 | /// Gets coefficients for a specific feature (`white_data`/`black_data`/`max`) and inserts them into `coeffs`.
170 | /// Similarly, their indices (starting from `index`) are inserted into `indices`.
171 | #[cfg(feature = "dev")]
172 | pub fn get_array_coeffs(white_data: u8, black_data: u8, max: u8, index: &mut u16, coeffs: &mut Vec, indices: &mut Vec) {
173 | use std::cmp;
174 |
175 | let white_data = cmp::min(white_data, max - 1);
176 | let black_data = cmp::min(black_data, max - 1);
177 |
178 | for i in 0..max {
179 | let sum = (white_data == i) as i8 - (black_data == i) as i8;
180 | if sum != 0 {
181 | coeffs.push(TunerCoeff::new(sum, OPENING));
182 | coeffs.push(TunerCoeff::new(sum, ENDING));
183 | indices.push(*index);
184 | indices.push(*index + 1);
185 | }
186 |
187 | *index += 2;
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Version 1.6.0 (01-02-2025)
2 | - Added perspective and more buckets to PST
3 | - Added "Soft Nodes" and "Search Noise" UCI options
4 | - Added new evaluation features: tempo, backward pawns, open files, pawn threats, safe checks
5 | - Added clearing of killers between plies
6 | - Added time management parameters as tunable options
7 | - Removed killer table aging
8 | - Removed TT cutoffs from PV nodes completely
9 | - Removed min game phase condition in NMP
10 | - Removed max depth condition in SNMP
11 | - Removed max depth condition in razoring
12 | - Improved evaluation parameters
13 | - Improved search parameters
14 | - Improved time management parameters
15 | - Increased default move overhead to 100 ms
16 | - Allowed LMR to reduce losing captures
17 | - Fixed rare invalid moves when checkmate in one
18 | - Fixed PGN parser crashing on h1h1 move
19 | - Fixed incorrect upper bound score when saving to transposition table
20 |
21 | **Strength**: 3100 Elo
22 |
23 | # Version 1.5.0 (01-11-2024)
24 | - Added aspiration windows
25 | - Added support for tuning based on evaluation
26 | - Added "k" and "wdl_ratio" parameters to "tuner" command
27 | - Added prefetching of transposition table entries
28 | - Added packed evaluation
29 | - Added pawn structure to fast evaluation
30 | - Removed "lock material" parameter from "tuner" command
31 | - Removed "Crash Files" UCI option from the release version
32 | - Removed Lazy SMP artificial noise
33 | - Removed allocator, pawn hashtable has fixed size now
34 | - Improved tuner performance and excessive memory usage
35 | - Improved tuner output by setting unused parameters to zero
36 | - Improved indexing of transposition, pawn and perft tables
37 | - Improved rand algorithm (change from xorshift to xorshift*)
38 | - Improved engine strength when using multiple threads
39 | - Improved evaluation parameters
40 | - Improved overall performance
41 | - Incorporated piece values in PST
42 | - Fixed endless search in fixed-nodes mode
43 | - Fixed tuner output filename
44 | - Fixed crash when parsing certain FENs
45 | - Fixed incorrect workload between threads in tuner
46 | - Fixed incorrect evaluation of black pawn chains
47 |
48 | **Strength**: 3000 Elo
49 |
50 | # Version 1.4.0 (03-08-2024)
51 | - Added relative PST
52 | - Added check extensions
53 | - Added countermove heuristic
54 | - Added simplified benchmark when "dev" feature is not enabled
55 | - Added history table penalties and reduced aging divisor
56 | - Added non-standard "fen" command to UCI
57 | - Added crash when the best move is invalid (only in dev version)
58 | - Added pawn attacks cache
59 | - Added support for LZCNT instruction
60 | - Improved evaluation parameters by using a new dataset for tuning
61 | - Improved search parameters
62 | - Improved header, now it also includes LLVM version, target, profile and enabled features
63 | - Renamed "tunerset" command to "dataset"
64 | - Merged "bindgen" and "syzygy" features
65 | - Fixed rare bug with invalid moves when a search was aborted
66 | - Fixed crash when ply is larger than the killer table size
67 | - Fixed performance overhead of setting a new position
68 |
69 | **Strength**: 2950 Elo
70 |
71 | # Version 1.3.0 (14-06-2024)
72 | - Added search parameters as UCI options (only if the "dev" feature is enabled)
73 | - Added gradient descent tuner in place of local search
74 | - Added internal iterative reduction
75 | - Added bishop pair evaluation
76 | - Removed "avg_game_phase" in "tunerset" command
77 | - Removed "magic", "testset", "tuner" and "tunerset" commands from the release builds
78 | - Improved king safety evaluation
79 | - Improved quality of tunerset output
80 | - Improved search parameters
81 | - Improved pawn structure evaluation
82 | - Improved mobility evaluation by excluding squares attacked by enemy pawns
83 | - Fixed invalid position score when both kings are checked
84 | - Fixed incorrect SEE results for sliding pieces
85 |
86 | **Strength**: 2900 Elo
87 |
88 | # Version 1.2.1 (04-09-2023)
89 | - Added executing commands directly from a command line
90 | - Added perft in UCI mode (go perft)
91 |
92 | # Version 1.2.0 (15-01-2023)
93 | - Added integration with Fathom library to better support Syzygy tablebases
94 | - Added "tbhits" to the search output
95 | - Added "avg_game_phase" parameter to "tunerset" command
96 | - Added "syzygy" and "bindgen" as switchable Cargo features
97 | - Added information about captures, en passants, castles, promotions and checks in perft's output
98 | - Added attackers/defenders cache
99 | - Added killer moves as separate move generator phase
100 | - Removed unnecessary check detection in null move pruning
101 | - Removed redundant abort flag check
102 | - Removed underpromotions in qsearch
103 | - Reduced binary size by removing dependencies and replacing them with custom implementation
104 | - Renamed "test" command to "testset"
105 | - Simplified evaluation by reducing the number of score taperings
106 | - Improved build process
107 | - Improved benchmark output
108 | - Improved allocation of all hashtables, now their size will always be a power of 2 for better performance
109 | - Improved king safety evaluation by taking a number of attacked adjacent fields more serious
110 | - Improved overall performance by a lot of minor refactors and adjustments
111 | - Improved game phase evaluation
112 | - Improved killer heuristic
113 | - Improved history table aging
114 | - Improved reduction formula in null move pruning
115 | - Fixed a few "tunerset" command bugs related to the game phase
116 | - Fixed PGN parser when there were no spaces between dots and moves
117 | - Fixed invalid evaluation of doubled passing pawns
118 | - Fixed invalid cut-offs statistics
119 | - Fixed qsearch failing hard instead of failing soft
120 |
121 | **Strength**: 2850 Elo
122 |
123 | # Version 1.1.1 (14-08-2022)
124 | - Added support for FEN property in PGN parser and "tunerset" command
125 | - Replaced crossbeam package with native scoped threads
126 | - Fixed invalid handling of "isready" UCI command during a search
127 | - Fixed engine crash when trying to search invalid position
128 | - Fixed incorrect version of toolchain used in GitHub Actions
129 |
130 | **No change in Elo strength**
131 |
132 | # Version 1.1.0 (31-07-2022)
133 | - Added support for Syzygy tablebases
134 | - Added support for "MultiPV" UCI option
135 | - Added support for "searchmoves" in "go" UCI command
136 | - Added "hashfull" in the UCI search output
137 | - Added "tunerset" command
138 | - Added "transposition_table_size" and "threads_count" parameters to "test" command
139 | - Added instant move when there is only one possible in the position
140 | - Added new benchmarks
141 | - Added tuner dataset generator
142 | - Added information about the compiler and a list of target features at the startup
143 | - Added diagnostic mode in search functions to gather statistics only if necessary
144 | - Added a simple PGN parser
145 | - Removed "tries_to_confirm" parameter from "test" command
146 | - Removed arr_macro crate from dependencies
147 | - Improved mobility evaluation, now the parameters are defined per piece instead of one value for all
148 | - Improved null move reduction formula, now should be more aggressive
149 | - Improved null move pruning, now it shouldn't be tried for hopeless positions
150 | - Improved make-undo scheme performance
151 | - Improved release script, now it's shorter and more flexible
152 | - Improved error messages and made them more detailed
153 | - Improved repetition draw detection
154 | - Increased late move pruning max depth
155 | - Increased amount of memory allocated for pawn hashtable
156 | - Adjusted evaluation parameters
157 | - Made LMR less aggressive in PV nodes
158 | - Made aging in the transposition table faster and more reliable
159 | - Merged reduction pruning with late move pruning
160 | - Decreased memory usage during tuner work
161 | - Deferred evaluation of evasion mask
162 | - Reduced amount of lazy evaluations
163 | - Reduced amount of locks in the UCI interface
164 | - Removed duplicated search calls in the PVS framework
165 | - Fixed crash when "tuner" command had not enough parameters
166 | - Fixed crash when FEN didn't have information about halfmove clock and move number
167 | - Fixed crash when search in ponder mode was trying to be started in already checkmated position
168 | - Fixed tuner and tester not being able to examine all positions when multithreading is enabled
169 | - Fixed draw detection issue caused by transposition table
170 | - Fixed undefined behaviors and reduced the amount of unsafe code
171 | - Fixed incorrect benchmark statistics
172 | - Fixed a few edge cases in the short algebraic notation parser
173 |
174 | **Strength**: 2800 Elo
175 |
176 | # Version 1.0.1 (05-04-2022)
177 | - Added a new UCI option "Crash Files" (disabled by default)
178 | - Fixed move legality check which in rare cases was leading to engine crashes
179 | - Fixed PV lines being too long due to endless repetitions
180 |
181 | **No change in Elo strength**
182 |
183 | # Version 1.0.0 (02-04-2022)
184 | - Initial release
185 |
186 | **Strength**: 2750 Elo
--------------------------------------------------------------------------------
/src/testing/benchmark.rs:
--------------------------------------------------------------------------------
1 | use crate::cache::pawns::PHTable;
2 | use crate::cache::search::TTable;
3 | use crate::engine::context::SearchContext;
4 | use crate::state::representation::Board;
5 | use std::sync::atomic::AtomicBool;
6 | use std::sync::Arc;
7 | use std::time::SystemTime;
8 |
9 | #[derive(Default)]
10 | pub struct BenchmarkResult {
11 | pub time: f32,
12 |
13 | pub nodes_count: u64,
14 | pub q_nodes_count: u64,
15 | pub leafs_count: u64,
16 | pub q_leafs_count: u64,
17 |
18 | pub beta_cutoffs: u64,
19 | pub q_beta_cutoffs: u64,
20 |
21 | #[cfg(feature = "dev")]
22 | pub perfect_cutoffs: u64,
23 | pub q_perfect_cutoffs: u64,
24 | pub non_perfect_cutoffs: u64,
25 | pub q_non_perfect_cutoffs: u64,
26 |
27 | pub pvs_full_window_searches: u64,
28 | pub pvs_zero_window_searches: u64,
29 | pub pvs_rejected_searches: u64,
30 |
31 | pub snmp_attempts: u64,
32 | pub snmp_accepted: u64,
33 | pub snmp_rejected: u64,
34 |
35 | pub nmp_attempts: u64,
36 | pub nmp_accepted: u64,
37 | pub nmp_rejected: u64,
38 |
39 | pub lmp_accepted: u64,
40 | pub lmp_rejected: u64,
41 |
42 | pub razoring_attempts: u64,
43 | pub razoring_accepted: u64,
44 | pub razoring_rejected: u64,
45 |
46 | pub q_score_pruning_accepted: u64,
47 | pub q_score_pruning_rejected: u64,
48 |
49 | pub q_futility_pruning_accepted: u64,
50 | pub q_futility_pruning_rejected: u64,
51 |
52 | pub tt_added: u64,
53 | pub tt_hits: u64,
54 | pub tt_misses: u64,
55 |
56 | pub tt_legal_hashmoves: u64,
57 | pub tt_illegal_hashmoves: u64,
58 | pub ktable_legal_moves: u64,
59 | pub ktable_illegal_moves: u64,
60 | pub cmtable_legal_moves: u64,
61 | pub cmtable_illegal_moves: u64,
62 |
63 | pub phtable_added: u64,
64 | pub phtable_hits: u64,
65 | pub phtable_misses: u64,
66 |
67 | pub movegen_hash_move_stages: u64,
68 | pub movegen_captures_stages: u64,
69 | pub movegen_killers_stages: u64,
70 | pub movegen_counters_stages: u64,
71 | pub movegen_quiets_stages: u64,
72 |
73 | pub result_hash: u16,
74 | }
75 |
76 | /// Runs a benchmark by performing a fixed-depth search for the built-in list of positions.
77 | pub fn run() -> BenchmarkResult {
78 | const BENCHMARK_POSITIONS: [&str; 30] = [
79 | // Opening
80 | "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
81 | "rnbqkb1r/pp2pppp/2p2n2/3p4/2PP4/4P3/PP3PPP/RNBQKBNR w KQkq - 1 4",
82 | "2kr1b1r/ppp1qppp/2n5/3p1b2/2PPn3/5N2/PP2BPPP/RNBQR1K1 w - - 1 10",
83 | "rnbqk2r/pppp1ppp/8/2b5/2P1P1n1/3pNN2/PP3PPP/R1BQKB1R b KQkq - 1 7",
84 | "r1bq1rk1/pp1nppbp/2p3p1/8/3PB3/2P2N2/PP2QPPP/R1B2RK1 b - - 6 10",
85 | "rnbq1rk1/ppn2ppp/4p3/2p5/3PP3/P1P2P2/2Q1N1PP/R1B1KB1R b KQ - 2 10",
86 | "r1bqkb1r/pp1nnp1p/4p1p1/2ppP2P/3P4/2PB1N2/PP3PP1/RNBQ1RK1 b kq - 0 9",
87 | // Midgame
88 | "5rk1/2b1qp1p/1r2p1pB/1ppnn3/3pN3/1P1P2P1/2P1QPBP/R4RK1 b - - 7 22",
89 | "2k4r/1p3pp1/p2p2n1/2P1p2q/P1P1P3/3PBPP1/2R3Qr/5RK1 b - - 2 22",
90 | "r6k/p1B4p/Pp3rp1/3p4/2nP4/2PQ1PPq/7P/1R3RK1 b - - 0 32",
91 | "r3kb2/p4pp1/2q1p3/1pP1n1N1/3B2nr/1QP1P3/PP1N3P/R2R2K1 w q b6 0 2",
92 | "rn1qkbnr/pp3ppp/4p3/3pPb2/1PpP4/4BN2/P1P1BPPP/RN1QK2R b KQkq b3 0 2",
93 | "rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 2",
94 | "r3k2r/1p1n4/1p1b1p2/2pp2p1/P2P2p1/1P1NP2P/R2BKP2/6R1 w kq - 0 21",
95 | "1k1r4/p1p5/Q2b1pp1/R2P4/b3P2P/P7/1P5P/6K1 w - - 1 34",
96 | "4rrk1/pppb1p1p/3pq3/4nNp1/2P1P3/3BP3/PP1Q1RPP/5RK1 w - - 4 18",
97 | "4rrk1/1q3pbp/1p4p1/p3p3/2P1bn2/2B1QN1P/PP3PP1/3RR1K1 w - - 0 26",
98 | "r5k1/ppq2rpp/2b5/4n3/3QPN2/P5P1/6BP/1R3RK1 b - - 3 22",
99 | "r3kn1r/1p3pbp/p5n1/q2pPRNQ/3P4/1P6/P3N1P1/R1B3K1 b kq - 0 19",
100 | "7r/4b1k1/N1nq1rp1/1p1p1p2/4pP1P/P1P1B2R/1P2Q1P1/3R2K1 b - - 2 39",
101 | "3r4/2q1b1k1/p1np1rp1/1p1Qpp2/5P2/PNP1B1R1/1P4PP/3R2K1 w - - 1 26",
102 | // Endgame
103 | "8/8/6Q1/8/6k1/1P2q3/7p/7K b - - 14 75",
104 | "8/8/4nPk1/8/6pK/8/1R3P1P/2B3r1 b - - 1 54",
105 | "8/7q/5K2/2q5/6k1/8/8/8 b - - 5 60",
106 | "8/8/5k2/bp1r4/4R1P1/3pK3/3N1P2/8 b - - 11 59",
107 | "8/2pkr1Q1/6p1/3P1p2/R6P/P7/1P5P/6K1 w - - 3 42",
108 | "8/2p2k2/3p4/p1rPn3/6pQ/1K6/8/8 w - - 2 119",
109 | "8/7N/8/2n5/p3p1Pp/3k1p1P/5P2/6K1 b - - 1 54",
110 | "8/6p1/1p5p/5PkP/6P1/6K1/p7/N6b b - - 1 48",
111 | "8/1p6/pP3p2/P2k1P2/6Kp/8/8/8 b - - 1 73",
112 | ];
113 |
114 | let mut benchmark_result = BenchmarkResult::default();
115 | let benchmark_time_start = SystemTime::now();
116 |
117 | for (current_position_index, fen) in BENCHMARK_POSITIONS.into_iter().enumerate() {
118 | println!("{}/{}. {}", current_position_index + 1, BENCHMARK_POSITIONS.len(), fen);
119 |
120 | let ttable = Arc::new(TTable::new(64 * 1024 * 1024));
121 | let phtable = Arc::new(PHTable::new(2 * 1024 * 1024));
122 | let abort_flag = Arc::new(AtomicBool::new(false));
123 | let ponder_flag = Arc::new(AtomicBool::new(false));
124 |
125 | let board = Board::new_from_fen(fen).unwrap();
126 | let mut context = SearchContext::new(board, ttable.clone(), phtable.clone(), abort_flag.clone(), ponder_flag.clone());
127 |
128 | context.forced_depth = 16;
129 | context.by_ref().last().unwrap();
130 |
131 | benchmark_result.nodes_count += context.stats.nodes_count;
132 | benchmark_result.q_nodes_count += context.stats.q_nodes_count;
133 | benchmark_result.leafs_count += context.stats.leafs_count;
134 | benchmark_result.q_leafs_count += context.stats.q_leafs_count;
135 |
136 | #[cfg(feature = "dev")]
137 | {
138 | benchmark_result.beta_cutoffs += context.stats.beta_cutoffs;
139 | benchmark_result.q_beta_cutoffs += context.stats.q_beta_cutoffs;
140 |
141 | benchmark_result.perfect_cutoffs += context.stats.perfect_cutoffs;
142 | benchmark_result.q_perfect_cutoffs += context.stats.q_perfect_cutoffs;
143 | benchmark_result.non_perfect_cutoffs += context.stats.non_perfect_cutoffs;
144 | benchmark_result.q_non_perfect_cutoffs += context.stats.q_non_perfect_cutoffs;
145 |
146 | benchmark_result.pvs_full_window_searches += context.stats.pvs_full_window_searches;
147 | benchmark_result.pvs_zero_window_searches += context.stats.pvs_zero_window_searches;
148 | benchmark_result.pvs_rejected_searches += context.stats.pvs_rejected_searches;
149 |
150 | benchmark_result.snmp_attempts += context.stats.snmp_attempts;
151 | benchmark_result.snmp_accepted += context.stats.snmp_accepted;
152 | benchmark_result.snmp_rejected += context.stats.snmp_rejected;
153 |
154 | benchmark_result.nmp_attempts += context.stats.nmp_attempts;
155 | benchmark_result.nmp_accepted += context.stats.nmp_accepted;
156 | benchmark_result.nmp_rejected += context.stats.nmp_rejected;
157 |
158 | benchmark_result.lmp_accepted += context.stats.lmp_accepted;
159 | benchmark_result.lmp_rejected += context.stats.lmp_rejected;
160 |
161 | benchmark_result.razoring_attempts += context.stats.razoring_attempts;
162 | benchmark_result.razoring_accepted += context.stats.razoring_accepted;
163 | benchmark_result.razoring_rejected += context.stats.razoring_rejected;
164 |
165 | benchmark_result.q_score_pruning_accepted += context.stats.q_score_pruning_accepted;
166 | benchmark_result.q_score_pruning_rejected += context.stats.q_score_pruning_rejected;
167 |
168 | benchmark_result.q_futility_pruning_accepted += context.stats.q_futility_pruning_accepted;
169 | benchmark_result.q_futility_pruning_rejected += context.stats.q_futility_pruning_rejected;
170 |
171 | benchmark_result.tt_added += context.stats.tt_added;
172 | benchmark_result.tt_hits += context.stats.tt_hits;
173 | benchmark_result.tt_misses += context.stats.tt_misses;
174 |
175 | benchmark_result.tt_legal_hashmoves += context.stats.tt_legal_hashmoves;
176 | benchmark_result.tt_illegal_hashmoves += context.stats.tt_illegal_hashmoves;
177 | benchmark_result.ktable_legal_moves += context.stats.ktable_legal_moves;
178 | benchmark_result.ktable_illegal_moves += context.stats.ktable_illegal_moves;
179 | benchmark_result.cmtable_legal_moves += context.stats.cmtable_legal_moves;
180 | benchmark_result.cmtable_illegal_moves += context.stats.cmtable_illegal_moves;
181 |
182 | benchmark_result.phtable_added += context.stats.phtable_added;
183 | benchmark_result.phtable_hits += context.stats.phtable_hits;
184 | benchmark_result.phtable_misses += context.stats.phtable_misses;
185 |
186 | benchmark_result.movegen_hash_move_stages += context.stats.movegen_hash_move_stages;
187 | benchmark_result.movegen_captures_stages += context.stats.movegen_captures_stages;
188 | benchmark_result.movegen_killers_stages += context.stats.movegen_killers_stages;
189 | benchmark_result.movegen_counters_stages += context.stats.movegen_counters_stages;
190 | benchmark_result.movegen_quiets_stages += context.stats.movegen_quiets_stages;
191 | }
192 |
193 | benchmark_result.result_hash ^= context.lines[0].pv_line[0].data;
194 | }
195 |
196 | benchmark_result.time = (benchmark_time_start.elapsed().unwrap().as_millis() as f32) / 1000.0;
197 | benchmark_result
198 | }
199 |
--------------------------------------------------------------------------------