diff --git a/Cargo.lock b/Cargo.lock
index 40207c0..9ab5ecf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -189,15 +189,26 @@ dependencies = [
"libloading",
]
+[[package]]
+name = "deranged"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
+dependencies = [
+ "powerfmt",
+]
+
[[package]]
name = "engine"
version = "1.0.1"
dependencies = [
"chrono",
+ "log",
"queues",
"serde",
"serde_derive",
"shakmaty",
+ "simplelog",
"toml",
"wasm-bindgen",
"web-sys",
@@ -279,6 +290,12 @@ dependencies = [
"hashbrown",
]
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
[[package]]
name = "js-sys"
version = "0.3.77"
@@ -350,6 +367,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -360,12 +383,27 @@ dependencies = [
"libm",
]
+[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@@ -532,6 +570,17 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+[[package]]
+name = "simplelog"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0"
+dependencies = [
+ "log",
+ "termcolor",
+ "time",
+]
+
[[package]]
name = "strsim"
version = "0.8.0"
@@ -584,6 +633,15 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
[[package]]
name = "textwrap"
version = "0.11.0"
@@ -613,6 +671,39 @@ dependencies = [
"syn 2.0.100",
]
+[[package]]
+name = "time"
+version = "0.3.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
+dependencies = [
+ "deranged",
+ "itoa",
+ "libc",
+ "num-conv",
+ "num_threads",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
+
+[[package]]
+name = "time-macros"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
[[package]]
name = "toml"
version = "0.8.20"
@@ -784,6 +875,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys",
+]
+
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@@ -849,6 +949,15 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
[[package]]
name = "windows-targets"
version = "0.52.6"
diff --git a/Makefile b/Makefile
index bd49f85..e08bafa 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
-# Build executables for Carp releases. Base rule is reserved for OpenBench
EXE := Pluto_1.0.1
LXE := Pluto_1.0.1
_THIS := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
TMPDIR := $(_THIS)/tmp
+HCE := true
ifeq ($(OS),Windows_NT)
EXT := .exe
@@ -16,45 +16,16 @@ else
VER := linux
endif
+ifeq ($(HCE),true)
+ FEATURES := tuning,log,classical
+else
+ FEATURES := tuning,log
+endif
+
NAME := $(EXE)$(EXT)
rule:
- cargo rustc -r -p engine --bins -- -C target-cpu=native --emit link=$(NAME)
+ cargo rustc -r -p engine --bins --features $(FEATURES) -- -C target-cpu=native --emit link=$(NAME)
tmp-dir:
mkdir -p $(TMPDIR)
-
-x86-64 x86-64-v2 x86-64-v3 x86-64-v4 native: tmp-dir
- cargo rustc -r -p engine --bins -- -C target-cpu=$@ -C profile-generate=$(TMPDIR) --emit link=$(LXE)-$(VER)-$@$(EXT)
- ./$(LXE)-$(VER)-$@$(EXT) bench 16
- llvm-profdata merge -o $(TMPDIR)/merged.profdata $(TMPDIR)
-
- cargo rustc -r -p engine --bins -- -C target-feature=+crt-static -C target-cpu=$@ -C profile-use=$(TMPDIR)/merged.profdata --emit link=$(LXE)-$(VER)-$@$(EXT)
-
- rm -rf $(TMPDIR)/*
- rm -f *.pdb
-
-syzygy: tmp-dir
- cargo rustc -r -p engine --bins --features syzygy -- -C target-cpu=native -C profile-generate=$(TMPDIR) --emit link=$(LXE)-$(VER)$(EXT)
- ./$(LXE)-$(VER)$(EXT) bench 16
- llvm-profdata merge -o $(TMPDIR)/merged.profdata $(TMPDIR)
-
- cargo rustc -r -p engine --bins --features syzygy -- -C target-feature=+crt-static -C target-cpu=native -C profile-use=$(TMPDIR)/merged.profdata --emit link=$(LXE)-$(VER)$(EXT)
-
- rm -rf $(TMPDIR)/*
- rm -f *.pdb
-
-datagen: tmp-dir
- cargo rustc -r -p tools -- -C target-cpu=native -C profile-generate=$(TMPDIR) --emit link=datagen$(EXT)
- ./datagen$(EXT) datagen -g 256 -t 32 -n 5000
- ./datagen$(EXT) datagen -g 256 -t 32 -d 8
- llvm-profdata merge -o $(TMPDIR)/merged.profdata $(TMPDIR)
-
- cargo rustc -r -p tools -- -C target-cpu=native -C profile-use=$(TMPDIR)/merged.profdata --emit link=datagen$(EXT)
-
- rm -rf $(TMPDIR)
- rm -rf $(_THIS)/data
- rm -f *.pdb
-
-release: x86-64 x86-64-v2 x86-64-v3 x86-64-v4
- rm -rf $(TMPDIR)
diff --git a/README.md b/README.md
index b6359bd..14c5da7 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,15 @@
# Pluto Chess Engine
-
-by [Lxdovic](https://github.com/Lxdovic)
+
+
+
+
+
+
+ [![License][license-badge]][license-link]
+ [![Release][release-badge]][release-link]
+ [![Commits][commits-badge]][commits-link]
+
## Table of Contents
@@ -19,9 +27,9 @@ by [Lxdovic](https://github.com/Lxdovic)
## Strength
-| Version | Date | Estimated ELO | CCRL |
-| -------------- | ----------- | ------------- | ---- |
-| 1.0.0 | 11 May 2025 | 2870 | 2872 |
+| Version | Date | Estimated ELO | CCRL 40/15 | CCRL Blitz 2+1 |
+| -------------- | ----------- | ------------- | ---- | ---- |
+| 1.0.0 | 11 May 2025 | 2870 | 2879 | 2976 |
## Testing
@@ -54,15 +62,27 @@ Pluto is tested with OpenBench, a distributed testing framework for UCI chess en
- **Killer Moves**
- **Transposition Tables**
- Evaluation:
- Pluto adopted Efficiently Updatable Neural Networks for its evaluation function quite early in development. Earlier versions were using Simple Eval/Pesto Eval
+ - **HCE** (classical build, see how to build it in #Building)
- **NNUE (768->512)x2->1** Trained using [Bullet](https://github.com/jw1912/bullet) and Stockfish data.
## Building
-To build the engine, clone the repository and run the following command in your terminal:
+To build the engine, clone the repository and use one of the following options:
+
+### NNUE
+
+This will build Pluto NNUE
```bash
-make
+cargo build --release --bin engine
+```
+
+### Classical
+
+This allows you to build Pluto HCE
+
+```bash
+cargo build --release --bin engine --features classical
```
## Contributors
@@ -81,3 +101,10 @@ Pluto was built using these resources and tools
- [Bullet](https://github.com/jw1912/bullet) -> great tool for building NNUEs
- [Stockfish](https://stockfishchess.org/) -> some implementation examples and ideas
+[license-badge]: https://img.shields.io/github/license/Lxdovic/Pluto?style=for-the-badge
+[release-badge]: https://img.shields.io/github/v/release/Lxdovic/Pluto?style=for-the-badge
+[commits-badge]: https://img.shields.io/github/commits-since/Lxdovic/Pluto/latest?style=for-the-badge
+
+[license-link]: https://github.com/Lxdovic/Pluto/blob/main/LICENSE
+[release-link]: https://github.com/Lxdovic/Pluto/releases/latest
+[commits-link]: https://github.com/Lxdovic/Pluto/commits/dev
diff --git a/engine/Cargo.toml b/engine/Cargo.toml
index 48ded00..dd56d6e 100644
--- a/engine/Cargo.toml
+++ b/engine/Cargo.toml
@@ -15,6 +15,12 @@ path = "src/lib.rs"
name = "engine"
path = "src/main.rs"
+[features]
+default = []
+tuning = []
+classical = []
+log = []
+
[dependencies]
queues = "1.1.0"
shakmaty = "0.27.2"
@@ -26,3 +32,5 @@ chrono = "0.4.40"
web-sys = { version = "0.3.77", features = [
'Worker',
] }
+log = "0.4"
+simplelog = "0.12"
diff --git a/engine/src/build.rs b/engine/src/build.rs
new file mode 100644
index 0000000..f50f482
--- /dev/null
+++ b/engine/src/build.rs
@@ -0,0 +1,14 @@
+fn main() {
+ if std::env::var("CARGO_FEATURE_TUNING").is_ok() {
+ println!("cargo:rustc-cfg=feature=\"tuning\"");
+ }
+
+ // pluto classical means no use of NNUE, HCE is used instead
+ if std::env::var("CARGO_FEATURE_CLASSICAL").is_ok() {
+ println!("cargo:rustc-cfg=feature=\"classical\"");
+ }
+
+ if std::env::var("CARGO_FEATURE_CLASSICAL").is_ok() {
+ println!("cargo:rustc-cfg=feature=\"log\"");
+ }
+}
diff --git a/engine/src/config.rs b/engine/src/config.rs
index e826f54..e87666d 100644
--- a/engine/src/config.rs
+++ b/engine/src/config.rs
@@ -15,6 +15,7 @@
along with this program. If not, see .
*/
+use crate::logger::Logger;
use std::fmt::{self};
#[derive(Debug)]
@@ -44,334 +45,134 @@ pub struct OptionDescriptor {
pub value: T,
pub min: T,
pub max: T,
+ pub tunable: bool,
}
-impl OptionDescriptor {
- pub fn fmt_spsa(&self) -> String {
- format!(
- "{}, int, {}, {}, {}, {}, {}",
- self.name, self.value, self.min, self.max, 2.25, 0.002
- )
- }
-}
-impl OptionDescriptor {
- pub fn fmt_spsa(&self) -> String {
- format!(
- "{}, int, {}, {}, {}, {}, {}",
- self.name, self.value, self.min, self.max, 2.25, 0.002
- )
- }
-}
-impl OptionDescriptor {
- pub fn fmt_spsa(&self) -> String {
- format!(
- "{}, int, {}, {}, {}, {}, {}",
- self.name, self.value, self.min, self.max, 2.25, 0.002
- )
- }
-}
-impl OptionDescriptor {
- pub fn fmt_spsa(&self) -> String {
- format!(
- "{}, int, {}, {}, {}, {}, {}",
- self.name, self.value, self.min, self.max, 2.25, 0.002
- )
- }
-}
-impl OptionDescriptor {
- pub fn fmt_spsa(&self) -> String {
- format!(
- "{}, int, {}, {}, {}, {}, {}",
- self.name, self.value, self.min, self.max, 2.25, 0.002
- )
- }
-}
-impl OptionDescriptor {
- pub fn fmt_spsa(&self) -> String {
- format!(
- "{}, float, {}, {}, {}, {}, {}",
- self.name, self.value, self.min, self.max, 2.25, 0.002
- )
- }
+#[cfg(feature = "tuning")]
+macro_rules! impl_fmt_spsa {
+ ($($t:ty => $ty_str:expr),*) => {
+ $(impl OptionDescriptor<$t> {
+ pub fn fmt_spsa(&self) -> String {
+ format!(
+ "{}, {}, {}, {}, {}, {}, {}",
+ self.name, $ty_str, self.value, self.min, self.max, 2.25, 0.002
+ )
+ }
+ })*
+ };
}
-impl fmt::Display for OptionDescriptor {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(
- f,
- "option name {} type {} default {} min {} max {}",
- self.name, self.kind, self.value, self.min, self.max
- )
- }
-}
-impl fmt::Display for OptionDescriptor {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(
- f,
- "option name {} type {} default {} min {} max {}",
- self.name, self.kind, self.value, self.min, self.max
- )
- }
-}
-impl fmt::Display for OptionDescriptor {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(
- f,
- "option name {} type {} default {} min {} max {}",
- self.name, self.kind, self.value, self.min, self.max
- )
- }
-}
-impl fmt::Display for OptionDescriptor {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(
- f,
- "option name {} type {} default {} min {} max {}",
- self.name, self.kind, self.value, self.min, self.max
- )
- }
-}
-impl fmt::Display for OptionDescriptor {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(
- f,
- "option name {} type {} default {} min {} max {}",
- self.name, self.kind, self.value, self.min, self.max
- )
- }
-}
-impl fmt::Display for OptionDescriptor {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(
- f,
- "option name {} type {} default {} min {} max {}",
- self.name, self.kind, self.value, self.min, self.max
- )
- }
-}
+#[cfg(feature = "tuning")]
+impl_fmt_spsa!(
+ i32 => "int",
+ u8 => "int",
+ usize => "int",
+ i64 => "int",
+ u64 => "int",
+ f64 => "float"
+);
-pub struct Config {
- pub move_overhead: OptionDescriptor,
- pub threads: OptionDescriptor,
- pub hash: OptionDescriptor,
- pub qsearch_depth: OptionDescriptor,
- pub rfp_depth: OptionDescriptor,
- pub rfp_base_margin: OptionDescriptor,
- pub rfp_reduction_improving: OptionDescriptor,
- pub fp_depth_margin: OptionDescriptor,
- pub fp_base_margin: OptionDescriptor,
- pub fp_margin_depth_factor: OptionDescriptor,
- pub nmp_depth: OptionDescriptor,
- pub nmp_margin: OptionDescriptor,
- pub nmp_divisor: OptionDescriptor,
- pub nmp_divisor_improving: OptionDescriptor,
- pub lmp_move_margin: OptionDescriptor,
- pub lmp_depth_factor: OptionDescriptor,
- pub lmr_depth: OptionDescriptor,
- pub lmr_move_margin: OptionDescriptor,
- pub lmr_quiet_margin: OptionDescriptor,
- pub lmr_quiet_divisor: OptionDescriptor,
- pub lmr_base_margin: OptionDescriptor,
- pub lmr_base_divisor: OptionDescriptor,
- pub mo_tt_entry_value: OptionDescriptor,
- pub mo_capture_value: OptionDescriptor,
- pub mo_killer_value: OptionDescriptor,
- pub tc_time_divisor: OptionDescriptor,
- pub tc_elapsed_factor: OptionDescriptor,
+macro_rules! impl_fmt_display {
+ ($($t:ty),*) => {
+ $(impl fmt::Display for OptionDescriptor<$t> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "option name {} type {} default {} min {} max {}",
+ self.name, self.kind, self.value, self.min, self.max
+ )
+ }
+ })*
+ };
}
-impl Config {
- pub fn default() -> Self {
- Self {
- move_overhead: OptionDescriptor {
- name: "MoveOverhead",
- kind: OptionKind::Spin,
- value: 0,
- min: 0,
- max: 10000,
- },
- threads: OptionDescriptor {
- name: "Threads",
- kind: OptionKind::Spin,
- value: 1,
- min: 1,
- max: 1,
- },
- hash: OptionDescriptor {
- name: "Hash",
- kind: OptionKind::Spin,
- value: 255,
- min: 1,
- max: 1024,
- },
- qsearch_depth: OptionDescriptor {
- name: "QSearchDepth",
- kind: OptionKind::Spin,
- value: 17,
- min: 1,
- max: 20,
- },
- rfp_depth: OptionDescriptor {
- name: "RFPDepth",
- kind: OptionKind::Spin,
- value: 11,
- min: 1,
- max: 20,
- },
- rfp_base_margin: OptionDescriptor {
- name: "RFPBaseMargin",
- kind: OptionKind::Spin,
- value: 57,
- min: 1,
- max: 200,
- },
- rfp_reduction_improving: OptionDescriptor {
- name: "RFPReductionImproving",
- kind: OptionKind::Spin,
- value: 24,
- min: 1,
- max: 200,
- },
- fp_depth_margin: OptionDescriptor {
- name: "FPDepthMargin",
- kind: OptionKind::Spin,
- value: 5,
- min: 1,
- max: 20,
- },
- fp_base_margin: OptionDescriptor {
- name: "FPBaseMargin",
- kind: OptionKind::Spin,
- value: 40,
- min: 1,
- max: 200,
- },
- fp_margin_depth_factor: OptionDescriptor {
- name: "FPMarginDepthFactor",
- kind: OptionKind::Spin,
- value: 35,
- min: 1,
- max: 200,
- },
- nmp_depth: OptionDescriptor {
- name: "NMPDepth",
- kind: OptionKind::Spin,
- value: 5,
- min: 1,
- max: 20,
- },
- nmp_margin: OptionDescriptor {
- name: "NMPMargin",
- kind: OptionKind::Spin,
- value: 7,
- min: 1,
- max: 20,
- },
- nmp_divisor: OptionDescriptor {
- name: "NMPDivisor",
- kind: OptionKind::Spin,
- value: 2,
- min: 1,
- max: 20,
- },
- nmp_divisor_improving: OptionDescriptor {
- name: "NMPDivisorImproving",
- kind: OptionKind::Spin,
- value: 10,
- min: 1,
- max: 20,
- },
- lmp_move_margin: OptionDescriptor {
- name: "LMPMoveMargin",
- kind: OptionKind::Spin,
- value: 5,
- min: 1,
- max: 20,
- },
- lmp_depth_factor: OptionDescriptor {
- name: "LMPDepthFactor",
- kind: OptionKind::Spin,
- value: 7,
- min: 1,
- max: 20,
- },
- lmr_depth: OptionDescriptor {
- name: "LMRDepth",
- kind: OptionKind::Spin,
- value: 15,
- min: 1,
- max: 20,
- },
- lmr_move_margin: OptionDescriptor {
- name: "LMRMoveMargin",
- kind: OptionKind::Spin,
- value: 5,
- min: 1,
- max: 20,
- },
- lmr_quiet_margin: OptionDescriptor {
- name: "LMRQuietMargin",
- kind: OptionKind::String,
- value: 2.59,
- min: 0.0,
- max: 10.0,
- },
- lmr_quiet_divisor: OptionDescriptor {
- name: "LMRQuietDivisor",
- kind: OptionKind::String,
- value: 2.02,
- min: 1.0,
- max: 10.0,
- },
- lmr_base_margin: OptionDescriptor {
- name: "LMRBaseMargin",
- kind: OptionKind::String,
- value: 1.31,
- min: 0.0,
- max: 10.0,
- },
- lmr_base_divisor: OptionDescriptor {
- name: "LMRBaseDivisor",
- kind: OptionKind::String,
- value: 3.28,
- min: 1.0,
- max: 10.0,
- },
- mo_tt_entry_value: OptionDescriptor {
- name: "MOTTEntryValue",
- kind: OptionKind::Spin,
- value: 229,
- min: 1,
- max: 500,
- },
- mo_capture_value: OptionDescriptor {
- name: "MOCaptureValue",
- kind: OptionKind::Spin,
- value: 55,
- min: 0,
- max: 500,
- },
- mo_killer_value: OptionDescriptor {
- name: "MOKillerValue",
- kind: OptionKind::Spin,
- value: 78,
- min: 0,
- max: 500,
- },
- tc_time_divisor: OptionDescriptor {
- name: "TCTimeDivisor",
- kind: OptionKind::Spin,
- value: 2,
- min: 2,
- max: 100,
- },
- tc_elapsed_factor: OptionDescriptor {
- name: "TCElapsedFactor",
- kind: OptionKind::Spin,
- value: 8,
- min: 1,
- max: 10,
- },
+impl_fmt_display!(i32, u8, usize, i64, u64, f64);
+
+macro_rules! make_config {
+ ($($field:ident: $t:ty = ($name:expr, $kind:expr, $val:expr, $min:expr, $max:expr, $tunable:expr);)*) => {
+ pub struct Config {
+ $(pub $field: OptionDescriptor<$t>,)*
}
- }
+
+ impl Config {
+ pub fn default() -> Self {
+ Self {
+ $(
+ $field: OptionDescriptor {
+ name: $name,
+ kind: $kind,
+ value: $val,
+ min: $min,
+ max: $max,
+ tunable: $tunable,
+ },
+ )*
+ }
+ }
+
+ pub fn set(&mut self, name: &str, val: &str) {
+ match name {
+ $(
+ $name => {
+ if !cfg!(feature = "tuning") && self.$field.tunable {
+ Logger::log(&format!("info string cannot modify tunable option in non tunable build"))
+ }
+ else if let Ok(parsed) = val.parse::<$t>() {
+ self.$field.value = parsed;
+ } else {
+ Logger::log(&format!("info string invalid value for {}: {}", $name, val));
+ }
+ },
+ )*
+ _ => Logger::log(&format!("info string unknown option: {}", name)),
+ }
+ }
+
+ #[cfg(feature = "tuning")]
+ pub fn all_spsa(&self) -> Vec {
+ vec![
+ $(self.$field.fmt_spsa(),)*
+ ]
+ }
+
+ pub fn print_uci_options(&self) {
+ $(
+
+ if cfg!(feature = "tuning") || !self.$field.tunable {
+ Logger::log(format!("{}", self.$field).as_str());
+ }
+ )*
+ }
+ }
+ };
+}
+
+make_config! {
+ move_overhead: usize = ("MoveOverhead", OptionKind::Spin, 0, 0, 10000, false);
+ threads: u8 = ("Threads", OptionKind::Spin, 1, 1, 1, false);
+ hash: usize = ("Hash", OptionKind::Spin, 255, 1, 1024, false);
+ qsearch_depth: u8 = ("QSearchDepth", OptionKind::Spin, 17, 1, 20, true);
+ rfp_depth: u8 = ("RFPDepth", OptionKind::Spin, 11, 1, 20, true);
+ rfp_base_margin: i32 = ("RFPBaseMargin", OptionKind::Spin, 57, 1, 200, true);
+ rfp_reduction_improving: i32 = ("RFPReductionImproving", OptionKind::Spin, 24, 1, 200, true);
+ fp_depth_margin: u8 = ("FPDepthMargin", OptionKind::Spin, 5, 1, 20, true);
+ fp_base_margin: i32 = ("FPBaseMargin", OptionKind::Spin, 40, 1, 200, true);
+ fp_margin_depth_factor: i32 = ("FPMarginDepthFactor", OptionKind::Spin, 35, 1, 200, true);
+ nmp_depth: u8 = ("NMPDepth", OptionKind::Spin, 5, 1, 20, true);
+ nmp_margin: u8 = ("NMPMargin", OptionKind::Spin, 7, 1, 20, true);
+ nmp_divisor: u8 = ("NMPDivisor",OptionKind::Spin, 2, 1, 20, true);
+ nmp_divisor_improving: u8 = ("NMPDivisorImproving", OptionKind::Spin, 10, 1, 20, true);
+ lmp_move_margin: usize = ("LMPMoveMargin", OptionKind::Spin, 5, 1, 20, true);
+ lmp_depth_factor: u8 = ("LMPDepthFactor", OptionKind::Spin, 7, 1, 20, true);
+ lmr_depth: u8 = ("LMRDepth", OptionKind::Spin, 15, 1, 20, true);
+ lmr_move_margin: usize = ("LMRMoveMargin", OptionKind::Spin, 5, 1, 20, true);
+ lmr_quiet_margin: f64 = ("LMRQuietMargin", OptionKind::String, 2.59, 0.0, 10.0, true);
+ lmr_quiet_divisor: f64 = ("LMRQuietDivisor", OptionKind::String, 2.02, 1.0, 10.0, true);
+ lmr_base_margin: f64 = ("LMRBaseMargin", OptionKind::String, 1.31, 0.0, 10.0, true);
+ lmr_base_divisor: f64 = ("LMRBaseDivisor", OptionKind::String, 3.28, 1.0, 10.0, true);
+ mo_tt_entry_value: i32 = ("MOTTEntryValue", OptionKind::Spin, 229, 1, 500, true);
+ mo_capture_value: i32 = ("MOCaptureValue", OptionKind::Spin, 55, 0, 500, true);
+ mo_killer_value: i32 = ("MOKillerValue", OptionKind::Spin, 78, 0, 500, true);
+ tc_time_divisor: u64 = ("TCTimeDivisor", OptionKind::Spin, 2, 2, 100, true);
+ tc_elapsed_factor: i64 = ("TCElapsedFactor", OptionKind::Spin, 8, 1, 10, true);
}
diff --git a/engine/src/eval.rs b/engine/src/eval.rs
index c8e8d1d..ec3e4c4 100644
--- a/engine/src/eval.rs
+++ b/engine/src/eval.rs
@@ -16,9 +16,25 @@
*/
/// Position evaluation module containing piece-square tables and evaluation functions.
+#[cfg(not(feature = "classical"))]
use crate::nnue::{NNUEState, NNUE};
+use crate::packing::s;
+#[cfg(feature = "classical")]
+use crate::packing::{extract_eg, extract_mg};
+#[cfg(feature = "classical")]
+use shakmaty::{attacks, Bitboard, Piece, Role, Square};
use shakmaty::{Chess, Color, Position};
+#[cfg(feature = "classical")]
+#[derive(Default)]
+pub struct EvalState {
+ phase: i32,
+ eval: [i32; 2],
+}
+
+#[cfg(feature = "classical")]
+type EvalRoleFn = fn(&Chess, Square, Piece) -> i32;
+
pub struct Eval {}
impl Eval {
@@ -32,13 +48,416 @@ impl Eval {
false
}
+ #[cfg(feature = "classical")]
+ fn eval_pawn(pos: &Chess, sq: Square, piece: Piece) -> i32 {
+ let doubled = Self::doubled(pos, sq, piece);
+ let isolated = Self::isolated(pos, sq, piece);
+ let passed = Self::passed(pos, sq, piece);
+
+ doubled + isolated + passed
+ }
+
+ #[cfg(feature = "classical")]
+ fn eval_knight(pos: &Chess, sq: Square, piece: Piece) -> i32 {
+ Self::mobility(pos, sq, piece)
+ }
+
+ #[cfg(feature = "classical")]
+ fn eval_bishop(pos: &Chess, sq: Square, piece: Piece) -> i32 {
+ Self::mobility(pos, sq, piece)
+ }
+
+ #[cfg(feature = "classical")]
+ fn eval_rook(pos: &Chess, sq: Square, piece: Piece) -> i32 {
+ let mobility = Self::mobility(pos, sq, piece);
+ let files = Self::rook_files(pos, sq);
+
+ mobility + files
+ }
+
+ #[cfg(feature = "classical")]
+ fn eval_queen(pos: &Chess, sq: Square, piece: Piece) -> i32 {
+ Self::mobility(pos, sq, piece)
+ }
+
+ #[cfg(feature = "classical")]
+ fn eval_king(pos: &Chess, sq: Square, piece: Piece) -> i32 {
+ Self::king_shield(pos, sq, piece)
+ }
+
+ #[cfg(feature = "classical")]
+ fn mobility(pos: &Chess, sq: Square, piece: Piece) -> i32 {
+ let attacks = attacks::attacks(sq, piece, pos.board().occupied());
+
+ MOBILITY[attacks.count()]
+ }
+
+ #[cfg(feature = "classical")]
+ fn doubled(pos: &Chess, sq: Square, piece: Piece) -> i32 {
+ let pawns = pos.board().by_piece(piece);
+ let file = FILES_TABLE[sq.file() as usize];
+ let count = pawns.intersect(file).count();
+
+ DOUBLED[count]
+ }
+
+ #[cfg(feature = "classical")]
+ fn isolated(pos: &Chess, sq: Square, piece: Piece) -> i32 {
+ let isolation = ADJACENT_FILES_TABLE[sq.file() as usize];
+ let our_pawns = pos.board().by_piece(piece);
+
+ if isolation.intersect(our_pawns).count() == 0 {
+ return ISOLATED;
+ }
+
+ 0
+ }
+
+ #[cfg(feature = "classical")]
+ fn passed(pos: &Chess, sq: Square, piece: Piece) -> i32 {
+ let isolation = ADJACENT_AND_FILE_TABLE[sq.file() as usize];
+ let their_pawns = pos.board().by_piece(Piece {
+ role: piece.role,
+ color: piece.color.other(),
+ });
+
+ if isolation.intersect(their_pawns).count() == 0 {
+ return PASSED;
+ }
+
+ 0
+ }
+
+ #[cfg(feature = "classical")]
+ fn king_shield(pos: &Chess, sq: Square, piece: Piece) -> i32 {
+ let our_pawns = pos.board().by_piece(Piece {
+ role: Role::Pawn,
+ color: piece.color,
+ });
+
+ let index = attacks::attacks(sq, piece, Bitboard(0))
+ .intersect(our_pawns)
+ .count();
+
+ KING_SHIELD[index]
+ }
+
+ #[cfg(feature = "classical")]
+ fn bishop_pair(pos: &Chess, state: &mut EvalState) {
+ let bishops = pos.board().bishops();
+ let white_bishops = pos.board().white().intersect(bishops);
+ let black_bishops = pos.board().black().intersect(bishops);
+
+ if white_bishops.count() >= 2 {
+ state.eval[Color::White as usize] += BISHOP_PAIR
+ }
+
+ if black_bishops.count() >= 2 {
+ state.eval[Color::Black as usize] += BISHOP_PAIR
+ }
+ }
+
+ #[cfg(feature = "classical")]
+ fn rook_files(pos: &Chess, sq: Square) -> i32 {
+ let board = pos.board();
+ let us = pos.turn();
+ let file = FILES_TABLE[sq.file() as usize];
+ let all_pawns = board.pawns();
+
+ if file.intersect(all_pawns).count() == 0 {
+ return ROOK_FILES[0];
+ }
+
+ let our_pawns = board.by_piece(Piece {
+ role: Role::Pawn,
+ color: us,
+ });
+
+ if file.intersect(our_pawns).count() == 0 {
+ return ROOK_FILES[1];
+ }
+
+ 0
+ }
+ #[cfg(feature = "classical")]
+ fn eval_piece(pos: &Chess, sq: Square, piece: Piece, state: &mut EvalState) {
+ let role_index = piece.role as usize - 1;
+ let piece_index = role_index * 2 + (!piece.color as usize);
+ let square_index = sq as usize;
+ let piece_score = EVAL_ROLES[role_index](pos, sq, piece);
+
+ state.eval[piece.color as usize] += TABLE[piece_index][square_index] + piece_score;
+ state.phase += GAME_PHASES[piece_index];
+ }
+
+ #[cfg(feature = "classical")]
+ fn tempo(pos: &Chess, state: &mut EvalState) {
+ state.eval[pos.turn() as usize] += TEMPO;
+ }
+
+ #[cfg(feature = "classical")]
+ pub fn eval(pos: &Chess) -> i32 {
+ let mut state = EvalState::default();
+
+ for (sq, piece) in pos.board() {
+ Self::eval_piece(pos, sq, piece, &mut state);
+ }
+
+ Self::tempo(pos, &mut state);
+ Self::bishop_pair(pos, &mut state);
+
+ let score = state.eval[Color::White as usize] - state.eval[Color::Black as usize];
+ let mg = extract_mg(score);
+ let eg = extract_eg(score);
+
+ (mg * state.phase + eg * (24 - state.phase)) / 24
+ * if pos.turn() == Color::White { 1 } else { -1 }
+ }
+
+ #[cfg(feature = "classical")]
+ pub const fn init_piece_table() -> [[i32; 64]; 12] {
+ let mut eg_table = [[0; 64]; 12];
+
+ let mut p = 0;
+ let mut pc = 0;
+ let mut sq = 0;
+
+ while p < 6 {
+ while sq < 64 {
+ eg_table[pc][sq] = PIECE_VALUES[p] + PESTO_TABLE[p][sq ^ 56];
+ eg_table[pc + 1][sq] = PIECE_VALUES[p] + PESTO_TABLE[p][sq];
+
+ sq += 1;
+ }
+
+ sq = 0;
+ p += 1;
+ pc += 2;
+ }
+
+ eg_table
+ }
+
+ #[cfg(not(feature = "classical"))]
pub fn nnue_eval(state: &NNUEState, pos: &Chess) -> i32 {
#[rustfmt::skip]
let (us, them) = match pos.turn() {
Color::White => (state.stack[state.current].white, state.stack[state.current].black),
- Color::Black => ( state.stack[state.current].black, state.stack[state.current].white),
+ Color::Black => (state.stack[state.current].black, state.stack[state.current].white),
};
NNUE.evaluate(&us, &them)
}
}
+
+#[cfg(feature = "classical")]
+const TABLE: [[i32; 64]; 12] = Eval::init_piece_table();
+#[cfg(feature = "classical")]
+const PESTO_TABLE: [[i32; 64]; 6] = [PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING];
+#[rustfmt::skip]
+pub const PIECE_VALUES: [i32; 6] = [s(82, 94), s(337, 281), s(365, 297), s(447, 512), s(1025, 936), s(0, 0)];
+#[cfg(feature = "classical")]
+const TEMPO: i32 = s(28, 0);
+#[cfg(feature = "classical")]
+const GAME_PHASES: [i32; 12] = [0, 0, 1, 1, 1, 1, 2, 2, 4, 4, 0, 0];
+#[cfg(feature = "classical")]
+const EVAL_ROLES: [EvalRoleFn; 6] = [
+ Eval::eval_pawn,
+ Eval::eval_knight,
+ Eval::eval_bishop,
+ Eval::eval_rook,
+ Eval::eval_queen,
+ Eval::eval_king,
+];
+
+#[cfg(feature = "classical")]
+const PASSED: i32 = s(10, 10);
+#[cfg(feature = "classical")]
+const ISOLATED: i32 = s(-10, -10);
+#[cfg(feature = "classical")]
+#[rustfmt::skip]
+const KING_SHIELD: [i32; 9] = [s(0, 0), s(1, 1), s(2, 2), s(3, 3), s(4, 4), s(5, 5), s(6, 6), s(7, 7), s(8, 8)];
+#[cfg(feature = "classical")]
+const ROOK_FILES: [i32; 2] = [s(20, 0), s(10, 0)];
+#[cfg(feature = "classical")]
+const BISHOP_PAIR: i32 = s(10, 40);
+
+#[cfg(feature = "classical")]
+const DOUBLED: [i32; 9] = [
+ s(5, 5),
+ s(0, 0),
+ s(-5, -5),
+ s(-10, -10),
+ s(-15, -15),
+ s(-20, -20),
+ s(-25, -25),
+ s(-30, -30),
+ s(-35, -35),
+];
+#[cfg(feature = "classical")]
+const MOBILITY: [i32; 28] = [
+ s(0, 0),
+ s(1, 1),
+ s(2, 2),
+ s(3, 3),
+ s(4, 4),
+ s(5, 5),
+ s(6, 6),
+ s(7, 7),
+ s(8, 8),
+ s(9, 9),
+ s(10, 10),
+ s(11, 11),
+ s(12, 12),
+ s(13, 13),
+ s(14, 14),
+ s(15, 15),
+ s(16, 16),
+ s(17, 17),
+ s(18, 18),
+ s(19, 19),
+ s(20, 20),
+ s(21, 21),
+ s(22, 22),
+ s(23, 23),
+ s(24, 24),
+ s(25, 25),
+ s(26, 26),
+ s(27, 27),
+];
+
+/* gives adjacent files for index i
+* if i = 2:
+* 0 1 0 1 0 0 0 0
+* 0 1 0 1 0 0 0 0
+* 0 1 0 1 0 0 0 0
+* 0 1 0 1 0 0 0 0
+* 0 1 0 1 0 0 0 0
+* 0 1 0 1 0 0 0 0
+* 0 1 0 1 0 0 0 0
+* 0 1 0 1 0 0 0 0
+*/
+#[cfg(feature = "classical")]
+const ADJACENT_FILES_TABLE: [Bitboard; 8] = [
+ Bitboard(0x202020202020202),
+ Bitboard(0x505050505050505),
+ Bitboard(0xa0a0a0a0a0a0a0a),
+ Bitboard(0x1414141414141414),
+ Bitboard(0x2828282828282828),
+ Bitboard(0x5050505050505050),
+ Bitboard(0xa0a0a0a0a0a0a0a0),
+ Bitboard(0x4040404040404040),
+];
+
+#[cfg(feature = "classical")]
+const FILES_TABLE: [Bitboard; 8] = [
+ Bitboard(0x101010101010101),
+ Bitboard(0x202020202020202),
+ Bitboard(0x404040404040404),
+ Bitboard(0x808080808080808),
+ Bitboard(0x1010101010101010),
+ Bitboard(0x2020202020202020),
+ Bitboard(0x4040404040404040),
+ Bitboard(0x8080808080808080),
+];
+
+/* gives adjacent and current files for index i
+* if i = 2:
+* 0 1 1 1 0 0 0 0
+* 0 1 1 1 0 0 0 0
+* 0 1 1 1 0 0 0 0
+* 0 1 1 1 0 0 0 0
+* 0 1 1 1 0 0 0 0
+* 0 1 1 1 0 0 0 0
+* 0 1 1 1 0 0 0 0
+* 0 1 1 1 0 0 0 0
+*/
+#[cfg(feature = "classical")]
+const ADJACENT_AND_FILE_TABLE: [Bitboard; 8] = [
+ Bitboard(0x101010101010101 & 0x202020202020202),
+ Bitboard(0x202020202020202 & 0x505050505050505),
+ Bitboard(0x404040404040404 & 0xa0a0a0a0a0a0a0a),
+ Bitboard(0x808080808080808 & 0x1414141414141414),
+ Bitboard(0x1010101010101010 & 0x2828282828282828),
+ Bitboard(0x2020202020202020 & 0x5050505050505050),
+ Bitboard(0x4040404040404040 & 0xa0a0a0a0a0a0a0a0),
+ Bitboard(0x8080808080808080 & 0x4040404040404040),
+];
+
+#[cfg(feature = "classical")]
+#[rustfmt::skip]
+const PAWN: [i32; 64] = [
+ s( 0, 0), s( 0, 0), s( 0, 0), s( 0, 0), s( 0, 0), s( 0, 0), s( 0, 0), s( 0, 0),
+ s( 98, 178), s(134, 173), s( 61, 158), s( 95, 134), s( 68, 147), s(126, 132), s(34, 165), s(-11, 187),
+ s( -6, 94), s( 7, 100), s( 26, 85), s( 31, 67), s( 65, 56), s( 56, 53), s(25, 82), s(-20, 84),
+ s(-14, 32), s( 13, 24), s( 6, 13), s( 21, 5), s( 23, -2), s( 12, 4), s(17, 17), s(-23, 17),
+ s(-27, 13), s( -2, 9), s( -5, -3), s( 12, -7), s( 17, -7), s( 6, -8), s(10, 3), s(-25, -1),
+ s(-26, 4), s( -4, 7), s( -4, -6), s(-10, 1), s( 3, 0), s( 3, -5), s(33, -1), s(-12, -8),
+ s(-35, 13), s( -1, 8), s(-20, 8), s(-23, 10), s(-15, 13), s( 24, 0), s(38, 2), s(-22, -7),
+ s( 0, 0), s( 0, 0), s( 0, 0), s( 0, 0), s( 0, 0), s( 0, 0), s( 0, 0), s( 0, 0),
+];
+
+#[cfg(feature = "classical")]
+#[rustfmt::skip]
+const KNIGHT: [i32; 64] = [
+ s(-167, -58), s(-89, -38), s(-34, -13), s(-49, -28), s( 61, -31), s(-97, -27), s(-15, -63), s(-107, -99),
+ s( -73, -25), s(-41, -8), s( 72, -25), s( 36, -2), s( 23, -9), s( 62, -25), s( 7, -24), s( -17, -52),
+ s( -47, -24), s( 60, -20), s( 37, 10), s( 65, 9), s( 84, -1), s(129, -9), s( 73, -19), s( 44, -41),
+ s( -9, -17), s( 17, 3), s( 19, 22), s( 53, 22), s( 37, 22), s( 69, 11), s( 18, 8), s( 22, -18),
+ s( -13, -18), s( 4, -6), s( 16, 16), s( 13, 25), s( 28, 16), s( 19, 17), s( 21, 4), s( -8, -18),
+ s( -23, -23), s( -9, -3), s( 12, -1), s( 10, 15), s( 19, 10), s( 17, -3), s( 25, -20), s( -16, -22),
+ s( -29, -42), s(-53, -20), s(-12, -10), s( -3, -5), s( -1, -2), s( 18, -20), s(-14, -23), s( -19, -44),
+ s(-105, -29), s(-21, -51), s(-58, -23), s(-33, -15), s(-17, -22), s(-28, -18), s(-19, -50), s( -23, -64),
+];
+
+#[cfg(feature = "classical")]
+#[rustfmt::skip]
+const BISHOP: [i32; 64] = [
+ s(-29, -14), s( 4, -21), s(-82, -11), s(-37, -8), s(-25, -7), s(-42, -9), s( 7, -17), s( -8, -24),
+ s(-26, -8), s(16, -4), s(-18, 7), s(-13, -12), s( 30, -3), s( 59, -13), s( 18, -4), s(-47, -14),
+ s(-16, 2), s(37, -8), s( 43, 0), s( 40, -1), s( 35, -2), s( 50, 6), s( 37, 0), s( -2, 4),
+ s( -4, -3), s( 5, 9), s( 19, 12), s( 50, 9), s( 37, 14), s( 37, 10), s( 7, 3), s( -2, 2),
+ s( -6, -6), s(13, 3), s( 13, 13), s( 26, 19), s( 34, 7), s( 12, 10), s( 10, -3), s( 4, -9),
+ s( 0, -12), s(15, -3), s( 15, 8), s( 15, 10), s( 14, 13), s( 27, 3), s( 18, -7), s( 10, -15),
+ s( 4, -14), s(15, -18), s( 16, -7), s( 0, -1), s( 7, 4), s( 21, -9), s( 33, -15), s( 1, -27),
+ s(-33, -23), s(-3, -9), s(-14, -23), s(-21, -5), s(-13, -9), s(-12, -16), s(-39, -5), s(-21, -17),
+];
+
+#[cfg(feature = "classical")]
+#[rustfmt::skip]
+const ROOK: [i32; 64] = [
+ s( 32, 13), s( 42, 10), s( 32, 18), s( 51, 15), s(63, 12), s( 9, 12), s( 31, 8), s( 43, 5),
+ s( 27, 11), s( 32, 13), s( 58, 13), s( 62, 11), s(80, -3), s(67, 3), s( 26, 8), s( 44, 3),
+ s( -5, 7), s( 19, 7), s( 26, 7), s( 36, 5), s(17, 4), s(45, -3), s( 61, -5), s( 16, -3),
+ s(-24, 4), s(-11, 3), s( 7, 13), s( 26, 1), s(24, 2), s(35, 1), s( -8, -1), s(-20, 2),
+ s(-36, 3), s(-26, 5), s(-12, 8), s( -1, 4), s( 9, -5), s(-7, -6), s( 6, -8), s(-23, -11),
+ s(-45, -4), s(-25, 0), s(-16, -5), s(-17, -1), s( 3, -7), s( 0, -12), s( -5, -8), s(-33, -16),
+ s(-44, -6), s(-16, -6), s(-20, 0), s( -9, 2), s(-1, -9), s(11, -9), s( -6, -11), s(-71, -3),
+ s(-19, -9), s(-13, 2), s( 1, 3), s( 17, -1), s(16, -5), s( 7, -13), s(-37, 4), s(-26, -20),
+];
+
+#[cfg(feature = "classical")]
+#[rustfmt::skip]
+const QUEEN: [i32; 64] = [
+ s(-28, -9), s( 0, 22), s( 29, 22), s( 12, 27), s( 59, 27), s( 44, 19), s( 43, 10), s( 45, 20),
+ s(-24, -17), s(-39, 20), s( -5, 32), s( 1, 41), s(-16, 58), s( 57, 25), s( 28, 30), s( 54, 0),
+ s(-13, -20), s(-17, 6), s( 7, 9), s( 8, 49), s( 29, 47), s( 56, 35), s( 47, 19), s( 57, 9),
+ s(-27, 3), s(-27, 22), s(-16, 24), s(-16, 45), s( -1, 57), s( 17, 40), s( -2, 57), s( 1, 36),
+ s( -9, -18), s(-26, 28), s( -9, 19), s(-10, 47), s( -2, 31), s( -4, 34), s( 3, 39), s( -3, 23),
+ s(-14, -16), s( 2, -27), s(-11, 15), s( -2, 6), s( -5, 9), s( 2, 17), s( 14, 10), s( 5, 5),
+ s(-35, -22), s( -8, -23), s( 11, -30), s( 2, -16), s( 8, -16), s( 15, -23), s( -3, -36), s( 1, -32),
+ s( -1, -33), s(-18, -28), s( -9, -22), s( 10, -43), s(-15, -5), s(-25, -32), s(-31, -20), s(-50, -41),
+];
+
+#[cfg(feature = "classical")]
+#[rustfmt::skip]
+const KING: [i32; 64] = [
+ s(-65, -74), s( 23, -35), s( 16, -18), s(-15, -18), s(-56, -11), s(-34, 15), s( 2, 4), s( 13, -17),
+ s( 29, -12), s( -1, 17), s(-20, 14), s( -7, 17), s( -8, 17), s( -4, 38), s(-38, 23), s(-29, 11),
+ s( -9, 10), s( 24, 17), s( 2, 23), s(-16, 15), s(-20, 20), s( 6, 45), s( 22, 44), s(-22, 13),
+ s(-17, -8), s(-20, 22), s(-12, 24), s(-27, 27), s(-30, 26), s(-25, 33), s(-14, 26), s(-36, 3),
+ s(-49, -18), s( -1, -4), s(-27, 21), s(-39, 24), s(-46, 27), s(-44, 23), s(-33, 9), s(-51, -11),
+ s(-14, -19), s(-14, -3), s(-22, 11), s(-46, 21), s(-44, 23), s(-30, 16), s(-15, 7), s(-27, -9),
+ s( 1, -27), s( 7, -11), s( -8, 4), s(-64, 13), s(-43, 14), s(-16, 4), s( 9, -5), s( 8, -17),
+ s(-15, -53), s( 36, -34), s( 12, -21), s(-54, -11), s( 8, -28), s(-28, -14), s( 24, -24), s( 14, -43),
+];
diff --git a/engine/src/lib.rs b/engine/src/lib.rs
index f70c380..ca384d3 100644
--- a/engine/src/lib.rs
+++ b/engine/src/lib.rs
@@ -30,7 +30,9 @@ mod bound;
mod config;
mod eval;
mod logger;
+#[cfg(not(feature = "classical"))]
mod nnue;
+mod packing;
mod search;
mod time_control;
mod uci;
diff --git a/engine/src/main.rs b/engine/src/main.rs
index 6a96506..b1f8637 100644
--- a/engine/src/main.rs
+++ b/engine/src/main.rs
@@ -21,15 +21,44 @@ mod bound;
mod config;
mod eval;
mod logger;
+#[cfg(not(feature = "classical"))]
mod nnue;
+mod packing;
mod search;
mod time_control;
mod uci;
+#[cfg(feature = "log")]
+use logger::Logger;
+#[cfg(feature = "log")]
+use simplelog::*;
+#[cfg(feature = "log")]
+use std::fs::OpenOptions;
+
#[cfg(not(target_arch = "wasm32"))]
pub fn main() {
use uci::UciReader;
+ #[cfg(feature = "log")]
+ {
+ let mut log_path = env::temp_dir();
+ log_path.push("pluto.log");
+
+ let file = OpenOptions::new()
+ .create(true)
+ .append(true)
+ .open(&log_path)
+ .expect("Unable to open log file");
+
+ WriteLogger::init(LevelFilter::Debug, Config::default(), file).unwrap();
+
+ std::panic::set_hook(Box::new(|panic_info| {
+ Logger::log(format!("{:?}", panic_info).as_str());
+ log::error!("Panic occurred: {:?}", panic_info);
+ log::logger().flush();
+ }));
+ }
+
let args: Vec = env::args().collect();
UciReader::default().run(args);
}
diff --git a/engine/src/packing.rs b/engine/src/packing.rs
new file mode 100644
index 0000000..08496c8
--- /dev/null
+++ b/engine/src/packing.rs
@@ -0,0 +1,12 @@
+#[inline]
+pub const fn s(mg: i32, eg: i32) -> i32 {
+ ((eg as u32) << 16) as i32 + mg
+}
+
+pub const fn extract_mg(value: i32) -> i32 {
+ value as i16 as i32
+}
+
+pub const fn extract_eg(value: i32) -> i32 {
+ ((value + 0x8000) >> 16) as i16 as i32
+}
diff --git a/engine/src/search/info.rs b/engine/src/search/info.rs
index 0957d1b..8444e64 100644
--- a/engine/src/search/info.rs
+++ b/engine/src/search/info.rs
@@ -15,13 +15,8 @@
along with this program. If not, see .
*/
+#[derive(Default)]
pub struct SearchInfo {
pub nodes: u32,
pub depth: u8,
}
-
-impl Default for SearchInfo {
- fn default() -> Self {
- SearchInfo { nodes: 0, depth: 0 }
- }
-}
diff --git a/engine/src/search/killers.rs b/engine/src/search/killers.rs
index 96379fa..0dd9ee1 100644
--- a/engine/src/search/killers.rs
+++ b/engine/src/search/killers.rs
@@ -16,15 +16,16 @@
*/
use shakmaty::Move;
+use std::u8;
pub struct Killers {
- table: [Vec; 64],
+ table: Vec>,
}
impl Killers {
pub fn new() -> Self {
Self {
- table: [const { Vec::new() }; 64],
+ table: vec![Vec::new(); u8::MAX as usize],
}
}
@@ -33,13 +34,15 @@ impl Killers {
}
pub fn store(&mut self, ply: usize, m: Move) {
- if ply >= 64 {
+ if ply >= self.table.len() {
return;
}
- if !self.get(ply).contains(&m) {
- self.table[ply].pop();
- self.table[ply].insert(0, m);
+ let killers = &mut self.table[ply];
+
+ if !killers.contains(&m) {
+ killers.pop();
+ killers.insert(0, m);
}
}
}
diff --git a/engine/src/search/mod.rs b/engine/src/search/mod.rs
index f3599cf..feda94f 100644
--- a/engine/src/search/mod.rs
+++ b/engine/src/search/mod.rs
@@ -30,18 +30,22 @@ use info::SearchInfo;
use killers::Killers;
use params::SearchParams;
use pv::PvTable;
-use shakmaty::{Chess, Position};
+use shakmaty::Chess;
+#[cfg(not(feature = "classical"))]
+use shakmaty::Position;
use tt::TranspositionTable;
-use crate::config::Config;
+#[cfg(not(feature = "classical"))]
+use crate::nnue::NNUEState;
use crate::search::history_stack::HistoryStack;
-use crate::{nnue::NNUEState, time_control::time_controller::TimeController};
+use crate::{config::Config, time_control::time_controller::TimeController};
pub struct SearchState {
pub game: Chess,
pub params: SearchParams,
pub info: SearchInfo,
pub tc: TimeController,
+ #[cfg(not(feature = "classical"))]
pub nnue: NNUEState,
pub tt: TranspositionTable,
pub hstack: HistoryStack,
@@ -61,6 +65,7 @@ impl SearchState {
info: SearchInfo::default(),
tc: TimeController::default(),
params: SearchParams::default(),
+ #[cfg(not(feature = "classical"))]
nnue: NNUEState::from_board(Chess::default().board()),
hstack: HistoryStack::new(),
pv: PvTable::default(),
diff --git a/engine/src/search/move_picker.rs b/engine/src/search/move_picker.rs
index 5c8cfde..4de65ab 100644
--- a/engine/src/search/move_picker.rs
+++ b/engine/src/search/move_picker.rs
@@ -19,25 +19,11 @@ use super::{tt::TranspositionTableEntry, SearchState};
use shakmaty::{Move, MoveList, Role};
const MO_FACTOR: i32 = 10000;
+use std::cmp::Reverse;
pub struct MovePicker<'a> {
- order: Vec<&'a Move>,
- curr: usize,
-}
-
-impl<'a> Iterator for MovePicker<'a> {
- type Item = &'a Move;
-
- fn next(&mut self) -> Option {
- if self.curr >= self.order.len() {
- return None;
- }
-
- let move_ref = self.order[self.curr];
- self.curr += 1;
-
- Some(move_ref)
- }
+ moves: Vec<(&'a Move, i32)>,
+ index: usize,
}
impl<'a> MovePicker<'a> {
@@ -49,14 +35,15 @@ impl<'a> MovePicker<'a> {
) -> Self {
let mut scored_moves: Vec<(&'a Move, i32)> = moves
.iter()
- .map(|m_ref| (m_ref, Self::move_importance(state, entry, ply, m_ref)))
+ .map(|m| (m, Self::move_importance(state, entry, ply, m)))
.collect();
- scored_moves.sort_by_key(|&(_, score)| -score);
-
- let order: Vec<&'a Move> = scored_moves.into_iter().map(|(m_ref, _)| m_ref).collect();
+ scored_moves.sort_by_key(|&(_, score)| Reverse(score));
- Self { order, curr: 0 }
+ Self {
+ moves: scored_moves,
+ index: 0,
+ }
}
fn move_importance(
@@ -89,3 +76,18 @@ impl<'a> MovePicker<'a> {
state.hist.get(m.role(), m.to())
}
}
+
+impl<'a> Iterator for MovePicker<'a> {
+ type Item = &'a Move;
+
+ fn next(&mut self) -> Option {
+ if self.index >= self.moves.len() {
+ return None;
+ }
+
+ let next_move = self.moves[self.index].0;
+ self.index += 1;
+ Some(next_move)
+ }
+}
+
diff --git a/engine/src/search/pv.rs b/engine/src/search/pv.rs
index d518908..b36679c 100644
--- a/engine/src/search/pv.rs
+++ b/engine/src/search/pv.rs
@@ -16,26 +16,27 @@
*/
use shakmaty::{CastlingMode, Move};
+use std::u8;
pub struct PvTable {
- pub length: [i32; 64],
+ pub length: Vec,
pub table: Vec>>,
}
impl PvTable {
pub fn default() -> PvTable {
+ const MAX_DEPTH: usize = u8::MAX as usize + 1;
+
PvTable {
- length: [0; 64],
- table: vec![vec![None; 64]; 64],
+ length: vec![0; MAX_DEPTH],
+ table: vec![vec![None; MAX_DEPTH]; MAX_DEPTH],
}
}
-}
-impl PvTable {
pub fn store(&mut self, ply: usize, m: Move) {
self.table[ply][ply] = Some(m);
- for next_ply in ply as i32 + 1..self.length[ply + 1] {
+ for next_ply in (ply as i32 + 1)..self.length[ply + 1] {
self.table[ply][next_ply as usize] = self.table[ply + 1][next_ply as usize].clone();
}
diff --git a/engine/src/search/search.rs b/engine/src/search/search.rs
index 6f997d8..a5f8f19 100644
--- a/engine/src/search/search.rs
+++ b/engine/src/search/search.rs
@@ -19,13 +19,16 @@ use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use crate::bound::Bound;
-use crate::eval::Eval;
+use crate::eval::{Eval, PIECE_VALUES};
use crate::logger::Logger;
-use crate::nnue::OFF;
-use crate::nnue::ON;
+#[cfg(not(feature = "classical"))]
+use crate::nnue::{OFF, ON};
+use crate::packing::extract_mg;
use crate::time_control::time_mode::TimeMode;
use shakmaty::zobrist::{Zobrist64, ZobristHash};
-use shakmaty::{CastlingMode, CastlingSide, Chess, EnPassantMode, Move, Piece, Position, Square};
+use shakmaty::{CastlingMode, Chess, EnPassantMode, Move, Position};
+#[cfg(not(feature = "classical"))]
+use shakmaty::{CastlingSide, Piece, Square};
use super::move_picker::MovePicker;
use super::SearchState;
@@ -42,71 +45,73 @@ impl Search {
}
pub fn make_move(&mut self, pos: &mut Chess, m: &Move, eval: i32) {
- self.state.nnue.push();
- let turn = pos.turn();
- let board = pos.board();
-
- match m {
- Move::EnPassant { from, to } => {
- let ep_target = Square::from_coords(to.file(), from.rank()); // captured pawn
-
- self.state
- .nnue
- .manual_update::((!pos.turn()).pawn(), ep_target);
- }
-
- Move::Castle { king, rook } => {
- let side = CastlingSide::from_queen_side(rook < king);
- let rook_target = Square::from_coords(side.rook_to_file(), rook.rank());
-
- self.state.nnue.move_update(turn.rook(), *rook, rook_target);
- self.state.nnue.move_update(
- Piece {
- color: turn,
- role: m.role(),
- },
- m.from().unwrap(),
- m.to(),
- );
- }
+ #[cfg(not(feature = "classical"))]
+ {
+ self.state.nnue.push();
+ let turn = pos.turn();
+ let board = pos.board();
- Move::Normal {
- role,
- from,
- capture: _capture,
- to,
- promotion,
- } => {
- if m.is_capture() {
- let target_piece = board.piece_at(*to).unwrap();
- let target_square = *to;
+ match m {
+ Move::EnPassant { from, to } => {
+ let ep_target = Square::from_coords(to.file(), from.rank()); // captured pawn
self.state
.nnue
- .manual_update::(target_piece, target_square);
+ .manual_update::((!pos.turn()).pawn(), ep_target);
}
- let piece = Piece {
- color: turn,
- role: *role,
- };
+ Move::Castle { king, rook } => {
+ let side = CastlingSide::from_queen_side(rook < king);
+ let rook_target = Square::from_coords(side.rook_to_file(), rook.rank());
+
+ self.state.nnue.move_update(turn.rook(), *rook, rook_target);
+ self.state.nnue.move_update(
+ Piece {
+ color: turn,
+ role: m.role(),
+ },
+ m.from().unwrap(),
+ m.to(),
+ );
+ }
- if m.is_promotion() {
- let promoted_piece = Piece {
+ Move::Normal {
+ role,
+ from,
+ capture: _capture,
+ to,
+ promotion,
+ } => {
+ if m.is_capture() {
+ let target_piece = board.piece_at(*to).unwrap();
+ let target_square = *to;
+
+ self.state
+ .nnue
+ .manual_update::(target_piece, target_square);
+ }
+
+ let piece = Piece {
color: turn,
- role: promotion.unwrap(),
+ role: *role,
};
- self.state.nnue.manual_update::(piece, *from);
- self.state.nnue.manual_update::(promoted_piece, *to);
- } else {
- self.state.nnue.move_update(piece, *from, *to);
+ if m.is_promotion() {
+ let promoted_piece = Piece {
+ color: turn,
+ role: promotion.unwrap(),
+ };
+
+ self.state.nnue.manual_update::(piece, *from);
+ self.state.nnue.manual_update::(promoted_piece, *to);
+ } else {
+ self.state.nnue.move_update(piece, *from, *to);
+ }
}
- }
- _ => {}
+ _ => {}
+ }
}
-
pos.play_unchecked(m);
self.state
.hstack
@@ -114,6 +119,7 @@ impl Search {
}
pub fn undo_move(&mut self) {
+ #[cfg(not(feature = "classical"))]
self.state.nnue.pop();
self.state.hstack.pop();
}
@@ -128,9 +134,12 @@ impl Search {
self.state.tc.stop = stop.clone();
let mut best_move = None;
+ let mut alpha = -100000;
+ let mut beta = 100000;
+ let mut current_depth = 0;
/* Iterative deepening */
- for current_depth in 0..self.state.params.depth {
+ while current_depth < self.state.params.depth {
if TimeMode::is_finite(&self.state.tc.time_mode)
&& (self.state.tc.elapsed() * self.state.cfg.tc_elapsed_factor.value) as u128
> self.state.tc.play_time
@@ -140,17 +149,26 @@ impl Search {
self.state.info.depth = current_depth + 1;
let pos = self.state.game.clone();
- let iteration_score = self.negamax(&pos, self.state.info.depth, -100000, 100000, 0);
+ let iteration_score = self.negamax(&pos, self.state.info.depth, alpha, beta, 0);
if self.state.tc.is_time_up() {
break;
}
- best_move = Some(self.state.pv.get_best_move().unwrap());
+ best_move = self.state.pv.get_best_move();
let elapsed = self.state.tc.elapsed();
let pv = self.state.pv.collect();
+ if iteration_score <= alpha || iteration_score >= beta {
+ alpha = -100000;
+ beta = 100000;
+ continue;
+ }
+
+ alpha = iteration_score - 60;
+ beta = iteration_score + 60;
+
if print {
Logger::log(&format!(
"info depth {} nodes {} nps {} score cp {} time {} pv {}",
@@ -162,6 +180,12 @@ impl Search {
pv.join(" ")
));
}
+
+ current_depth += 1;
+ }
+
+ if best_move.is_none() {
+ best_move = self.state.pv.get_best_move();
}
if print {
@@ -182,7 +206,7 @@ impl Search {
) -> i32 {
self.state.pv.update_length(ply);
- if self.state.tc.is_time_up() {
+ if self.state.info.nodes % 2048 == 0 && self.state.tc.is_time_up() {
return 0;
}
@@ -208,22 +232,14 @@ impl Search {
return entry.score;
}
+ #[cfg(not(feature = "classical"))]
let static_eval = Eval::nnue_eval(&self.state.nnue, pos);
+ #[cfg(feature = "classical")]
+ let static_eval = Eval::eval(pos);
+
/* Improving */
- let improving = match ply {
- ply if ply < 2 => false,
- _ => {
- static_eval >= {
- let e = self.state.hstack.get_eval(ply - 2);
- if let Some(e) = e {
- e
- } else {
- static_eval
- }
- }
- }
- };
+ let improving = static_eval >= self.state.hstack.get_eval(ply - 2).unwrap_or(static_eval);
/* Threefold Detection */
if ply > 0 && self.state.hstack.count_zobrist(position_key) >= 1 {
@@ -401,25 +417,41 @@ impl Search {
}
fn quiesce(&mut self, pos: &Chess, mut alpha: i32, beta: i32, limit: u8) -> i32 {
+ if self.state.info.nodes % 2048 == 0 && self.state.tc.is_time_up() {
+ return 0;
+ }
self.state.info.nodes += 1;
+ #[cfg(not(feature = "classical"))]
let stand_pat = Eval::nnue_eval(&self.state.nnue, pos);
+ #[cfg(feature = "classical")]
+ let stand_pat = Eval::eval(pos);
+
if limit == 0 {
return stand_pat;
}
+
if stand_pat >= beta {
return beta;
}
+
if alpha < stand_pat {
alpha = stand_pat;
}
let moves = pos.capture_moves();
+ let mp = MovePicker::new(&moves, &self.state, &Default::default(), 0);
+
+ for m in mp {
+ let captured_value = extract_mg(PIECE_VALUES[m.capture().unwrap() as usize - 1]);
+
+ if stand_pat + captured_value + 200 < alpha {
+ continue;
+ }
- for m in moves {
let mut pos = pos.clone();
- self.make_move(&mut pos, &m, stand_pat);
+ self.make_move(&mut pos, m, stand_pat);
let score = -self.quiesce(&pos, -beta, -alpha, limit - 1);
self.undo_move();
diff --git a/engine/src/uci.rs b/engine/src/uci.rs
index e3e6a98..0eb8806 100644
--- a/engine/src/uci.rs
+++ b/engine/src/uci.rs
@@ -22,6 +22,7 @@ use std::sync::Arc;
use std::{io, thread};
use crate::logger::Logger;
+#[cfg(not(feature = "classical"))]
use crate::nnue::NNUEState;
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
use crate::postMessage;
@@ -133,11 +134,13 @@ impl UciController {
let scope = tokens.remove().unwrap();
match scope {
+ #[cfg(feature = "tuning")]
"spsa" => self.handle_print_spsa(tokens),
_ => Logger::log(&format!("unknown scope: {}", scope)),
}
}
+ #[cfg(feature = "tuning")]
fn handle_print_spsa(&self, tokens: &mut Queue<&str>) {
let target = tokens.remove().unwrap();
@@ -147,31 +150,13 @@ impl UciController {
}
}
+ #[cfg(feature = "tuning")]
fn handle_print_spsa_workload(&self) {
- Logger::log(&self.search.state.cfg.qsearch_depth.fmt_spsa());
- Logger::log(&self.search.state.cfg.rfp_depth.fmt_spsa());
- Logger::log(&self.search.state.cfg.rfp_base_margin.fmt_spsa());
- Logger::log(&self.search.state.cfg.rfp_reduction_improving.fmt_spsa());
- Logger::log(&self.search.state.cfg.fp_base_margin.fmt_spsa());
- Logger::log(&self.search.state.cfg.fp_depth_margin.fmt_spsa());
- Logger::log(&self.search.state.cfg.fp_margin_depth_factor.fmt_spsa());
- Logger::log(&self.search.state.cfg.nmp_depth.fmt_spsa());
- Logger::log(&self.search.state.cfg.nmp_margin.fmt_spsa());
- Logger::log(&self.search.state.cfg.nmp_divisor.fmt_spsa());
- Logger::log(&self.search.state.cfg.nmp_divisor_improving.fmt_spsa());
- Logger::log(&self.search.state.cfg.lmp_move_margin.fmt_spsa());
- Logger::log(&self.search.state.cfg.lmp_depth_factor.fmt_spsa());
- Logger::log(&self.search.state.cfg.lmr_depth.fmt_spsa());
- Logger::log(&self.search.state.cfg.lmr_move_margin.fmt_spsa());
- Logger::log(&self.search.state.cfg.lmr_quiet_margin.fmt_spsa());
- Logger::log(&self.search.state.cfg.lmr_quiet_divisor.fmt_spsa());
- Logger::log(&self.search.state.cfg.lmr_base_margin.fmt_spsa());
- Logger::log(&self.search.state.cfg.lmr_base_divisor.fmt_spsa());
- Logger::log(&self.search.state.cfg.mo_tt_entry_value.fmt_spsa());
- Logger::log(&self.search.state.cfg.mo_capture_value.fmt_spsa());
- Logger::log(&self.search.state.cfg.mo_killer_value.fmt_spsa());
- Logger::log(&self.search.state.cfg.tc_time_divisor.fmt_spsa());
- Logger::log(&self.search.state.cfg.tc_elapsed_factor.fmt_spsa());
+ self.search.state.cfg.all_spsa();
+
+ for line in self.search.state.cfg.all_spsa() {
+ println!("{}", line);
+ }
}
fn handle_bench(&mut self, stop: Arc) {
@@ -192,9 +177,12 @@ impl UciController {
let fen: Fen = position.parse().ok().unwrap();
let game = fen.into_position(CastlingMode::Standard).ok().unwrap();
- self.search.state.nnue = NNUEState::from_board(self.search.state.game.board());
+ #[cfg(not(feature = "classical"))]
+ {
+ self.search.state.nnue = NNUEState::from_board(self.search.state.game.board());
+ }
self.search.state.game = game;
- self.search.state.params.depth = 12;
+ self.search.state.params.depth = 10;
self.search.state.tc.time_mode = TimeMode::Infinite;
self.search.go(false, &stop);
@@ -205,7 +193,11 @@ impl UciController {
let elapsed = Local::now().timestamp_millis() - start_time;
self.search.state.game = Chess::default();
- self.search.state.nnue = NNUEState::from_board(self.search.state.game.board());
+
+ #[cfg(not(feature = "classical"))]
+ {
+ self.search.state.nnue = NNUEState::from_board(self.search.state.game.board());
+ }
println!(
"{} nodes {} nps",
@@ -334,7 +326,10 @@ impl UciController {
}
}
- self.search.state.nnue = NNUEState::from_board(self.search.state.game.board());
+ #[cfg(not(feature = "classical"))]
+ {
+ self.search.state.nnue = NNUEState::from_board(self.search.state.game.board());
+ }
}
fn handle_position_fen(&mut self, tokens: &mut Queue<&str>) {
@@ -378,7 +373,10 @@ impl UciController {
}
}
- self.search.state.nnue = NNUEState::from_board(self.search.state.game.board());
+ #[cfg(not(feature = "classical"))]
+ {
+ self.search.state.nnue = NNUEState::from_board(self.search.state.game.board());
+ }
}
fn handle_setoption(&mut self, tokens: &mut Queue<&str>) {
@@ -405,70 +403,7 @@ impl UciController {
self.search.state.tt = TranspositionTable::new(entries as usize);
}
- "QSearchDepth" => {
- self.search.state.cfg.qsearch_depth.value = value.parse::().unwrap()
- }
- "RFPDepth" => self.search.state.cfg.rfp_depth.value = value.parse::().unwrap(),
- "RFPBaseMargin" => {
- self.search.state.cfg.rfp_base_margin.value = value.parse::().unwrap()
- }
- "RFPReductionImproving" => {
- self.search.state.cfg.rfp_reduction_improving.value = value.parse::().unwrap()
- }
- "FPDepthMargin" => {
- self.search.state.cfg.fp_depth_margin.value = value.parse::().unwrap()
- }
- "FPBaseMargin" => {
- self.search.state.cfg.fp_base_margin.value = value.parse::().unwrap()
- }
- "FPMarginDepthFactor" => {
- self.search.state.cfg.fp_margin_depth_factor.value = value.parse::().unwrap()
- }
- "NMPDepth" => self.search.state.cfg.nmp_depth.value = value.parse::().unwrap(),
- "NMPMargin" => self.search.state.cfg.nmp_margin.value = value.parse::().unwrap(),
- "NMPDivisor" => self.search.state.cfg.nmp_divisor.value = value.parse::().unwrap(),
- "NMPDivisorImproving" => {
- self.search.state.cfg.nmp_divisor_improving.value = value.parse::().unwrap()
- }
- "LMPMoveMargin" => {
- self.search.state.cfg.lmp_move_margin.value = value.parse::().unwrap()
- }
- "LMPDepthFactor" => {
- self.search.state.cfg.lmp_depth_factor.value = value.parse::().unwrap()
- }
- "LMRDepth" => self.search.state.cfg.lmr_depth.value = value.parse::().unwrap(),
- "LMRMoveMargin" => {
- self.search.state.cfg.lmr_move_margin.value = value.parse::().unwrap()
- }
- "LMRQuietMargin" => {
- self.search.state.cfg.lmr_quiet_margin.value = value.parse::().unwrap()
- }
- "LMRQuietDivisor" => {
- self.search.state.cfg.lmr_quiet_divisor.value = value.parse::().unwrap()
- }
- "LMRBaseMargin" => {
- self.search.state.cfg.lmr_base_margin.value = value.parse::().unwrap()
- }
- "LMRBaseDivisor" => {
- self.search.state.cfg.lmr_base_divisor.value = value.parse::().unwrap()
- }
- "MOTTEntryValue" => {
- self.search.state.cfg.mo_tt_entry_value.value = value.parse::().unwrap()
- }
- "MOCaptureValue" => {
- self.search.state.cfg.mo_capture_value.value = value.parse::().unwrap()
- }
- "MOKillerValue" => {
- self.search.state.cfg.mo_killer_value.value = value.parse::().unwrap()
- }
- "TCTimeDivisor" => {
- self.search.state.cfg.tc_time_divisor.value = value.parse::().unwrap()
- }
- "TCElapsedFactor" => {
- self.search.state.cfg.tc_elapsed_factor.value = value.parse::().unwrap()
- }
-
- _ => Logger::log(&format!("info string unknown option: {}", name)),
+ _ => self.search.state.cfg.set(name, value),
}
}
@@ -495,35 +430,13 @@ impl UciController {
Logger::log(r#"id name Pluto 1.0.1"#);
Logger::log(r#"id author Lxdovic"#);
- Logger::log(format!("{}", self.search.state.cfg.move_overhead).as_str());
- Logger::log(format!("{}", self.search.state.cfg.threads).as_str());
- Logger::log(format!("{}", self.search.state.cfg.hash).as_str());
-
- // Values to tune
- Logger::log(format!("{}", self.search.state.cfg.qsearch_depth).as_str());
- Logger::log(format!("{}", self.search.state.cfg.rfp_depth).as_str());
- Logger::log(format!("{}", self.search.state.cfg.rfp_base_margin).as_str());
- Logger::log(format!("{}", self.search.state.cfg.rfp_reduction_improving).as_str());
- Logger::log(format!("{}", self.search.state.cfg.fp_base_margin).as_str());
- Logger::log(format!("{}", self.search.state.cfg.fp_depth_margin).as_str());
- Logger::log(format!("{}", self.search.state.cfg.fp_margin_depth_factor).as_str());
- Logger::log(format!("{}", self.search.state.cfg.nmp_depth).as_str());
- Logger::log(format!("{}", self.search.state.cfg.nmp_margin).as_str());
- Logger::log(format!("{}", self.search.state.cfg.nmp_divisor).as_str());
- Logger::log(format!("{}", self.search.state.cfg.nmp_divisor_improving).as_str());
- Logger::log(format!("{}", self.search.state.cfg.lmp_move_margin).as_str());
- Logger::log(format!("{}", self.search.state.cfg.lmp_depth_factor).as_str());
- Logger::log(format!("{}", self.search.state.cfg.lmr_depth).as_str());
- Logger::log(format!("{}", self.search.state.cfg.lmr_move_margin).as_str());
- Logger::log(format!("{}", self.search.state.cfg.lmr_quiet_margin).as_str());
- Logger::log(format!("{}", self.search.state.cfg.lmr_quiet_divisor).as_str());
- Logger::log(format!("{}", self.search.state.cfg.lmr_base_margin).as_str());
- Logger::log(format!("{}", self.search.state.cfg.lmr_base_divisor).as_str());
- Logger::log(format!("{}", self.search.state.cfg.mo_tt_entry_value).as_str());
- Logger::log(format!("{}", self.search.state.cfg.mo_capture_value).as_str());
- Logger::log(format!("{}", self.search.state.cfg.mo_killer_value).as_str());
- Logger::log(format!("{}", self.search.state.cfg.tc_time_divisor).as_str());
- Logger::log(format!("{}", self.search.state.cfg.tc_elapsed_factor).as_str());
+ #[cfg(not(feature = "classical"))]
+ Logger::log(r#"info string using NNUE eval"#);
+
+ #[cfg(feature = "classical")]
+ Logger::log(r#"info string using HCE eval"#);
+
+ self.search.state.cfg.print_uci_options();
Logger::log(r#"uciok"#);
}