diff --git a/CLAUDE.md b/CLAUDE.md index 9fdcbeb..d005958 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,13 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project -`viberogue` is a roguelike game written in Rust (edition 2024). It is in early development with no dependencies yet. +`viberogue` is a ZZT-inspired game-making system written in Rust (edition 2024). The goal is a system where players can create games using a scripting language, similar to the classic DOS game ZZT. It uses **Rhai** for scripting (WASM-compatible, sandboxed, pure Rust). ## Commands ```bash cargo build # compile -cargo run # build and run +cargo run # build and run (loads maps/start.toml) cargo test # run all tests cargo test # run a single test by name (substring match) cargo clippy # lint @@ -23,8 +23,74 @@ The game is a single Rust binary using **eframe 0.33 / egui 0.33** for the GUI. `update` is structured in two panels: - `egui::TopBottomPanel::top` — menu bar (File → Exit) -- `egui::CentralPanel::default` — game viewport; use `ui.painter()` to draw directly +- `egui::CentralPanel::default` — game viewport; rendered with `ui.painter()` -As the codebase grows, game state will live on the `App` struct and game logic will be split into modules under `src/` (map generation, entities, etc.). Keep game logic separate from the egui drawing calls in `update`. +### 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` (behavior palette), `cells: Vec<(Glyph, usize)>` (row-major; usize indexes into `elements`), `player: Player`, `objects: Vec`, `portals: Vec`. `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 + +**`src/map_file.rs`** — map file loading: +- `MapFile` and friends — serde `Deserialize` types for TOML map files +- `impl From for Board` — converts a parsed file into a ready-to-use `Board` +- `pub fn load(path: &str) -> Result>` — reads and converts a `.toml` map file + +**`src/main.rs`** — app entry point: +- 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` +- 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 + +### 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. + +```toml +[map] +name = "Room Name" +width = 60 +height = 25 +player_start = [30, 12] + +[palette] +" " = { passable = true, ch = " ", fg = "#000000", bg = "#000000" } +"#" = { passable = false, ch = "#", fg = "#808080", bg = "#606060" } + +[grid] +content = """ +############################################################ +# # +############################################################ +""" + +[[objects]] # optional; parsed but not yet runtime-wired +x = 10 +y = 5 +script = """ + on_touch(|| { send_message("open"); }); +""" + +[[portals]] # optional; parsed but not yet runtime-wired +x = 59 +y = 12 +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()`. + +### 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. +- **`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. +- **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.