12 KiB
UCI Engine Architecture Plan
Goal
Support two families of executables:
-
GUI/UI executable
- Renders and tracks games.
- Lets humans play/edit/replay games.
- Can let people play against bots.
- Can spawn bot/engine executables as subprocesses on demand.
- Speaks UCI to engine subprocesses.
- Can optionally use a user-selected external UCI engine, such as Stockfish, for playback analysis.
- Eventually stores games in a database.
-
Bot/engine executables
- No GUI.
- Implement chess engine logic.
- Speak UCI over stdin/stdout.
- Can be launched by the GUI.
- Can also run as long-lived processes connected to external services such as lichess.org through a service adapter.
Important terminology
In UCI terminology, the engine is the side that implements the UCI command loop over stdin/stdout.
The GUI/app is the UCI controller/client:
GUI/controller
├── starts engine process
├── writes UCI commands to engine stdin
├── reads UCI responses from engine stdout
└── applies bestmove to the current game
The engine executable is the UCI engine/server-like process:
Engine
├── reads commands from stdin
├── maintains/searches position
├── writes info/bestmove responses to stdout
└── exits on quit
Avoid naming the GUI-side wrapper a "server" in code. Prefer names like:
UciEngineClient
UciController
EngineProcess
Desired long-term process architecture
┌─────────────────────┐
│ zig-chess-gui │
│ │
│ - Vulkan UI │
│ - game tracking │
│ - human input │
│ - playback │
│ - database later │
│ - UCI client │
└──────────┬──────────┘
│ UCI stdin/stdout
▼
┌─────────────────────┐
│ zig-chess-engine-* │
│ │
│ - evaluation │
│ - search │
│ - move selection │
│ - UCI command loop │
└─────────────────────┘
Future external-service architecture:
┌─────────────────────┐
│ lichess-adapter │
│ │
│ - lichess API │
│ - account/game I/O │
│ - UCI client │
└──────────┬──────────┘
│ UCI stdin/stdout or socket/process bridge
▼
┌─────────────────────┐
│ zig-chess-engine-* │
└─────────────────────┘
Key design principle:
GUI ───────────────┐
├── UCI ── engine executable
Lichess adapter ───┘
The engine should not know whether it is being used by the GUI, a test harness, or a lichess adapter.
Suggested source layout
Possible future layout:
src/
├── chess/ // board/game/FEN/PGN/legal moves
├── main.zig // current GUI entry point, or later src/gui/main.zig
├── uci/
│ ├── protocol.zig // shared UCI parse/format types
│ ├── client.zig // GUI/service side: controls engine process
│ └── server.zig // engine side: stdin/stdout command loop
├── engine/
│ ├── engine.zig // common engine interface
│ ├── eval.zig // static evaluation
│ ├── search.zig // search algorithms
│ └── perft.zig // move-generator validation tooling
└── bots/
├── random.zig // random/legal-move bot executable
├── material.zig // material-eval bot executable
└── search.zig // stronger search bot executable
This layout can evolve. The important boundary is that engine executables communicate with outside controllers through UCI.
Build targets
Eventually build.zig should produce multiple executables, for example:
zig-chess-gui
zig-chess-random-bot
zig-chess-material-bot
zig-chess-search-bot
The GUI target links rendering/UI code.
The engine targets should avoid linking Vulkan/windowing code.
Analysis engine policy
The GUI should support an optional external UCI engine for real-time analysis during game playback.
Primary intended use case:
Playback mode
├── user steps through game
├── GUI sends current position to analysis engine
├── analysis engine returns eval/PV/best line
└── GUI displays analysis to the user
This analysis engine is separate from project bot engines:
- It is for user-facing analysis only.
- It must not be used by project bot engines as their search/evaluation implementation.
- Project bots should have their own evaluation/search logic.
- The GUI should treat the analysis engine as just another external UCI process.
Stockfish licensing policy
Stockfish is GPL-3.0 licensed. Because this project is intended to be MIT licensed, do not bundle Stockfish directly into the executable or source distribution by default.
Preferred approaches:
-
User-provided engine path
- User installs Stockfish or another UCI engine separately.
- GUI stores/configures the path.
- GUI launches it as an external process.
-
Optional download flow
- GUI can offer to download Stockfish or guide the user through downloading it.
- The download should be explicit and optional.
- The UI should clearly identify the engine and its license before download/use.
- Keep downloaded engine files outside the MIT-licensed source tree/release bundle unless licensing obligations are intentionally handled.
-
Generic UCI analysis engine support
- Do not hard-code Stockfish as the only option.
- Any compatible UCI engine path should work.
Analysis engine UI/config needs
Eventually the GUI should provide:
- analysis engine executable path
- enable/disable analysis
- analysis depth or movetime
- optional thread/hash settings via UCI options
- current eval display
- principal variation display
- best move display
- start/stop analysis when stepping through playback
Suggested app-side abstraction:
AnalysisEngine
├── engine_process / UciEngineClient
├── executable_path
├── enabled
├── depth or movetime limit
├── latest_score
├── latest_pv
└── latest_bestmove
UCI protocol responsibilities
GUI/client side
The GUI should be able to:
- Spawn an engine executable.
- Send UCI commands.
- Read engine output asynchronously or incrementally.
- Track engine readiness.
- Track available engine options.
- Send current game position.
- Request a move/search.
- Stop a search.
- Shut down the engine process cleanly.
Common commands sent by GUI:
uci
isready
ucinewgame
position startpos
position startpos moves e2e4 e7e5
position fen <fen fields> moves ...
go depth 5
go movetime 1000
stop
quit
Common responses parsed by GUI:
id name <name>
id author <author>
option name <name> type <type> ...
uciok
readyok
info depth 3 score cp 20 nodes 1234 time 10 pv e2e4 e7e5
bestmove e2e4
bestmove e2e4 ponder e7e5
Engine/server side
Each engine executable should:
- Read one line at a time from stdin.
- Parse UCI commands.
- Maintain an internal position.
- Search when given
go. - Print
bestmove ...when done. - Print
info ...optionally during search. - Respond to
stoppromptly. - Exit on
quit.
First milestone: minimal UCI engine executable
Implement an engine executable that supports only:
Input:
uci
isready
quit
Output:
id name ZigChess Random Bot
id author WayfinderAK
uciok
readyok
No search or move generation required yet.
Purpose:
- Prove executable separation.
- Prove stdin/stdout loop.
- Prove GUI/test harness can launch and communicate with engine.
Second milestone: GUI-side engine process wrapper
Implement a UI/application-side wrapper that can:
- Start an engine executable by path.
- Send
uci. - Wait for
uciok. - Send
isready. - Wait for
readyok. - Send
quiton shutdown.
Suggested name:
UciEngineClient
or:
EngineProcess
This code belongs on the GUI/controller side, not inside the engine bot.
Third milestone: position and bestmove
Add UCI support for:
position startpos
position startpos moves e2e4 e7e5
position fen <fen> moves ...
go depth 1
bestmove <move>
For the first engine, go depth 1 can simply pick the first legal move or a random legal move.
Move format is UCI long algebraic coordinate format:
e2e4
e7e8q
e1g1
This is different from SAN/PGN notation.
Fourth milestone: reusable protocol module
Create shared parse/format helpers for UCI text:
src/uci/protocol.zig
Potential responsibilities:
- Parse GUI-to-engine commands.
- Parse engine-to-GUI responses.
- Format moves as UCI coordinate moves.
- Parse UCI coordinate moves into from/to/promotion.
- Format
position ...commands from aGamemove list.
Keep protocol parsing separate from subprocess management and separate from search/evaluation.
Fifth milestone: stronger engines
Once the protocol boundary works:
- random legal move engine
- material evaluation engine
- shallow search engine
- alpha-beta search engine
- improved move ordering
- transposition table
- time management
Each stronger engine can be its own executable or selected by options/config.
Future lichess adapter
Do not put lichess-specific code inside the engine.
Instead, later create a separate adapter/service that:
- talks to lichess.org APIs
- manages authentication/account events
- receives games/moves from lichess
- launches or connects to an engine executable
- sends positions/search commands via UCI
- sends engine moves back to lichess
This keeps engines reusable by:
- GUI
- lichess adapter
- local match runner
- tournament harness
- tests/benchmarks
Future playback analysis milestone
After the generic UCI client exists, add analysis support to playback mode:
- Let the user configure an external UCI engine path.
- Start that engine as an analysis process.
- Send
uci/isreadyhandshake. - When playback cursor changes, send:
position fen <current playback FEN>
go depth <n>
or:
position fen <current playback FEN>
go movetime <ms>
- Parse
infolines for score and PV. - Display the latest analysis in the UI.
- Send
stopwhen the user moves to another ply or disables analysis. - Send
quitwhen closing the app or changing engine path.
Keep this separate from bot-vs-human play. The analysis engine is an advisor for the user, not the implementation of project bots.
References
Primary UCI reference:
- Stefan Meyer-Kahlen, Universal Chess Interface (UCI) protocol, April 2006. Commonly distributed as
uci.txtand mirrored by chess-programming resources. Coversuci,isready,position,go,stop,quit,info,bestmove, and engine options.
Stockfish licensing/reference:
- Official Stockfish repository: https://github.com/official-stockfish/Stockfish
- Stockfish
Copying.txt: GNU General Public License v3.0, https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt - Because the project goal is MIT licensing, Stockfish should be user-provided or optionally downloaded rather than bundled by default.
Useful related notation distinction:
- UCI moves are coordinate moves such as
e2e4ore7e8q. - PGN/SAN moves are display/game-record notation such as
e4,Nf3,O-O,Qxf7#. - The GUI move list should display PGN/SAN.
- Engine communication should use UCI coordinate moves.