383 lines
11 KiB
Markdown
383 lines
11 KiB
Markdown
# 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.
|