LazyChess is a fast and memory-efficient chess engine library. It offers full FIDE features like castling, en passant, and every draw detection. It also includes FEN/PGN serialization, opening detection, UCI Engine Communication, and analysis of every move with move classification analysis.
[!NOTE] LazyChess is still in its early stages of development. Your feedback and suggestions are very helpful for this library! Don't hesitate to visit the issues page if you have any problems with LazyChess!
Installation
Install with cargo:
Quick Start
use Game;
Output:
+------------------------+
8 | r . b q k b n r |
7 | p p p p . p p p |
6 | . . n . . . . . |
5 | . B . . p . . . |
4 | . . . . P . . . |
3 | . . . . . N . . |
2 | P P P P . P P P |
1 | R N B Q K . . R |
+------------------------+
a b c d e f g h
Opening : Some("Ruy Lopez")
Status : ongoing
FEN : r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3
PGN : 1. e4 e5 2. Nf3 Nc6 3. Bb5 *
Features
- Full FIDE rules: all piece types, castling, en passant, promotion
- Draw detection: 50-move rule, threefold repetition, insufficient material
- Notation: FEN & PGN import/export, SAN generation, UCI move format
- Opening book: built-in ECO table, load your own
openings.jsonat runtime - UCI: spawn and communicate with any UCI-compatible engine (Stockfish, etc.)
- Undo: full move history stack
- Analysis: Move classification with accuracy scoring
Examples
More complete examples are available in the examples/ folder:
| Example | Description |
|---|---|
basic |
New game, make moves, display board, undo |
board_inspection |
Access the board array and piece data |
fen_pgn |
FEN/PGN import and export |
game_status |
Checkmate, stalemate, and draw detection |
move_validation |
Legal move generation and validation |
uci_engine |
Connect to Stockfish, get best move, MultiPV analysis |
analysis |
Full game analysis with move classification |
Quick Documentation
UCI
LazyChess communicates with external UCI-compatible engines (Stockfish, Lc0, etc.) through a dedicated background thread that reads the engine's stdout without blocking your application. This design choice comes from the nature of chess engines themselves — a serious analysis at depth 20+ can take several seconds, and blocking the main thread (or an async executor) for that duration is impractical.
Why a thread-based approach?
- Dedicated resources: Each engine instance gets its own OS thread for stdout reading, ensuring lines are never dropped or delayed by other work happening in your program.
- No async dependency: You don't need Tokio or any async runtime. LazyChess works in synchronous codebases, CLI tools, game servers, or anywhere else without pulling in a heavy runtime.
- Backpressure-free: The reader thread buffers lines in an
mpscchannel. Your code reads from the channel at its own pace — there's no risk of the engine's output blocking the engine process itself.
[!NOTE] This is an experimental feature. Any errors or bugs may occur. Your suggestions will help us improve!
Quick Start
Spawn an engine, make a move, and ask for the best reply:
use ;
Output:
Spawning engine: stockfish
Engine : Stockfish 18
Author : the Stockfish developers (see AUTHORS file)
+------------------------+
8 | r n b q k b n r |
7 | p p p p p p p p |
6 | . . . . . . . . |
5 | . . . . . . . . |
4 | . . . . P . . . |
3 | . . . . . . . . |
2 | P P P P . P P P |
1 | R N B Q K B N R |
+------------------------+
a b c d e f g h
Engine plays : e7e5
+------------------------+
8 | r n b q k b n r |
7 | p p p p . p p p |
6 | . . . . . . . . |
5 | . . . . p . . . |
4 | . . . . P . . . |
3 | . . . . . . . . |
2 | P P P P . P P P |
1 | R N B Q K B N R |
+------------------------+
a b c d e f g h
Pondering
Pondering lets the engine think in the background while waiting for the opponent's reply. play() returns both the engine's chosen move and its suggested ponder move extracted directly from the bestmove e2e4 ponder e7e5 response — you never have to hardcode which move to ponder.
use ;
If the opponent plays a different move than expected, call ponder_miss() to abort the background search, then call play() again for a fresh search:
engine.ponder_miss.unwrap;
game.do_move.unwrap; // actual opponent move
let result = engine.play.expect;
println!;
Timeout
LazyChess enforces a read timeout on every blocking call to the engine to guard against hangs — for example if the engine crashes silently, takes unexpectedly long at a high depth, or is left in infinite search mode by mistake.
| Scope | Default | How to change |
|---|---|---|
| Engine-level (all operations) | 10 000 ms | engine.set_timeout(ms) |
| Per-search override | inherited | SearchConfig::builder().read_timeout_ms(ms) |
| Analyzer per-move | 60 000 ms | AnalyzerConfig::depth(n).with_timeout(ms) |
// Raise the engine-level timeout globally (affects handshake, isready, etc.)
engine.set_timeout; // 30 seconds
// Override for one deep search only — does not affect the engine default.
let config = builder
.depth
.read_timeout_ms // 2 minutes for this search
.build;
// Disable timeout entirely — useful during development or on slow hardware.
engine.set_timeout;
When the timeout is reached, the operation returns Err(UciError::Timeout) with the message: Engine did not respond in time.
Move Analysis
[!NOTE] This feature is experimental. Classification results may not be perfectly accurate in all positions. Feedback and bug reports are very welcome!
MoveAnalyzer analyses every move in a game by asking the UCI engine to evaluate positions before and after each move, then comparing those evaluations to determine how good or bad the move was. The result is a human-readable classification familiar from platforms like Chess.com and Lichess.
Classification Types
| Classification | Symbol | Meaning |
|---|---|---|
Brilliant |
!! |
An outstanding move, often a surprising sacrifice |
Great |
! |
A strong move that is hard to find |
Best |
The objectively top engine move | |
Excellent |
Very close to best with minimal loss | |
Good |
A solid move with small inaccuracy | |
Okay |
Playable but slightly imprecise | |
Inaccuracy |
?! |
A noticeable mistake that gives up some advantage |
Miss |
? |
A missed opportunity — a much better move was available |
Mistake |
? |
A significant error that worsens the position |
Blunder |
?? |
A serious error, often losing material or the game |
Forced |
Only one legal move was available | |
Book |
A known opening theory move | |
Risky |
!? |
Objectively fine but leads to sharp, dangerous play |
Usage
use ;
Output:

Filtering and Options
The builder returned by analyze_pgn() supports additional options:
use Side;
// Analyse only White's moves
let report = analyzer
.analyze_pgn
.side
.run?;
// Only include moves classified as Inaccuracy or worse
use ClassificationKind;
let report = analyzer
.analyze_pgn
.min_classification
.run?;
Partial Results on Engine Crash
.run() stops and returns an error the moment the engine fails, discarding all results collected so far. Use .run_partial() instead to recover whatever was analysed before the crash.
match analyzer
.analyze_pgn
.on_progress
.run_partial
run_partial() accepts all the same builder options as run():
analyzer
.analyze_pgn
.side
.min_classification
.run_partial
The PartialReport returned in Err contains:
report: a validGameReportof every move that succeeded before the crasherror: the underlyingChessErrorthat caused the abort
If you don't need partial results and just want to propagate the error, .run() is still the simpler choice.
Timeout for Deep Analysis
The default 60-second timeout per move is sufficient for most depths. For very deep searches (depth 22+) or slow hardware, raise it:
// 2 minutes per move
let config = depth.with_timeout;
License
MIT License
Copyright (c) 2026 Ditzzy LazyChess Authors
Copyright (c) 2026 LazyChess Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.