Skip to content
codyjkPublic

Latest commit

Β 

History

349 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

chess

CI

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.

Example of player playing against the engine

Installation

Clone this repository, and then run:

make

Once 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").

Usage

$ 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 version

The 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 help

Each 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.

Starting from a custom 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.

Calculating the best move from a given position

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.

UCI Protocol Support

The engine supports the Universal Chess Interface (UCI) protocol, allowing it to integrate with external chess GUIs and online platforms:

chess uci

This 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.

Customizing TUI Colors

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, 50

Colors 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.

Performance

Throughput

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.

Gameplay

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 2000

At alpha-beta search depth 6, you can observe the engine winning against Stockfish playing at a 2000 ELO.

Implementation details

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.

Codebase structure

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) and Square (newtype-wrapped u8 for individual squares).

  • precompile - Build-time code generation: ZobristHashTable tables 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 chess
    • chess_search - Chess-specific trait implementations for the search algorithm
    • board - 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 logic
    • move_generator - Chess move generation with magic bitboards, and check, checkmate, and game ending rules
    • evaluate - Tapered position evaluation (material, piece-square tables, pawn structure, piece activity, king safety, mobility)
    • game - Game loop and engine coordination, with separate InputSource and GameRenderer traits for modularity
    • book - Opening book lookup for move suggestions
    • input_handler - Move input parsing (coordinate and algebraic notation)
    • cli - Command-line interface (clap 4)
    • tools - Developer tools that the chess dev commands run
    • uci - UCI protocol implementation for GUI integration
    • tui - Terminal user interface with ratatui
    • diagnostics - Memory profiling and performance diagnostics

Contributing

For information on development setup, architecture, code standards, and profiling/optimization workflows, see CONTRIBUTING.md.