From 4364b90d848d9fb3c9eb690853661914d695dbc3 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 27 Mar 2025 17:12:02 -0400 Subject: [PATCH 01/12] feat: rework just file and add code coverage generation --- .gitignore | 5 ++++- Justfile | 34 +++++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 3b0e89a..97d938a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ /data/lichess_db_puzzle.csv **/build/* *.exe -**/.DS_Store \ No newline at end of file +**/.DS_Store +*.profraw +*.profdata +*.lcov \ No newline at end of file diff --git a/Justfile b/Justfile index 3d4e39c..954254f 100644 --- a/Justfile +++ b/Justfile @@ -1,14 +1,34 @@ set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"] -default: build +default: (build) -build: +build config="debug": echo "Building the project..." - cargo build --release --all + cargo build --workspace --all-features {{ if config=="release" {"--release"} else {""} }} -test: +test config="debug": echo "Running tests..." - cargo test --release --all --all-features + cargo test --workspace --all-features {{ if config=="release" {"--release"} else {""} }} -- --include-ignored + +export RUSTFLAGS:="-Cinstrument-coverage" +export LLVM_PROFILE_FILE:="./target/coverage/byte_knight-%p-%m.profraw" +coverage: (build "debug") + echo "Running tests with coverage..." + mkdir -p target/coverage + cargo test --workspace -- --skip "perft" + grcov target/coverage engine/target/coverage chess/target/coverage -s . \ + --binary-path ./target/debug/ --output-types lcov -o ./target/coverage/byte-knight.lcov \ + --branch --keep-only "src/*" --keep-only "engine/*" --keep-only "chess/*" \ + --ignore "src/bin/byte-knight/*" --ignore "chess/src/perft*" + +purge-coverage: + echo "Purging coverage data..." + rm -rf *.profraw + rm -rf target/coverage + rm -rf chess/target + rm -rf engine/target + rm -rf chess/*.profraw + rm -rf engine/*.profraw lint: cargo clippy --all --all-features --tests -- -D warnings @@ -42,10 +62,10 @@ release target: echo "Building release binaries..." cargo rustc --release --bin byte-knight --target={{ target }} -cache-main: build +cache-main: (build "release") echo "Caching binary for testing..." cp target/release/byte-knight ./bk-main -compare-to-main engine1: build +compare-to-main engine1: (build "release") echo "Comparing {{ engine1 }} to bk-main" fastchess -engine cmd="{{ engine1 }}" name="dev" -engine cmd="./bk-main" name="bk-main" -openings file="./data/Pohl.epd" format=epd order=random -each tc=10+0.1 -rounds 200 -repeat -concurrency 8 -sprt elo0=0 elo1=5 alpha=0.05 beta=0.1 model=normalized -output format=cutechess From 6573d5389bbb4c6fc8dc3f14e30d424b13ad8afa Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 27 Mar 2025 17:12:17 -0400 Subject: [PATCH 02/12] fix: ignore this test for code coverage --- engine/src/search.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/src/search.rs b/engine/src/search.rs index 565eca8..a5397ff 100644 --- a/engine/src/search.rs +++ b/engine/src/search.rs @@ -648,6 +648,7 @@ mod tests { } #[test] + #[ignore] fn do_not_exceed_time() { let mut board = Board::default_board(); let config = SearchParameters { From 4f0310f08280a379dba08d36fdb15b5765090325 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 27 Mar 2025 17:12:43 -0400 Subject: [PATCH 03/12] fix: bug in score +/- Add and minus impl was overflowing in debug. We needed to control this explicitly --- engine/src/score.rs | 52 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/engine/src/score.rs b/engine/src/score.rs index 18aebab..85e5dfe 100644 --- a/engine/src/score.rs +++ b/engine/src/score.rs @@ -24,6 +24,25 @@ use crate::defs::MAX_DEPTH; pub type ScoreType = i16; pub(crate) type LargeScoreType = i32; /// Represents a score in centipawns. +/// +/// This type has saturating add/sub operations to prevent overflow. +/// It will not wrap around on overflow, but instead saturate to the internal types min/max. +/// +/// The score is represented as a signed 16-bit integer, which allows for a range of -32,768 to 32,767. +/// +/// Example usage: +/// ```rust +/// use engine::score::{Score, ScoreType}; +/// let score = Score::new(150); // Represents a score of 150 centipawns +/// let mate_score = Score::MATE; // Represents a checkmate score +/// let draw_score = Score::DRAW; // Represents a draw score +/// let mut s = Score::INF / 2; +/// s += Score::INF; +/// assert_eq!(s, Score::INF); // Saturating addition +/// let mut ss = -Score::INF; +/// ss -= Score::INF; +/// assert_eq!(ss, Score::new(ScoreType::MIN)); // Saturating subtraction +/// ``` #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct Score(pub ScoreType); @@ -83,55 +102,55 @@ impl Neg for Score { impl AddAssign for Score { fn add_assign(&mut self, other: Score) { - self.0 += other.0; + *self = *self + other; } } impl AddAssign for Score { fn add_assign(&mut self, other: ScoreType) { - self.0 += other; + *self = *self + other; } } impl Add for Score { type Output = Score; - fn add(self, other: Score) -> Score { - Score(self.0 + other.0) + fn add(self, other: Score) -> Self::Output { + Score(self.0.saturating_add(other.0)) } } impl Add for Score { type Output = Score; - fn add(self, other: ScoreType) -> Score { - Score(self.0 + other) + fn add(self, other: ScoreType) -> Self::Output { + Score(self.0.saturating_add(other)) } } impl Sub for Score { type Output = Score; - fn sub(self, other: Score) -> Score { - Score(self.0 - other.0) + fn sub(self, other: Score) -> Self::Output { + Score(self.0.saturating_sub(other.0)) } } impl Sub for Score { type Output = Score; fn sub(self, other: ScoreType) -> Score { - Score(self.0 - other) + Score(self.0.saturating_sub(other)) } } impl SubAssign for Score { fn sub_assign(&mut self, other: Score) { - self.0 -= other.0; + *self = *self - other; } } impl SubAssign for Score { fn sub_assign(&mut self, rhs: ScoreType) { - self.0 -= rhs; + *self = *self - rhs; } } @@ -193,3 +212,14 @@ impl Shl for Score { Score(self.0 << rhs) } } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn add_assign() { + let mut right = Score::INF / 2; + right += Score::INF; + assert_eq!(right, Score::INF); + } +} From 56f91d60ef0e9bf4df8beb215c0c43c30f20c904 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 27 Mar 2025 23:17:52 -0400 Subject: [PATCH 04/12] chore: add code coverage CI --- .github/workflows/coverage.yml | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..2844f68 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,36 @@ +name: Code coverage + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +env: + CARGO_TERM_COLOR: always + +jobs: + generate-coverage: + runs-on: ubuntu-24.04 + steps: + - name: Rust nightly + run: rustup default nightly + - uses: actions/checkout@v4 + with: + lfs: true + - name: Install just + uses: extractions/setup-just@v2 + - name: Install grcov + run: | + sudo apt update + sudo apt upgrade -y + ssudo apt install -y grcov + - name: Install just + uses: extractions/setup-just@v2 + - name: Generate coverage + run: just coverage + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: target/debug/coverage/byte-knight.lcov From 40e18db2174b22326fbd28f42321c88c8a0ba852 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 27 Mar 2025 23:18:00 -0400 Subject: [PATCH 05/12] chore: update build CI --- .github/workflows/build_and_test.yml | 46 ++++++++++------------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 2cbb0df..4b546c5 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -2,40 +2,28 @@ name: Build and test on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] env: CARGO_TERM_COLOR: always jobs: - build-nightly: - + build: + strategy: + matrix: + runs-on: [ubuntu-22.04] + rust: [stable, nightly] + build-type: [debug, release] runs-on: ubuntu-22.04 - - steps: - - name: Rust nightly - run: rustup default stable - - uses: actions/checkout@v4 - with: - lfs: true - - name: Install just - uses: extractions/setup-just@v2 - - name: Run tests - run: just test - - build-stable: - - runs-on: ubuntu-22.04 - steps: - - name: Rust stable - run: rustup default stable - - uses: actions/checkout@v4 - with: - lfs: true - - name: Install just - uses: extractions/setup-just@v2 - - name: Run tests - run: just test + - name: Rust ${{ matrix.rust }} + run: rustup default ${{ matrix.rust }} + - uses: actions/checkout@v4 + with: + lfs: true + - name: Install just + uses: extractions/setup-just@v2 + - name: Run tests + run: just test ${{ matrix.build-type }} From 581cd545e129b7429363115a35a6da5b66ba4cfa Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 27 Mar 2025 23:18:13 -0400 Subject: [PATCH 06/12] chore: don't export coverage data for all tests --- Justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Justfile b/Justfile index 954254f..8fcae82 100644 --- a/Justfile +++ b/Justfile @@ -10,11 +10,11 @@ test config="debug": echo "Running tests..." cargo test --workspace --all-features {{ if config=="release" {"--release"} else {""} }} -- --include-ignored -export RUSTFLAGS:="-Cinstrument-coverage" export LLVM_PROFILE_FILE:="./target/coverage/byte_knight-%p-%m.profraw" coverage: (build "debug") echo "Running tests with coverage..." mkdir -p target/coverage + RUSTFLAGS="-Cinstrument-coverage" \ cargo test --workspace -- --skip "perft" grcov target/coverage engine/target/coverage chess/target/coverage -s . \ --binary-path ./target/debug/ --output-types lcov -o ./target/coverage/byte-knight.lcov \ From c9e34e8e81a52ca72abe04894e7f23baa9b9ef8c Mon Sep 17 00:00:00 2001 From: Paul T Date: Thu, 27 Mar 2025 23:19:42 -0400 Subject: [PATCH 07/12] fix: error in coverage CI Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 2844f68..69220d6 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -24,7 +24,7 @@ jobs: run: | sudo apt update sudo apt upgrade -y - ssudo apt install -y grcov + sudo apt install -y grcov - name: Install just uses: extractions/setup-just@v2 - name: Generate coverage From cf660cf03ca47c21dd38c02ce4471d3184b97d2d Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 27 Mar 2025 23:21:24 -0400 Subject: [PATCH 08/12] chore: add ignore reason --- engine/src/search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/search.rs b/engine/src/search.rs index a5397ff..ea0fd58 100644 --- a/engine/src/search.rs +++ b/engine/src/search.rs @@ -648,7 +648,7 @@ mod tests { } #[test] - #[ignore] + #[ignore = "Timing on this is not consistent when instrumentation is enabled"] fn do_not_exceed_time() { let mut board = Board::default_board(); let config = SearchParameters { From 513d36ff9ef7be97bfe49622b9eb55ce5b4a8266 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 27 Mar 2025 23:23:54 -0400 Subject: [PATCH 09/12] fix: don't apt upgrade in CI --- .github/workflows/coverage.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 69220d6..ddb0a9d 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -23,7 +23,6 @@ jobs: - name: Install grcov run: | sudo apt update - sudo apt upgrade -y sudo apt install -y grcov - name: Install just uses: extractions/setup-just@v2 From 83076dbf308360542bf029d2de6fba496dea3260 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 27 Mar 2025 23:29:03 -0400 Subject: [PATCH 10/12] fix: install missing tool --- .github/workflows/coverage.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ddb0a9d..dcc798c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -14,7 +14,9 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Rust nightly - run: rustup default nightly + run: | + rustup default nightly + rustup component add llvm-tools-preview - uses: actions/checkout@v4 with: lfs: true From bac728d21c70797b665cd536253460010ba91645 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 27 Mar 2025 23:39:23 -0400 Subject: [PATCH 11/12] chore: add codecov badge to README bench: 760228 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f03fb5d..1c5370a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@

byte-knight

+[![codecov](https://codecov.io/gh/DeveloperPaul123/byte-knight/graph/badge.svg?token=USEPKU8K4G)](https://codecov.io/gh/DeveloperPaul123/byte-knight) + `byte-knight` is a UCI compliant chess engine written in Rust. It started as a port of the chess engine I submitted for Sebatian Lague's [Chess Engine Challenge](https://github.com/DeveloperPaul123/Leonidas) where it placed in the top 32 out of 600+ entries. # Overview From 9242770f4454a3aaa085032997131c798312a4c0 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 27 Mar 2025 23:51:13 -0400 Subject: [PATCH 12/12] chore: add docs for Justfile bench: 760228 --- Justfile | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Justfile b/Justfile index 8fcae82..8cf0790 100644 --- a/Justfile +++ b/Justfile @@ -2,15 +2,22 @@ set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"] default: (build) +[group('dev')] +[doc('Build the project (default is debug)')] build config="debug": echo "Building the project..." cargo build --workspace --all-features {{ if config=="release" {"--release"} else {""} }} +[group('dev')] +[doc('Build and run tests (default is debug)')] test config="debug": echo "Running tests..." cargo test --workspace --all-features {{ if config=="release" {"--release"} else {""} }} -- --include-ignored export LLVM_PROFILE_FILE:="./target/coverage/byte_knight-%p-%m.profraw" + +[group('dev')] +[doc('Generate test coverage')] coverage: (build "debug") echo "Running tests with coverage..." mkdir -p target/coverage @@ -21,6 +28,8 @@ coverage: (build "debug") --branch --keep-only "src/*" --keep-only "engine/*" --keep-only "chess/*" \ --ignore "src/bin/byte-knight/*" --ignore "chess/src/perft*" +[group('dev')] +[doc('Purge files generated by @coverage')] purge-coverage: echo "Purging coverage data..." rm -rf *.profraw @@ -30,42 +39,65 @@ purge-coverage: rm -rf chess/*.profraw rm -rf engine/*.profraw +[group('dev')] +[doc('Run clippy')] lint: cargo clippy --all --all-features --tests -- -D warnings +[group('chess')] +[group('performance')] +[doc('Run sarch benchmark - required before committing for OpenBench.')] search-bench: echo "Running search benchmark..." cargo rustc --release --bin byte-knight -- -C target-cpu=native ./target/release/byte-knight bench +[group('chess')] +[doc('Run perft at a specified depth')] perft depth: echo "Running perft..." cargo run --release --bin perft -- -d {{ depth }} +[group('chess')] +[doc('Run perft over the EPD test suite')] perft-epd: echo "Running EPD perft test suite..." cargo run --release --bin perft -- --epd-file data/standard.epd +[group('chess')] +[group('performance')] +[doc('Run perft benchmark over the EPD test suite')] perft-bench: echo "Running perft benchmark..." cargo run --release --bin perft-bench -- -e data/standard.epd +[group('chess')] +[doc('Generate magic numbers use for magic bitboards')] magics: echo "Generating magics..." cargo run --release --bin generate_magics +[group('chess')] +[doc('Verify that generated Zobrist hashes are unique')] verify-zobrist: echo "Verifying Zobrist hash..." cargo run --release --bin verify_zobrist +[group('dev')] +[doc('Generate release binaries for given target.')] release target: echo "Building release binaries..." cargo rustc --release --bin byte-knight --target={{ target }} +[group('dev')] +[doc('Caches the release binary to bk-main for testing.')] cache-main: (build "release") echo "Caching binary for testing..." cp target/release/byte-knight ./bk-main +[group('dev')] +[group('chess')] +[doc('Run the engine against itself. Requires @cache-main to be run first and fastchess to be installed.')] compare-to-main engine1: (build "release") echo "Comparing {{ engine1 }} to bk-main" fastchess -engine cmd="{{ engine1 }}" name="dev" -engine cmd="./bk-main" name="bk-main" -openings file="./data/Pohl.epd" format=epd order=random -each tc=10+0.1 -rounds 200 -repeat -concurrency 8 -sprt elo0=0 elo1=5 alpha=0.05 beta=0.1 model=normalized -output format=cutechess