Skip to content

feat: rework just file and add code coverage generation #73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 28, 2025
46 changes: 17 additions & 29 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
37 changes: 37 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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
rustup component add llvm-tools-preview
- uses: actions/checkout@v4
with:
lfs: true
- name: Install just
uses: extractions/setup-just@v2
- name: Install grcov
run: |
sudo apt update
sudo 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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
/data/lichess_db_puzzle.csv
**/build/*
*.exe
**/.DS_Store
**/.DS_Store
*.profraw
*.profdata
*.lcov
66 changes: 59 additions & 7 deletions Justfile
Original file line number Diff line number Diff line change
@@ -1,51 +1,103 @@
set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"]

default: build
default: (build)

build:
[group('dev')]
[doc('Build the project (default is debug)')]
build config="debug":
echo "Building the project..."
cargo build --release --all
cargo build --workspace --all-features {{ if config=="release" {"--release"} else {""} }}

test:
[group('dev')]
[doc('Build and run tests (default is debug)')]
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 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
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 \
--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
rm -rf target/coverage
rm -rf chess/target
rm -rf engine/target
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 }}

cache-main: build
[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

compare-to-main engine1: build
[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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<center><h1> byte-knight </h1></center>

[![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
Expand Down
52 changes: 41 additions & 11 deletions engine/src/score.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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<ScoreType> 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<ScoreType> 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<ScoreType> 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<ScoreType> for Score {
fn sub_assign(&mut self, rhs: ScoreType) {
self.0 -= rhs;
*self = *self - rhs;
}
}

Expand Down Expand Up @@ -193,3 +212,14 @@ impl Shl<u32> 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);
}
}
1 change: 1 addition & 0 deletions engine/src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ mod tests {
}

#[test]
#[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 {
Expand Down