Update CLAUDE.md and ARCHITECTURE.md for Behavior/Archetype redesign and editor mode
Documents the replacement of Element with Behavior + Archetype, the elimination of the per-board element palette, the new map file format (archetype = "name" instead of passable = bool), the ErrorBlock sentinel, and the editor mode (AppMode, EditorState, palette panel, click-to-paint). Also updates "What's not yet implemented" to reflect editor progress and call out the Object behavior / glyph chooser / file open gaps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,16 +27,20 @@ cargo fmt # format
|
||||
|
||||
The game is a single Rust binary using **eframe 0.33 / egui 0.33** for the GUI. eframe drives a retained-mode UI: the `App::update` method is called every frame and is responsible for both drawing and responding to input.
|
||||
|
||||
`update` is structured in two panels:
|
||||
- `egui::TopBottomPanel::top` — menu bar (File → Exit)
|
||||
- `egui::CentralPanel::default` — game viewport; rendered with `ui.painter()`
|
||||
`update` is structured in phases:
|
||||
- Input handling — arrow keys move the player (Play mode only)
|
||||
- `egui::TopBottomPanel::top` — menu bar (File → Exit, Play/Edit mode toggle)
|
||||
- `egui::SidePanel::right` — archetype palette panel (Edit mode only; declared before CentralPanel)
|
||||
- `egui::CentralPanel::default` — game viewport; rendered with `ui.painter()`; click-to-paint in Edit mode
|
||||
|
||||
### Modules
|
||||
|
||||
**`src/game.rs`** — all core game types:
|
||||
- `Glyph` (`Copy`) — per-cell visual: `ch: char`, `fg/bg: Color32`. `Glyph::player()` is the only constructor used at runtime (colors for board tiles come from the map file).
|
||||
- `Element` — behavior: `passable: bool` (future: `opaque`, etc.)
|
||||
- `Board` — the complete game unit (ZZT-style "board"): `width`, `height`, `elements: Vec<Element>` (behavior palette), `cells: Vec<(Glyph, usize)>` (row-major; usize indexes into `elements`), `player: Player`, `objects: Vec<ObjectDef>`, `portals: Vec<PortalDef>`. `cells` is `pub(crate)`.
|
||||
- `Behavior` — plain data struct of runtime behavioral properties: `passable: bool`, `opaque: bool`. Returned by `Archetype::behavior()`; new properties added here require no match arms elsewhere.
|
||||
- `Archetype` (`Copy`, `PartialEq`) — enum of named element types: `Empty`, `Wall`, `Object`, `ErrorBlock`. Each variant provides `behavior()`, `name()` (used in map files), and `default_glyph()` (used by the editor when stamping a cell). `ErrorBlock` is a sentinel for unknown archetype names — renders as yellow `?` on red.
|
||||
- `ALL_ARCHETYPES: &[Archetype]` — ordered list of valid editor choices (excludes `ErrorBlock`).
|
||||
- `Board` — the complete game unit (ZZT-style "board"): `width`, `height`, `cells: Vec<(Glyph, Archetype)>` (row-major; each cell owns its visual and behavioral class directly), `player: Player`, `objects: Vec<ObjectDef>`, `portals: Vec<PortalDef>`. `cells` is `pub(crate)`.
|
||||
- `Player` — `x: i32, y: i32`
|
||||
- `ObjectDef` / `PortalDef` — parsed from map files, stored on Board; not yet runtime-wired
|
||||
- `GameState` — holds `board: Board`; `try_move(dx, dy)` checks passability before moving the player
|
||||
@@ -47,16 +51,18 @@ The game is a single Rust binary using **eframe 0.33 / egui 0.33** for the GUI.
|
||||
- `pub fn load(path: &str) -> Result<Board, Box<dyn std::error::Error>>` — reads and converts a `.toml` map file
|
||||
|
||||
**`src/main.rs`** — app entry point:
|
||||
- `AppMode` enum (`Play` | `Edit`) — gates arrow-key input; toggles the palette panel
|
||||
- `EditorState` — holds `selected: Archetype` (the archetype to paint on click)
|
||||
- Loads `maps/start.toml` before creating the window (so board dimensions drive window size)
|
||||
- Window min-size = `board.width * CELL_W + 2*PANEL_MARGIN` × `board.height * CELL_H + MENU_H + 2*PANEL_MARGIN`
|
||||
- Window min-size = `board.width * CELL_W + 2*PANEL_MARGIN` × `board.height * CELL_H + MENU_H + 2*PANEL_MARGIN` (sized for Play mode; Edit mode panel may clip at minimum size)
|
||||
- Board is centered in the CentralPanel when the window is larger than minimum
|
||||
- `draw_glyph(painter, origin, x, y, glyph)` draws one cell: filled rect (bg) + monospace char (fg)
|
||||
- Player is rendered on top of the board using `Glyph::player()`
|
||||
- Arrow key input handled via `ctx.input(|i| ...)` before panel rendering
|
||||
- Click-to-paint converts `mouse_pos - origin` to cell coordinates using the same math as rendering
|
||||
|
||||
### Map file format (`maps/*.toml`)
|
||||
|
||||
XPM-inspired: a `[palette]` maps single characters to `(Glyph, Element)` definitions; `[grid] content` is a TOML multi-line string where each character indexes the palette.
|
||||
XPM-inspired: a `[palette]` maps single characters to `(Glyph, Archetype)` definitions; `[grid] content` is a TOML multi-line string where each character indexes the palette.
|
||||
|
||||
```toml
|
||||
[map]
|
||||
@@ -66,8 +72,8 @@ height = 25
|
||||
player_start = [30, 12]
|
||||
|
||||
[palette]
|
||||
" " = { passable = true, ch = " ", fg = "#000000", bg = "#000000" }
|
||||
"#" = { passable = false, ch = "#", fg = "#808080", bg = "#606060" }
|
||||
" " = { archetype = "empty", ch = " ", fg = "#000000", bg = "#000000" }
|
||||
"#" = { archetype = "wall", ch = "#", fg = "#808080", bg = "#606060" }
|
||||
|
||||
[grid]
|
||||
content = """
|
||||
@@ -90,13 +96,15 @@ target_map = "cave"
|
||||
target_entry = "west_door"
|
||||
```
|
||||
|
||||
Colors are `"#RRGGBB"` hex strings. `player_start` is a header field — the player is not a board cell. The grid multi-line string's leading newline is trimmed by TOML; trailing newline is handled correctly by `str::lines()`.
|
||||
Colors are `"#RRGGBB"` hex strings. `player_start` is a header field — the player is not a board cell. The grid multi-line string's leading newline is trimmed by TOML; trailing newline is handled correctly by `str::lines()`. Unknown archetype names produce an `ErrorBlock` cell and a logged warning.
|
||||
|
||||
### Key design decisions
|
||||
|
||||
- **`cells: Vec<(Glyph, usize)>`** — the `usize` element index is intentionally an anonymous tuple field; it only has meaning relative to a specific `Board`'s `elements` palette and cannot be misused as a standalone value.
|
||||
- **`Behavior` and `Archetype` are separate types** — `Archetype` is the named class of a thing (`Wall`, `Empty`, `Object`); `Behavior` is its runtime properties (`passable`, `opaque`). Adding a new property means adding a field to `Behavior`, not a match arm at every call site.
|
||||
- **`cells: Vec<(Glyph, Archetype)>`** — each cell owns its visual and behavioral class directly; there is no per-board element palette or integer indirection. `Archetype` is `Copy` so this is efficient.
|
||||
- **Archetypes are referenced by name in map files** — so `ALL_ARCHETYPES` can be reordered or extended without breaking saved games.
|
||||
- **`Board` is the complete unit** — grid, player, objects, and portals all live on `Board`, matching how ZZT treats a "board". No separate wrapper struct.
|
||||
- **Glyph (visual) and Element (behavior) are decoupled** — each cell has its own `Glyph` (so colors can vary per-cell, e.g. fire flickering) but shares `Element` definitions from the palette.
|
||||
- **Glyph (visual) and Archetype (behavior) are decoupled** — each cell has its own `Glyph` (so colors can vary per-cell, e.g. fire flickering) while sharing an `Archetype` with other cells of the same type.
|
||||
- **File loading happens in `main()`** before the window is created, so board dimensions are available for window sizing.
|
||||
|
||||
eframe runs on its own event loop thread; do not assume single-threaded execution.
|
||||
|
||||
Reference in New Issue
Block a user