A high-performance chess engine written in Rust, using the classical alpha-beta pruning algorithm for best-move selection. The engine achieves ~264M positions/second in pure search and features UCI protocol support for integration with chess GUIs.
Clone this repository, and then run:
makeOnce installed, you can run the engine with chess, so long as you have the chess binary in your PATH (e.g. export PATH="$PATH:$HOME/.cargo/bin").
$ chess --help
A classical chess engine implemented in Rust β
Usage: chess <COMMAND>
Commands:
play Play a game against the computer
watch Watch the computer play against itself
pvp Play a game against another human on this computer
best-move Print the best move for a position, in algebraic notation
uci Start UCI (Universal Chess Interface) mode for chess GUIs
dev Developer tools: perft, search benchmark, Elo estimate, and puzzles
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print versionThe dev command groups the developer tools:
$ chess dev --help
Developer tools: perft, search benchmark, Elo estimate, and puzzles
Usage: chess dev <COMMAND>
Commands:
perft Count the positions at a depth (perft), and report the time
search-bench Run a quick alpha-beta search benchmark on a set of positions
elo Estimate the Elo rating of the engine with games against Stockfish
puzzles Run the puzzle suite and report the solve rates
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print helpEach command has its own help, for example chess play --help. The help shows the default value of each option.
The old command names (calculate-best-move, count-positions, benchmark-alpha-beta, determine-stockfish-elo, solve-puzzles) still work, but the help does not show them.
You can start a game from any valid chess position by specifying it in FEN (ForsythβEdwards Notation) format. For example:
chess play --fen "rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"This starts a game from the Sicilian Defense position after 1.e4 c5 2.Nf3. The default starting position is used if no FEN is specified.
The --fen parameter is available for the play, pvp, and watch commands. Each command will validate the FEN string and ensure it represents a legal chess position before starting the game.
There is also the option to calculate the best move from a given position. For example:
$ chess best-move --fen "1Q6/8/8/8/8/k1K5/8/8 w - - 0 1"
Qb3#This evaluates the position using the engine at a default --depth of 4, and writes the result to stdout in algebraic notation.
The engine supports the Universal Chess Interface (UCI) protocol, allowing it to integrate with external chess GUIs and online platforms:
chess uciThis starts UCI mode, where the engine reads UCI commands from stdin and responds on stdout. You can use this with popular chess GUIs like Arena, cutechess-cli, or for integration with online platforms like lichess.
The TUI color scheme can be customized by creating a tui_colors.toml file in the current working directory. Edit this file to change colors without rebuilding:
# Light squares (traditional wheat/beige)
light_square = 240, 217, 181
# Dark squares (traditional sienna/brown)
dark_square = 181, 136, 99
# White pieces (traditional white)
piece_white = 255, 255, 255
# Black pieces (traditional dark gray/black)
piece_black = 50, 50, 50Colors are specified as RGB values (0-255). If the file is missing or invalid, default colors are used. Changes take effect immediately on the next run - no rebuild required.
On an M1 MacBook Pro, the engine achieves approximately 264 million positions per second in pure depth-first search.
$ chess dev perft --depth 6
depth: 1, positions: 420, positions per second: 665610.1426307448
depth: 2, positions: 9322, positions per second: 42958525.34562212
depth: 3, positions: 206579, positions per second: 81234368.85568225
depth: 4, positions: 5070585, positions per second: 192060338.62353697
depth: 5, positions: 124064942, positions per second: 349045107.3455229
depth: 6, positions: 3317378318, positions per second: 262101541.1108804
total positions: 3446730166, total duration: 13.042077s, positions per second: 264277704.08041602
Builds with --features instrumentation also print the board clone and MoveGenerator creation counts.
This is a pure depth-first search of all possible positions - no pruning is applied.
In real games, the engine uses alpha-beta pruning to find the best move. Alpha-beta pruning uses the evaluation function to skip branches of the search tree. From the starting position, the engine reaches depth 10 in less than 1 second.
For gameplay performance on curated positions, use the dev search-bench command:
$ chess dev search-bench --depth 6
======================================================================
Alpha-Beta Performance Benchmark (depth: 6, parallel: false)
======================================================================
...
======================================================================
SUMMARY
----------------------------------------------------------------------
Total nodes: 514,065
Total time: 0.50s
Avg speed: 1025k nodes/s
======================================================================At depth 10 from the starting position, the engine searches ~690K nodes in 0.57s. These figures vary by hardware. For the best performance, use the release build. The [profile.release] section of Cargo.toml sets its compiler optimizations.
To measure the engine's performance in actual gameplay, use the dev elo command. After each set of games, the Stockfish Elo goes up or down by 25, until the engine scores about 50%. Then the command reports the rating.
chess dev elo --depth 6 --starting-elo 2000At alpha-beta search depth 6, you can observe the engine winning against Stockfish playing at a 2000 ELO.
The engine employs a sophisticated combination of algorithms and optimizations to achieve high performance:
-
Bitboard representation with magic bitboards for sliding pieces (rooks, bishops, queens) enables O(1) attack generation via precomputed lookup tables. The board state uses 64-bit integers for efficient bitwise operations and newtype-wrapped u8 indices for type-safe square indexing.
-
Alpha-beta search with iterative deepening, aspiration windows, and quiescence search. Iterative deepening searches at increasing depths (1..target), using transposition table results to improve move ordering at each level. Aspiration windows narrow the search window around the previous depth's score to reduce nodes. Quiescence search extends beyond the nominal depth for tactical moves to avoid the horizon effect.
-
Pruning makes the search tree much smaller:
- Null move pruning: the engine skips a turn to find positions that are too good to search.
- Reverse futility pruning: at shallow depths, the engine prunes a node when the static evaluation is far above the bound.
- Futility pruning: the engine skips quiet moves that cannot reach the bound.
- Late move reductions with logarithmic scaling: the engine searches later moves at a lower depth.
-
Check extensions extend search depth by 1 ply when in check, preventing the horizon effect from hiding tactical sequences.
-
Transposition tables cache position evaluations by Zobrist hash, so the search does not evaluate a transposed position again. The table has a fixed size (64MB default) and needs no locks. Each bucket has two slots: one depth-preferred slot and one always-replace slot. Each entry stores the score, depth, bound type (exact, upper, or lower), and the best move for move ordering.
-
Move ordering puts the moves that most often cause cutoffs first:
- The principal variation move from the transposition table.
- Killer moves, in thread-local storage, so threads do not wait for locks.
- Captures in MVV-LVA order (Most Valuable Victim, Least Valuable Attacker).
- Quiet moves by the history heuristic.
Interior nodes select the next best move only when the search needs it (pick-best). A full sort would also sort the moves that a beta cutoff skips.
-
Parallel search with thread-local killer move storage enables lock-free parallelization at the root level. Move generation uses conditional cloning (only when parallelizing) and MoveGenerator sharing to minimize allocations.
-
Zobrist hashing: the precompile build script generates the hash tables at compile time. The engine updates the position hash after each move, so hash lookups are fast.
-
Tapered evaluation blends middlegame and endgame scores by the remaining material (phase 0-24). It scores these features:
- Material values and piece-square tables (separate middlegame and endgame tables for all 6 piece types).
- Pawn structure: passed, doubled, isolated, backward, and connected pawns.
- Piece activity: bishop pair, knight outposts, and rooks on open files and on the 7th rank.
- King safety: pawn shield, open file penalties, and knight attack units.
- Mobility of knights and bishops, from magic bitboard lookups.
-
Generic trait-based architecture: the alpha-beta algorithm is a game-agnostic search that uses Rust traits. This keeps the search logic separate from the chess code, and lets the tests check the search on a simpler game.
-
Simple TUI built with ratatui and crossterm provides real-time game visualization with customizable colors. UCI protocol support enables integration with external chess GUIs and online platforms like lichess.
RustChess/
βββ common/ # Shared code between engine and precompiler
β βββ src/bitboard/ # Bitboard and Square types
βββ precompile/ # Build-time code generation
β βββ src/zobrist/ # Zobrist hash table generation
β βββ src/magic/ # Magic bitboard calculation
β βββ src/book/ # Opening book generation
βββ src/ # Main engine implementation
βββ prelude.rs # Common type re-exports
βββ alpha_beta_searcher/ # Generic search algorithm
βββ chess_search/ # Chess-specific search implementations
βββ board/ # Board state representation
βββ chess_move/ # Move types and application
βββ move_generator/ # Legal move generation
βββ evaluate/ # Position evaluation
βββ game/ # Game loop and engine coordination
βββ book/ # Opening book lookup
βββ input_handler/ # Move input parsing
βββ cli/ # Command-line interface
βββ tools/ # Developer tools (perft, benchmarks, Elo, puzzles)
βββ uci/ # UCI protocol implementation
βββ tui/ # Terminal user interface
βββ diagnostics/ # Memory profiling and diagnostics
Key directories:
-
common- Shared types between engine and precompiler:Bitboard(64-bit integer for sets of squares) andSquare(newtype-wrapped u8 for individual squares). -
precompile- Build-time code generation:ZobristHashTabletables and magic bitboard calculation (see this for background). -
src- Main engine implementation:prelude- Common types re-exported for convenience (Board,Color,Piece,ChessMove,Bitboard,Square)alpha_beta_searcher- Generic alpha-beta search algorithm, independent of chesschess_search- Chess-specific trait implementations for the search algorithmboard- Chess board state representation, FEN parsing and serialization, including newtype wrappers (CastleRights,HalfmoveClock,FullmoveNumber) and state management (StateStack)chess_move- Chess move types and application logicmove_generator- Chess move generation with magic bitboards, and check, checkmate, and game ending rulesevaluate- Tapered position evaluation (material, piece-square tables, pawn structure, piece activity, king safety, mobility)game- Game loop and engine coordination, with separateInputSourceandGameRenderertraits for modularitybook- Opening book lookup for move suggestionsinput_handler- Move input parsing (coordinate and algebraic notation)cli- Command-line interface (clap 4)tools- Developer tools that thechess devcommands runuci- UCI protocol implementation for GUI integrationtui- Terminal user interface with ratatuidiagnostics- Memory profiling and performance diagnostics
For information on development setup, architecture, code standards, and profiling/optimization workflows, see CONTRIBUTING.md.
