Skip to content
willmccallionPublic

About

UCI-compliant chess engine in Rust. Magic bitboards for O(1) move generation, PVS alpha-beta search with transposition tables, and NNUE evaluation. 3500+ ELO.

Resources

Stars

1 star

Watchers

1 watching

Forks

Latest commit

 

History

113 Commits

Folders and files

Repository files navigation

Chess Engine in Rust

This is a chess engine written in Rust. It’s designed to be simple, correct, and reasonably strong, while still being easy to experiment with and extend.

You can interact with the engine in several ways:

  • Play it in your terminal with the mouse, with its evaluation shown or hidden.
  • Watch it play itself.
  • Connect it to a graphical chess program (Arena, Scid vs. PC, Cute Chess, etc.) and use a visual board via UCI.

Features

  • Standard Chess Rules
    Implements all standard rules, including castling, pawn promotion, en passant, threefold repetition, and the fifty-move rule.

  • Smart Move Search
    Uses alpha–beta search (with typical pruning and move-ordering techniques) to look ahead and find strong moves.

  • Opening Book
    Uses a Polyglot opening book to play many common openings confidently from move one.

  • Move Generation Testing (perft)
    Includes a perft tool to verify the correctness of move generation by counting all legal move sequences to a given depth.

  • UCI Protocol Support
    Speaks the Universal Chess Interface (UCI) protocol, so it works with most modern chess GUIs.

  • NNUE Evaluation
    Evaluates positions with Stockfish 18's networks (see NNUE below).


Strength

Roughly 3650–3750 Elo on the CCRL Blitz scale (single thread). This is an estimate from local 200-game matches at 60+0.6, not an official rating:

Opponent (CCRL Blitz) Result Implied rating
Carp 3.0.1 (3526) 143–57, +160 ± 30 ≈3690
Sirius 9.0 (3529) 148.5–51.5, +184 ± 32 ≈3710
Stockfish 18, 1 thread (3772) 76.5–123.5, −83 ± 21 ≈3690

Building

The repository ships a Nix flake. With direnv and nix-direnv, run direnv allow once; otherwise use nix develop. The dev shell provides:

  • the stable Rust toolchain pinned in rust-toolchain.toml (with clippy, rustfmt, rust-analyzer),
  • the Stockfish 18 networks ($CHESS_NNUE_BIG, $CHESS_NNUE_SMALL), whose paths are baked into the binary at build time,
  • Stockfish 18 ($STOCKFISH) for matches and comparisons,
  • opening suites: $CHESS_BOOK_UHO (UHO Lichess, for engine-vs-engine testing) and $CHESS_BOOK_BALANCED (noob 3-moves),
  • the Baron Polyglot book, linked to moves/book.bin,
  • perf and Python with python-chess for the scripts in tools/.

Build in release mode:

cargo build --release

The executable is target/release/chess. A profiling profile (release plus debug info) is available for perf:

cargo build --profile profiling
perf record -g target/profiling/chess bench 16

How to Use

All examples below assume you’re running commands from the project root.

Playing in the Terminal

play opens a board in the terminal. Click or drag pieces with the mouse, or press Enter and type a move (e2e4, Nf3, O-O). To castle, move the king two squares or onto its rook.

# Casual: the engine's evaluation, analysis of your position and takebacks are available
cargo run --release -- play

# A real game: no engine output and no takebacks until it ends, 5 minutes + 3 seconds each
cargo run --release -- play --mode game --clock 5+3 --side black

# Watch the engine play itself
cargo run --release -- play --mode watch --movetime 1000

Without --clock the engine thinks for --movetime milliseconds per move (default 3000). --threads and --hash set the engine's resources, and --fen starts from another position. Press ? for the keys. You can hide the engine panel (e) and the move list (l), turn analysis on your turn on or off (a), flip the board (f), and switch the pieces between pictures, symbols and letters (p). On quitting, the game's moves and final position are printed.


Learning with the coach

play --mode learn opens a coach that teaches openings, sets puzzles and endgame drills, plays you at your level and reviews your games:

cargo run --release -- play --mode learn
  • Play: a game against the coach's bot, at the level that matches your rating or any of ten levels. The bot plays human-like moves, mistakes included. The optional blunder check stops you before a move that gives away material or allows mate, and says why. You can also have the bot steer into a chapter of your repertoire.
  • Review: after each game the engine goes through every move, marks inaccuracies (?!), mistakes (?) and blunders (??), and explains each one: a piece left hanging, a threat ignored, a fork walked into, a mate or free material missed. It shows what happens after the move and what the better move leads to, with the material each line wins or loses, and how the positions differ (castling, pawn cover, pawn weaknesses, development, room for the pieces). Press t or b to play either line through on the board.
  • Puzzles: every mistake and blunder from your games becomes a puzzle, and comes back on a spaced-repetition schedule until you get it right. Puzzles from the Lichess database fill the gaps, picked near your puzzle rating and weighted towards the themes behind your mistakes.
  • Openings: a repertoire built on 1.d4 and 2.c4 as White, and 1...e5 and 1...d5 as Black, with a note on every move. A lesson walks through a line; a drill has you play your side from memory, and lines come back for review on a schedule.
  • Endgames: the basic mates, king-and-pawn endings, and the Lucena and Philidor positions, against the engine's best defence.
  • Progress: your rating and puzzle rating, accuracy by phase of the game, mistakes per game by cause with trends, opening habits, and what to work on.

Your games and practice are kept in ~/.local/share/chess/coach (or $XDG_DATA_HOME/chess/coach); --coach-dir uses another directory. To add Lichess puzzles, download lichess_db_puzzle.csv.zst from database.lichess.org and import it once:

zstd -dc lichess_db_puzzle.csv.zst | cargo run --release -- import-puzzles -

This keeps a sample of about 50,000 popular puzzles rated 400 to 2400.


Using the Engine with a Chess GUI (UCI)

To use this engine with a graphical interface, start it in uci mode:

cargo run --release -- uci

Then, in your favorite chess GUI:

  1. Open the engine management/settings dialog.
  2. Add a new UCI engine.
  3. When prompted for the engine executable, point to the binary (for example, target/release/chess).
  4. Save the configuration and start a game using this engine.

The CoachLevel option (1 to 10, 0 for off) makes the engine play like one of the coach's levels in learn mode instead of at full strength.


Testing Move Generation (perft)

The perft command is a debugging tool that counts all possible legal move sequences up to a given depth. This is an effective way to verify that the engine’s understanding of the rules is correct.

From the standard starting position:

# Calculate all possible legal moves up to a depth of 5 from the starting position
cargo run --release -- perft 5

From a custom FEN position:

cargo run --release -- perft 4 --fen "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1"

Benchmarking

bench searches 30 fixed positions to a fixed depth and prints the total node count and speed. The node count is a signature of the search: a refactor that should not change behavior must leave it unchanged.

cargo run --release -- bench          # depth 13, 16 MB hash
cargo run --release -- bench 16 --hash 64

Matches and SPRT testing

challenge plays two UCI engines against each other. With --openings, each sampled opening is played twice with colors swapped, results are reported as pentanomial pair statistics, and --sprt stops as soon as the test is decided.

# New build vs a saved older build, stop when +0..+5 Elo is decided
target/release/chess challenge target/release/chess versions/old \
  --e1-option OwnBook=false --e2-option OwnBook=false \
  --time 10000 --inc 100 --rounds 20000 --concurrency 8 \
  --openings "$CHESS_BOOK_UHO" --sprt 0,5

# Against Stockfish at a limited strength
target/release/chess challenge target/release/chess "$STOCKFISH" \
  --e1-option OwnBook=false --e2-option UCI_LimitStrength=true --e2-option UCI_Elo=3000 \
  --time 60000 --inc 600 --rounds 100 --concurrency 8 --openings "$CHESS_BOOK_BALANCED"

Tuning search parameters (SPSA)

The search margins and reductions are runtime parameters (chess params lists them with their defaults, ranges and steps; UCI setoption accepts them by name). chess tune adjusts them with SPSA: each game pair perturbs every parameter up or down, plays the two versions against each other with colors swapped, and moves the estimate toward the winner. The current estimate is written to --output after every 20 pairs and can be continued with --resume.

taskset -c 0-15 chess tune --openings "$CHESS_BOOK_UHO" --pairs 10000 --time 5000 --inc 50 --concurrency 8 --output spsa.txt

Pin the run to one core type: the two engines of a pair should run at the same speed.

Comparing analysis with Stockfish

compare analyses positions with this engine and a reference engine (Stockfish by default) and reports both scores, best moves, the score gap and static evaluations, plus a summary over all positions.

cargo run --release -- compare --fen "r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16"
cargo run --release -- compare --epd "$CHESS_BOOK_BALANCED" --count 50 --depth 14

Perft against Stockfish

tools/perft_test.py compares perft --divide with Stockfish's go perft on a suite of tricky positions:

python3 tools/perft_test.py --stockfish "$STOCKFISH"

NNUE

The evaluation is a port of Stockfish 18's: the big network nn-c288c895ea92.nnue (HalfKAv2_hm piece-square plus FullThreats attacker–target inputs, 1024 wide, 8 layer stacks chosen by piece count) and the small network nn-37f18f62d772.nnue (piece-square only, 128 wide), combined exactly as Stockfish does: the small net for lopsided material with a big-net recheck near equality, complexity damping, material scaling and 50-move damping. Scores are converted to centipawns with Stockfish's WDL normalisation.

The networks are read at runtime. The default paths come from the dev shell at build time; the UCI options EvalFile and EvalFileSmall override them before the first isready.

Accumulators are updated lazily and incrementally: each move records its piece changes and the attacker–target relations it creates and destroys, king moves refresh from a per-king-square cache, and the dense layers use AVX2 (AVX-VNNI when available) with a sparse first layer.

Choosing the evaluator

--eval stockfish (the default) uses the networks above; --eval bootstrap uses a small hand-written evaluation that plays the first games of our own network's training data. Both are also available through the UCI option Evaluator. Building with --no-default-features leaves the Stockfish code out entirely.

chess eval-trace <fen> prints the same per-bucket table and final evaluation as Stockfish's eval command, and tools/nnue_check.py diffs the two over random playout positions:

python3 tools/nnue_check.py --stockfish "$STOCKFISH" --epd "$CHESS_BOOK_UHO" --count 3000

Acknowledgements

  • Opening Book The opening book data is sourced from The Baron's Polyglot Opening Book, available via the Chess Programming Wiki.

  • Stockfish The networks are Stockfish's, and the network format, input features and evaluation follow the Stockfish 18 source (GPLv3).

License

This program is free software, released under the GNU General Public License version 3 or (at your option) any later version. See LICENSE.

About

UCI-compliant chess engine in Rust. Magic bitboards for O(1) move generation, PVS alpha-beta search with transposition tables, and NNUE evaluation. 3500+ ELO.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages