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
chess 1.0.0
A classical chess engine implemented in Rust β
USAGE:
chess <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
SUBCOMMANDS:
benchmark-alpha-beta Run a quick alpha-beta performance benchmark on a curated set of positions. Reports
nodes/sec, transposition table hit rate, and other metrics for fast iteration. Use
`--depth` (default: 4) and `--parallel` flag to test different configurations.
calculate-best-move Use the chess engine to determine the best move from a given position, provided in
FEN notation with `--fen` (required). You can optionally specify the depth of the
search with the `--depth` arg (default: 4).
count-positions Count the number of possible positions for a given `--depth` (default: 4), and
reports the time it took to do so. By default, this searches all possible positions.
The routine can be run with alpha-beta pruning by selecting `--strategy alpha-beta`.
determine-stockfish-elo Determine the ELO rating of the engine at a given `--depth` (default: 4) and
`--starting-elo` (default: 1000). The engine will increment the Stockfish ELO until
it plateaus at a 50% win rate, at which point the rating is reported.
help Prints this message or the help of the given subcommand(s)
play Play a game against the computer, which will search for the best move using alpha-
beta pruning at the given `--depth` (default: 4). Your starting color will be
chosen at random unless you specify with `--color`. The initial position can be
specified using FEN notation with `--fen` (default: starting position).
pvp Play a game against another human on this local machine. The initial position can be
specified using FEN notation with `--fen` (default: starting position).
uci Start UCI (Universal Chess Interface) mode for integration with external chess GUIs
like Arena, cutechess-cli, or lichess. Reads UCI commands from stdin and responds on
stdout.
watch Watch the computer play against itself at the given `--depth` (default: 4). The
initial position can be specified using FEN notation with `--fen` (default: starting
position).
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 calculate-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 count-positions --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
Board clones: 5285741
MoveGen creates: 1This is a pure depth-first search of all possible positions - no pruning is applied.
Alpha-beta pruning, which incorporates the engine's scoring heuristic to prune branches of the search tree, is used to search for the "best" move in actual gameplay. Using this approach, the engine achieves approximately 909,000 positions per second:
$ chess count-positions --strategy alpha-beta --depth 6
depth: 1, positions: 40, positions per second: 12187.69043266301
depth: 2, positions: 882, positions per second: 157500
depth: 3, positions: 8266, positions per second: 417580.1970194494
depth: 4, positions: 32373, positions per second: 828780.6251760068
depth: 5, positions: 136461, positions per second: 633673.7110457907
depth: 6, positions: 845113, positions per second: 1003543.4001204091
total positions: 1023135, total duration: 1.125216s, positions per second: 909278.7518129853Note that with alpha-beta, the number of positions searched is dramatically reduced (1.0M vs 3.4B) due to effective pruning, and the total search completes in just over a second. This demonstrates that the goal of alpha-beta isn't raw throughput, but rather finding the best move quickly by eliminating irrelevant branches. The low latency enables the engine to reach much higher search depths during actual gameplay.
For more realistic gameplay performance on curated positions, you can use the benchmark-alpha-beta subcommand:
$ chess benchmark-alpha-beta --depth 6
======================================================================
Alpha-Beta Performance Benchmark (depth: 6, parallel: false)
======================================================================
...
======================================================================
SUMMARY
----------------------------------------------------------------------
Total nodes: 6,030,428
Total time: 46.56s
Avg speed: 130k nodes/s
TT hit rate: 4.3%
======================================================================These figures vary by hardware. To achieve the best performance, make sure to use the release build, which leverages compiler optimizations:
To measure the engine's performance in actual gameplay, use the determine-stockfish-elo subcommand. This will increment the Stockfish ELO until it plateaus at a 50% win rate, at which point the rating is reported.
chess determine-stockfish-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 and quiescence search. Iterative deepening searches at increasing depths (1..target), using transposition table results to improve move ordering at each level. Quiescence search extends beyond the nominal depth for tactical moves to avoid the horizon effect.
- Transposition tables use an LRU cache (64MB default) to store position evaluations by Zobrist hash, avoiding redundant computation of transposed positions. Each entry stores score, depth, bound type (exact/upper/lower), and the best move for move ordering.
- Advanced move ordering prioritizes moves likely to cause cutoffs: principal variation moves from the transposition table, killer moves stored in thread-local storage (eliminating lock contention), and MVV-LVA (Most Valuable Victim - Least Valuable Attacker) for capture ordering.
- 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 tables are generated at compile time via the precompile build script, enabling incremental position hashing for efficient caching of move generation and transposition table lookups.
- Generic trait-based architecture implements the alpha-beta algorithm as a game-agnostic search using Rust traits, enabling clean separation between search logic and chess-specific implementations for comprehensive testing and maintainability.
- 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/ # FEN parsing and input handling
βββ cli/ # Command-line interface
βββ 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, including newtype wrappers (CastleRights,HalfmoveClock,FullmoveNumber) and state management (StateStack)chess_move- Chess move types and application logicmove_generator- Chess move generation with magic bitboardsevaluate- Position evaluation (material + piece-square tables)game- Game loop and engine coordination, with separateInputSourceandGameRenderertraits for modularitybook- Opening book lookup for move suggestionsinput_handler- FEN parsing and position validationcli- Command-line interface with subcommandsuci- 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.
