449 lines
12 KiB
Markdown
449 lines
12 KiB
Markdown
# UCI Engine Architecture Plan
|
|
|
|
## Goal
|
|
|
|
Support two families of executables:
|
|
|
|
1. **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.
|
|
|
|
2. **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**:
|
|
|
|
```text
|
|
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**:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
UciEngineClient
|
|
UciController
|
|
EngineProcess
|
|
```
|
|
|
|
## Desired long-term process architecture
|
|
|
|
```text
|
|
┌─────────────────────┐
|
|
│ 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:
|
|
|
|
```text
|
|
┌─────────────────────┐
|
|
│ lichess-adapter │
|
|
│ │
|
|
│ - lichess API │
|
|
│ - account/game I/O │
|
|
│ - UCI client │
|
|
└──────────┬──────────┘
|
|
│ UCI stdin/stdout or socket/process bridge
|
|
▼
|
|
┌─────────────────────┐
|
|
│ zig-chess-engine-* │
|
|
└─────────────────────┘
|
|
```
|
|
|
|
Key design principle:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
1. **User-provided engine path**
|
|
- User installs Stockfish or another UCI engine separately.
|
|
- GUI stores/configures the path.
|
|
- GUI launches it as an external process.
|
|
|
|
2. **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.
|
|
|
|
3. **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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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 `stop` promptly.
|
|
- Exit on `quit`.
|
|
|
|
## First milestone: minimal UCI engine executable
|
|
|
|
Implement an engine executable that supports only:
|
|
|
|
Input:
|
|
|
|
```text
|
|
uci
|
|
isready
|
|
quit
|
|
```
|
|
|
|
Output:
|
|
|
|
```text
|
|
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 `quit` on shutdown.
|
|
|
|
Suggested name:
|
|
|
|
```zig
|
|
UciEngineClient
|
|
```
|
|
|
|
or:
|
|
|
|
```zig
|
|
EngineProcess
|
|
```
|
|
|
|
This code belongs on the GUI/controller side, not inside the engine bot.
|
|
|
|
## Third milestone: position and bestmove
|
|
|
|
Add UCI support for:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
e2e4
|
|
e7e8q
|
|
e1g1
|
|
```
|
|
|
|
This is different from SAN/PGN notation.
|
|
|
|
## Fourth milestone: reusable protocol module
|
|
|
|
Create shared parse/format helpers for UCI text:
|
|
|
|
```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 a `Game` move 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:
|
|
|
|
1. Let the user configure an external UCI engine path.
|
|
2. Start that engine as an analysis process.
|
|
3. Send `uci` / `isready` handshake.
|
|
4. When playback cursor changes, send:
|
|
|
|
```text
|
|
position fen <current playback FEN>
|
|
go depth <n>
|
|
```
|
|
|
|
or:
|
|
|
|
```text
|
|
position fen <current playback FEN>
|
|
go movetime <ms>
|
|
```
|
|
|
|
5. Parse `info` lines for score and PV.
|
|
6. Display the latest analysis in the UI.
|
|
7. Send `stop` when the user moves to another ply or disables analysis.
|
|
8. Send `quit` when 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.txt` and mirrored by chess-programming resources. Covers `uci`, `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 `e2e4` or `e7e8q`.
|
|
- 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.
|