├── 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 | --------------------------------------------------------------------------------