Add game framework tests and planning docs
This commit is contained in:
@@ -0,0 +1,448 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,382 @@
|
||||
# UI Move List and Playback Mode Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Add UI support for:
|
||||
|
||||
- A fixed-size move list panel on the left side of the board.
|
||||
- Two move columns: White move and Black move, with turn number on the left.
|
||||
- Standard PGN/SAN notation display.
|
||||
- Scroll support when the move list is longer than the visible panel.
|
||||
- A move-list button that copies the current game as PGN to the clipboard.
|
||||
- A playback-only vertical analysis bar between the board and move list.
|
||||
- A board-orientation control that can render from White's perspective or Black's perspective.
|
||||
- A new playback mode where pieces cannot be moved, and the user can step through a loaded game.
|
||||
- In edit mode, a UI control to choose whose move is next.
|
||||
- Mode-specific paste behavior:
|
||||
- Edit mode: `Ctrl+V` pastes FEN.
|
||||
- Playback mode: `Ctrl+V` pastes PGN.
|
||||
- Play mode: no FEN/PGN paste.
|
||||
|
||||
## Collaboration rule
|
||||
|
||||
The assistant should manage only UI/application-side changes for this feature.
|
||||
|
||||
Do **not** edit files under `src/chess/` unless explicitly requested.
|
||||
|
||||
When UI work needs chess-layer functionality, stop and describe the required chess API/data shape so the project owner can implement it.
|
||||
|
||||
## Current state
|
||||
|
||||
- `BoardState` owns the current chess position.
|
||||
- `src/chess/game.zig` has a basic `Game` framework with:
|
||||
- `initial_state`
|
||||
- `state`
|
||||
- `moves`
|
||||
- `time_control`
|
||||
- `clocks`
|
||||
- `status`
|
||||
- UI currently uses `current_state` directly in `src/main.zig`.
|
||||
- Existing modes are:
|
||||
- `play`
|
||||
- `edit`
|
||||
- Existing paste behavior parses clipboard as FEN regardless of mode.
|
||||
- FEN copy is available via `Ctrl+C`.
|
||||
- Desired copy behavior: `Ctrl+C` should continue to copy FEN in every mode. PGN copy should use an explicit move-list button, not replace `Ctrl+C`.
|
||||
|
||||
## Desired architecture
|
||||
|
||||
Eventually, UI should treat `Game` as the game/session model:
|
||||
|
||||
```text
|
||||
UI/App
|
||||
├── current mode: play | edit | playback
|
||||
├── active Game
|
||||
├── playback cursor / ply index
|
||||
├── move list scroll offset
|
||||
├── board orientation: white perspective | black perspective
|
||||
├── analysis display state for playback mode
|
||||
└── rendering state
|
||||
```
|
||||
|
||||
The chess layer should provide enough data for the UI to display moves and reconstruct positions, but the UI should not implement chess notation rules itself.
|
||||
|
||||
## Stage 0: Board orientation and edit-side-to-move controls
|
||||
|
||||
### UI work
|
||||
|
||||
- Add app/UI state for board orientation:
|
||||
|
||||
```text
|
||||
white perspective: current rendering
|
||||
black perspective: visually flipped board, with White pieces/ranks at the top and Black pieces/ranks at the bottom
|
||||
```
|
||||
|
||||
- This must be a purely visual transform.
|
||||
- Keep the same chess square numbering and logical coordinates:
|
||||
- `a1` is still square `0`
|
||||
- White still starts on ranks 1 and 2 logically
|
||||
- Black still starts on ranks 7 and 8 logically
|
||||
- Update all board visual mappings consistently:
|
||||
- square rendering
|
||||
- piece rendering
|
||||
- coordinate labels
|
||||
- mouse hit-testing
|
||||
- hover/selection/check/checkmate highlights
|
||||
- valid/legal move dots
|
||||
- dragging source/target display
|
||||
- promotion popup placement
|
||||
- Add a UI control to toggle orientation, probably near the mode buttons or move list panel.
|
||||
- In edit mode, add a control to choose whose move is next:
|
||||
- White to move
|
||||
- Black to move
|
||||
- Changing side-to-move in edit mode should update only the current board state's active color/turn field and then rebuild rendering.
|
||||
- In non-edit modes, the side-to-move selector should either be hidden or disabled.
|
||||
|
||||
### Chess-layer needs
|
||||
|
||||
No chess-rule changes are required for board orientation. It is visual only.
|
||||
|
||||
For edit-side-to-move, the UI needs mutable access to the current position's active color. If `BoardState.turn` remains public, no new chess API is required. If you later make it private, add a small setter such as:
|
||||
|
||||
```zig
|
||||
pub fn setTurn(self: *BoardState, color: piece.Color) void
|
||||
```
|
||||
|
||||
## Stage 1: Add playback mode UI shell
|
||||
|
||||
### UI work
|
||||
|
||||
- Add `playback` to the app/input mode enum.
|
||||
- Add a third mode button, likely labeled `VIEW` or `PGN`/`REPLAY`.
|
||||
- In playback mode:
|
||||
- ignore board clicks/drags for moving pieces
|
||||
- no palette editing
|
||||
- no legal-move highlight generation
|
||||
- Keep reset behavior available or decide whether reset resets the active game/playback.
|
||||
|
||||
### Chess-layer needs
|
||||
|
||||
None.
|
||||
|
||||
## Stage 2: Make paste mode-specific
|
||||
|
||||
### UI work
|
||||
|
||||
Change clipboard paste behavior:
|
||||
|
||||
- If mode is `.edit`:
|
||||
- parse clipboard as FEN
|
||||
- replace current board state/game position
|
||||
- If mode is `.playback`:
|
||||
- send clipboard text to a future PGN import function
|
||||
- for now, log that PGN paste is not yet implemented if chess API is missing
|
||||
- If mode is `.play`:
|
||||
- ignore paste or log debug message
|
||||
|
||||
### Chess-layer needs
|
||||
|
||||
Eventually needed:
|
||||
|
||||
```zig
|
||||
pub fn loadPgn(allocator: std.mem.Allocator, pgn_text: []const u8) !Game
|
||||
```
|
||||
|
||||
or equivalent parse API.
|
||||
|
||||
For stage 2, this can be stubbed from the UI side with no chess changes.
|
||||
|
||||
## Stage 3: Move list panel with placeholder/static data
|
||||
|
||||
### UI work
|
||||
|
||||
- Define a fixed rectangle left of the board.
|
||||
- Render panel background and border.
|
||||
- Render column headers, for example:
|
||||
|
||||
```text
|
||||
# White Black
|
||||
```
|
||||
|
||||
- Add a visible PGN copy button inside or attached to the move list panel.
|
||||
- Clicking it should copy the current game PGN to the clipboard once chess-layer PGN export exists.
|
||||
- Until PGN export exists, the button can be visually present but log that export is not implemented.
|
||||
- Render rows using temporary/static strings or currently available move placeholders.
|
||||
- Establish layout constants:
|
||||
- panel left/right/top/bottom
|
||||
- row height
|
||||
- turn-number column x
|
||||
- white column x
|
||||
- black column x
|
||||
- max visible rows
|
||||
|
||||
### Chess-layer needs
|
||||
|
||||
None for placeholder rendering.
|
||||
|
||||
## Stage 4: Record/display move notation from played games
|
||||
|
||||
### UI work
|
||||
|
||||
- Replace direct `current_state.move(...)` calls with `game.makeMove(...)` once `Game` is wired into `main.zig`.
|
||||
- Read `game.moves` and draw move rows.
|
||||
- Update the move list after every played move.
|
||||
|
||||
### Chess-layer needs
|
||||
|
||||
The `Game` move history should expose display notation for each ply.
|
||||
|
||||
Suggested shape:
|
||||
|
||||
```zig
|
||||
pub const MoveRecord = struct {
|
||||
from: bitboard.Square,
|
||||
to: bitboard.Square,
|
||||
promotion: ?piece.PieceType,
|
||||
san: []const u8, // or fixed buffer / owned slice
|
||||
time_remaining: u32,
|
||||
};
|
||||
```
|
||||
|
||||
If avoiding heap allocation in each move record, possible alternatives:
|
||||
|
||||
```zig
|
||||
san: [16]u8,
|
||||
san_len: u8,
|
||||
```
|
||||
|
||||
The chess layer should generate SAN before mutating the board, because SAN depends on the pre-move position.
|
||||
|
||||
Needed API could be:
|
||||
|
||||
```zig
|
||||
pub fn makeMove(self: *Game, allocator: std.mem.Allocator, from: Square, to: Square, promotion: ?PieceType) !void
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```zig
|
||||
pub fn formatSanMove(allocator: std.mem.Allocator, state_before: BoardState, from: Square, to: Square, promotion: ?PieceType) ![]u8
|
||||
```
|
||||
|
||||
## Stage 5: Playback analysis bar
|
||||
|
||||
### UI work
|
||||
|
||||
- Add a vertical analysis bar between the board and the move list panel.
|
||||
- The analysis bar should be visible only in playback mode.
|
||||
- The bar is divided into a White section and a Black section.
|
||||
- When analysis is equal, the White/Black divide should be centered.
|
||||
- When White is winning, the White portion grows and the divide moves toward Black's side.
|
||||
- When Black is winning, the Black portion grows and the divide moves toward White's side.
|
||||
- The bar is purely display; it should not affect board state or move legality.
|
||||
- Initial placeholder behavior can render the bar at 50/50 until analysis data exists.
|
||||
- Later, map engine evaluation scores to a display fraction.
|
||||
|
||||
Suggested visual mapping:
|
||||
|
||||
```text
|
||||
score = 0.00 pawns -> 50% White / 50% Black
|
||||
score > 0, White better -> White section larger
|
||||
score < 0, Black better -> Black section larger
|
||||
```
|
||||
|
||||
Use a bounded/nonlinear mapping so very large evaluations do not immediately consume the whole bar. For example, later UI code could map centipawns to a normalized value with a clamp or sigmoid-like curve. Exact mapping can be tuned visually.
|
||||
|
||||
### Chess/engine-layer needs
|
||||
|
||||
No chess-layer changes are needed for placeholder rendering.
|
||||
|
||||
Eventually, playback analysis needs external UCI analysis data:
|
||||
|
||||
```text
|
||||
score cp <centipawns>
|
||||
score mate <moves>
|
||||
pv <principal variation moves>
|
||||
```
|
||||
|
||||
The UI only needs a normalized analysis value or raw score from the UCI analysis client.
|
||||
|
||||
## Stage 6: Scrolling move list
|
||||
|
||||
### UI work
|
||||
|
||||
- Track `move_list_scroll_row` or similar state in `main.zig`.
|
||||
- Add mouse wheel handling when cursor is over the move panel.
|
||||
- Clamp scroll offset between `0` and `max(0, total_rows - visible_rows)`.
|
||||
- Optional later: visual scrollbar.
|
||||
|
||||
### Chess-layer needs
|
||||
|
||||
None.
|
||||
|
||||
## Stage 7: Playback cursor and stepping
|
||||
|
||||
### UI work
|
||||
|
||||
- Add `playback_ply_index` app state.
|
||||
- In playback mode:
|
||||
- Right arrow: advance one ply
|
||||
- Left arrow: go back one ply
|
||||
- Home: first position
|
||||
- End: final position
|
||||
- Render the board for the current playback ply, not necessarily the final game state.
|
||||
- Highlight or mark the current row/ply in the move list.
|
||||
|
||||
### Chess-layer needs
|
||||
|
||||
Need a way to reconstruct board state at an arbitrary ply.
|
||||
|
||||
Possible API:
|
||||
|
||||
```zig
|
||||
pub fn stateAtPly(self: *const Game, ply_index: usize) board.BoardState
|
||||
```
|
||||
|
||||
or, if allocation/errors are involved:
|
||||
|
||||
```zig
|
||||
pub fn replayToPly(self: *const Game, ply_index: usize) !board.BoardState
|
||||
```
|
||||
|
||||
This can replay from `initial_state` through the first `ply_index` moves.
|
||||
|
||||
## Stage 8: PGN paste/import and PGN copy/export
|
||||
|
||||
### UI work
|
||||
|
||||
- In playback mode, `Ctrl+V` gets clipboard text and passes it to chess PGN parser.
|
||||
- Keep `Ctrl+C` as FEN copy in every mode.
|
||||
- Add click handling for the move-list PGN copy button.
|
||||
- When the PGN copy button is clicked, copy exported PGN to the clipboard.
|
||||
- On success:
|
||||
- replace active game with parsed game
|
||||
- set mode to playback or remain playback
|
||||
- set playback cursor to final ply or start; choose behavior intentionally
|
||||
- reset move-list scroll
|
||||
- On failure:
|
||||
- log a warning
|
||||
- keep current game unchanged
|
||||
|
||||
### Chess-layer needs
|
||||
|
||||
PGN parser that returns a `Game` with move history and final state, plus PGN export for the current game.
|
||||
|
||||
Suggested API:
|
||||
|
||||
```zig
|
||||
pub fn initFromPgn(allocator: std.mem.Allocator, pgn_text: []const u8, time_control: TimeControl) !Game
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```zig
|
||||
pub fn parsePgn(allocator: std.mem.Allocator, pgn_text: []const u8) !Game
|
||||
```
|
||||
|
||||
Suggested export API:
|
||||
|
||||
```zig
|
||||
pub fn formatPgn(allocator: std.mem.Allocator, game: Game) ![]u8
|
||||
```
|
||||
|
||||
or as a method:
|
||||
|
||||
```zig
|
||||
pub fn formatPgn(self: *const Game, allocator: std.mem.Allocator) ![]u8
|
||||
```
|
||||
|
||||
Parser should understand at least:
|
||||
|
||||
- move numbers: `1.`, `1...`
|
||||
- SAN moves
|
||||
- castling: `O-O`, `O-O-O`
|
||||
- captures: `x`
|
||||
- promotion: `=Q`, `=R`, `=B`, `=N`
|
||||
- check/checkmate suffixes: `+`, `#`
|
||||
- game termination markers: `1-0`, `0-1`, `1/2-1/2`, `*`
|
||||
- comments and tags can be added later if desired
|
||||
|
||||
Reference: Steven J. Edwards, *Portable Game Notation Specification and Implementation Guide*, especially movetext/SAN sections.
|
||||
|
||||
## Stage 9: Optional polish
|
||||
|
||||
- Auto-scroll move list to latest move in play mode.
|
||||
- Click a move row in playback mode to jump to that ply.
|
||||
- Show current move highlight.
|
||||
- Add scrollbar.
|
||||
- Show numeric eval next to the analysis bar.
|
||||
- Show best line/PV next to or below the move list.
|
||||
- Add richer PGN copy/export options, such as including/excluding tag pairs.
|
||||
- Display game result in the move panel.
|
||||
- Show time remaining per move later if clocks are implemented.
|
||||
|
||||
## Immediate next action
|
||||
|
||||
Start with Stage 0, then Stage 1 and Stage 2 in UI-only files:
|
||||
|
||||
- `src/board_input.zig`
|
||||
- `src/text_render.zig`
|
||||
- `src/main.zig`
|
||||
|
||||
Do not modify `src/chess/` for these stages.
|
||||
Reference in New Issue
Block a user