diff --git a/DEMO.md b/DEMO.md deleted file mode 100644 index 9a855db..0000000 --- a/DEMO.md +++ /dev/null @@ -1,252 +0,0 @@ -# DEMO.md — "Morning Coffee" - -The `domain/` folder contains a 5-minute mini-game that exercises every -important `pncdsl` feature in one sitting — without the demo itself -outgrowing the library. The code is a good place to learn from, because -each mechanic appears in **exactly one small file**. - -## Story - -You've just woken up in your bedroom. The cat (Whiskers) is already -meowing in the kitchen, and you'd better make coffee before she tears -the whole apartment apart. - -## Running it - -```bash -go run . -``` - -A window opens at 1280×800 (internal resolution 320×200, 4× scale). No -asset files are needed — every graphic is a colored placeholder / -stylized humanoid until you replace it with real art (see -[`GFX.md`](GFX.md)). - -Controls: - -- **left click** on a hotspot/UI button: run the active verb -- **left click** on an inventory item: select it (the cursor picks it up) -- **left click** on a hotspot while an item is selected: use-with -- **right click**: deselect the held item, or reset the verb to "Look" -- click during dialog: advance -- **F1**: toggle the hotspot debug overlay (yellow rectangles + names on - every clickable region in the current scene) - -## Walkthrough (spoilers) - -1. **Bedroom** — the `intro` script runs two `Say` lines from the - `player`. -2. Click the **nightstand** with the "Take" verb → you receive `key`. -3. Click the **door** → you switch to the kitchen. -4. **Kitchen** — Whiskers is sitting in the corner. Try "Talk" on her — - a conditional dialog opens; one of the branches is only available - while the cupboard hasn't been opened yet. -5. Click the **shelf** with "Take" → you receive `mug`. -6. Click the **cupboard** with "Use" (key in inventory) → it opens, you - automatically receive `beans`, and the key is consumed. *Or* select - the key in the inventory and click the cupboard — `OnUseWith` does - the same thing. -7. Click the **coffee machine** with `beans` → beans loaded. -8. Click the coffee machine with `mug` → mug in place. -9. Once both flags are set, the coffee machine fires the `victory` - script: two characters talk, a sound plays (stubbed), end card → done. - -## Scenes - -| ID | Background | Music | Contents | -|------------|------------------|--------------|-----------------------------------------------------------| -| `bedroom` | `bg/bedroom` | `mus/wakeup` | bed, nightstand (key), door | -| `kitchen` | `bg/kitchen` | `mus/calm` | cupboard, shelf, coffee machine, cat (NPC), door | - -## Items - -| ID | Acquired from | Used for | -|---------|----------------------------------------------|---------------------------------------------------------------------| -| `key` | `bedroom.nightstand` → Take | Use on `kitchen.cupboard`, or as a selected item via `OnUseWith` | -| `beans` | `kitchen.cupboard` (after unlocking) | Selected, used on `kitchen.coffee_machine` | -| `mug` | `kitchen.shelf` → Take | Selected, used on `kitchen.coffee_machine` | - -## Characters - -- **`player`** — the player character. Walks (`Walk`), talks (`Say`). - Size 28×62 (W×H), roughly the SCUMM-style 30% screen height. -- **`cat`** — Whiskers, the household pet. Present in the kitchen, only - responds to `Talk`, has a dialog tree. - -Until you provide real sprites, they're rendered as a stylized humanoid -(skin-tone head + colored torso + dark trousers + feet) and quadruped -(side-view with ears, head, eye, tail) placeholder shape. - -## Dialog (`dialog.cat_morning.go`) - -``` -[start] -cat: Meeeoooow. It's late. - > "Coffee's coming right up." → SetFlag(promised_coffee), end - > "Did you eat the beans?" (only while NOT cupboard_open) → goto beans - > "Leave me alone." → end - -[beans] -cat: They're in the cupboard. They're always in the cupboard. - > "Thanks." → end -``` - -The `Show: p.Not(p.Flag(FlagCupboardOpen))` condition makes the "Did -you eat the beans?" choice disappear once you've opened the cupboard -— this demonstrates conditional choice visibility -(`DialogueChoice.Show`). - -## Cutscenes - -**`script.intro.go`** — wired up to `OnStart`, runs in the bedroom: - -```go -p.Seq( - p.Wait(0.4), - p.Say("player", "Brr, it's cold."), - p.Say("player", "I need coffee before my head explodes."), - p.Walk("player", p.Point{X: 200, Y: 145}), -) -``` - -**`script.victory.go`** — fired when the coffee machine sees both flags: - -```go -p.Seq( - p.Say("player", "All done!"), - p.PlaySound("snd/coffee_done"), - p.Wait(0.6), - p.Say("cat", "Mrrrrow. *content cat noises*"), - p.Say("player", "Morning coffee: saved."), - p.ShowEnd("The End — thanks for playing!"), -) -``` - -## Flag/var table (`domain.flag.go`) - -```go -const ( - FlagKeyTaken = "key_taken" - FlagCupboardOpen = "cupboard_open" - FlagMugTaken = "mug_taken" - FlagCoffeeHasBeans = "coffee_has_beans" - FlagCoffeeHasMug = "coffee_has_mug" - FlagPromisedCoffee = "promised_coffee" -) -``` - -Naming the flags as constants is worth it because the type system then -catches typos — `pncdsl.Flag("cupbord_open")` would otherwise silently -return `false` forever. - -## What the demo exercises - -| Library feature | Where it shows up | -|------------------------------------|----------------------------------------------------------------| -| `Scene` + `GoTo` | bedroom ↔ kitchen door, with fade transition | -| `Hotspot` with every verb | each scene has multiple hotspots: Look / Use / Talk / Take | -| `Inventory` + `Give` / `TakeAway` | picking up and consuming key, beans, mug | -| `RequireItem` semantics | cupboard checks `HasItem("key")` | -| `OnUseWith` (from the hotspot) | cupboard + key | -| `OnUseWith` (from the item) | coffee machine + beans / mug | -| `Flag` + `If` | cupboard "already open", shelf "already took one" | -| `Dialogue` + `Choice.Show` | cat's dialog tree, vanishing branch driven by a flag | -| `Script` / cutscene | intro (walk+say) + victory (multi-actor, sound, end card) | -| `Walk` with straight-line stepping | every interaction is preceded by a walk target | -| `ShowEnd` | last action of the victory script | -| `Validate()` | `domain/build_test.go` CI-friendly smoke test | -| `RegisterDefaultUI` + widget tree | `game.go` registers the SCUMM-style HUD widgets | -| Theme system (`classic-scumm`) | every color comes from the active theme; runtime-switchable | -| `HotspotDebug` widget | F1 in-game toggles a yellow outline over every clickable area | - -## Files in `domain/` - -``` -domain/ -├── game.go # Build() — calls every define*() in order -├── domain.asset.go # registerAssets(g) — all assets in one place -├── domain.flag.go # flag-name constants -│ -├── character.player.go # definePlayer(g) -├── character.cat.go # defineCat(g) -│ -├── item.key.go # defineKey(g) — OnUseWith: {"cupboard": ...} -├── item.beans.go # defineBeans(g) -├── item.mug.go # defineMug(g) -│ -├── dialog.cat_morning.go # defineCatMorning(g) — 2 nodes, 3+1 choices -│ -├── scene.bedroom.go # defineBedroom(g) -├── scene.kitchen.go # defineKitchen(g) -│ -├── script.intro.go # defineIntroScript(g) -├── script.victory.go # defineVictoryScript(g) -│ -└── build_test.go # Build() + Validate() smoke test -``` - -Adding a new scene/item: one new file + one line in `Build()` inside -`game.go`. There's no central registry list to edit. - -## How to extend it - -- **New scene**: create `scene..go` with a `define(g *p.Game)` - function that calls `g.SceneManager.Register(...)`. Add a call in - `Build()`. If any hotspot uses `GoTo("")`, `Validate()` will - cross-check the link for you. -- **New item**: same pattern, `item..go`, register into - `g.ItemManager`. Map keys inside `OnUseWith` must exactly match the - target hotspot's `Name` field. -- **New dialog**: `dialog..go` — `DialogueChoice.Show` accepts - anything that implements `Condition` (`Flag`, `HasItem`, `Not(...)`, - etc.). -- **New cutscene**: `script..go`, register into - `g.ScriptManager`, then fire it anywhere with - `p.RunScript("")`. - -## UI customisation - -The demo calls `p.RegisterDefaultUI(g)` for the classic SCUMM HUD, but -the whole UI is a tree of `Widget`s in `g.UIManager` — swap pieces or -add your own: - -```go -// 1. Verb coin instead of verb bar -p.RegisterRadialVerbUI(g) // alternative preset - -// 2. Or: start from the SCUMM default, then customise -p.RegisterDefaultUI(g) -g.UIManager.Remove("verbs") // drop the verb bar -g.UIManager.Register(&p.RadialVerbs{Name: "verbs", Radius: 40}) - -// 3. Add a custom widget — anything implementing pncdsl.Widget plugs in -g.UIManager.Register(&ChatPanel{Name: "chat", Bounds: p.Rect(220, 4, 96, 130)}) - -// 4. Change the theme at any point -g.UseTheme("terminal-green") // classic-scumm | sierra-coin | paper-notebook | terminal-green -``` - -The built-in widget catalogue (each in its own `pncdsl/ui..go`): -`Panel`, `StatusLine`, `VerbBar`, `RadialVerbs`, `InventoryBar`, -`SpeechBubble`, `DialogBox`, `EndCard`, `Cursor`, `HotspotDebug`. - -`Widget` is a three-method interface (`GetName`, `Tick`, `Draw`); see -[`UIPLAN.md`](UIPLAN.md) for the design rationale and a chat-panel -example. - -## Graphics - -The library substitutes a **deterministic colored rectangle** for every -missing image and renders characters as a stylized humanoid/quadruped -shape. The game therefore runs without any asset on disk. When you drop -a generated image into `assets/bg/.png` or -`assets/spr/.png`, the library switches to it immediately — no -code change required. - -Prompts and positioning tips for an image AI: [`GFX.md`](GFX.md). - -## Back to the library - -For the framework structure and the Manager pattern itself, see -[`README.md`](README.md) and [`PLAN.md`](PLAN.md). The widget-based UI -design is in [`UIPLAN.md`](UIPLAN.md). diff --git a/README.md b/README.md index e63cc3c..75bcf08 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,16 @@ single `*Game` root. The library handles input, rendering, dialog trees, cutscenes, HUD, themes and lazy asset loading; the domain only declares content. -This document is the full library reference. For a 5-minute tour of the -shipped demo, see [`DEMO.md`](DEMO.md). +This document is the full library reference. The companion `pncdsl-demo` +repo at is a small +end-to-end example ("Morning Coffee") that consumes this library as a +plain Go module. + +**Module path:** `git.teletype.hu/games/pncdsl` + +```go +import "git.teletype.hu/games/pncdsl" +``` --- @@ -81,8 +89,9 @@ fluent chains, no scattered registries. The same mental model applies to items, scenes, characters, dialogues, scripts, assets, verbs, widgets and themes alike. -A complete example game ("Morning Coffee") lives in `domain/` and is -described in [`DEMO.md`](DEMO.md). +A complete example game ("Morning Coffee") lives in the separate +[`pncdsl-demo`](ssh://git@git.teletype.hu:2222/games/pncdsl-demo) repo +and consumes this library as a regular Go module. --- @@ -91,18 +100,21 @@ described in [`DEMO.md`](DEMO.md). **Prerequisites.** Go 1.24+ (for generic type aliases) and a working OpenGL context. Ebitengine handles windowing and the render loop. -**Run the bundled demo:** +**Use the library in your own project:** ```bash -git clone -cd pncdsl -go run . +go mod init my-game +go get git.teletype.hu/games/pncdsl ``` -A window opens at 1280×800 (the library uses a 320×200 internal resolution -and Ebitengine scales it 4×). No asset files are required — placeholders -are generated for anything missing under `assets/`. Press **F1** at any -time to toggle the hotspot debug overlay. +If the private host can't be reached over HTTPS, point Go at the SSH +endpoint: + +```bash +git config --global \ + url."ssh://git@git.teletype.hu:2222/".insteadOf "https://git.teletype.hu/" +export GOPRIVATE=git.teletype.hu/* +``` **Minimum game:** @@ -111,7 +123,7 @@ package main import ( "log" - "pncdsl/pncdsl" + "git.teletype.hu/games/pncdsl" ) func main() { @@ -1519,10 +1531,13 @@ should crash loudly in development. ## 20. Testing ```bash -go test ./... +go build ./... ``` -`domain/build_test.go` is the canonical headless smoke test: +The library is single-package and has no in-tree unit tests at this +milestone — the manager + action primitives are intentionally small +enough that the `pncdsl-demo` repo's `build_test.go` (a headless +`Build() + Validate()` smoke test) covers the integration surface: ```go func TestBuildValidates(t *testing.T) { @@ -1530,16 +1545,14 @@ func TestBuildValidates(t *testing.T) { if err := g.Validate(); err != nil { t.Fatalf("validate: %v", err) } - // ... assert specific managers contain expected entries ... } ``` -It calls `Build()` (which registers every entity) and `Validate()` (which -cross-checks every name reference). No Ebiten window is opened — perfect -for CI. +Drop a similar test into any project that consumes the library — it +opens no window and finishes in milliseconds, so it's a cheap CI gate. -Library-internal unit tests are not shipped yet; the manager + action -primitives are intentionally small enough to verify through smoke runs +Library-internal unit tests are not shipped yet; the building blocks are +small enough to verify through smoke runs of the demo for the time being. ### 20.1 Useful runtime debug switches @@ -1553,87 +1566,88 @@ pncdsl.DebugLog = true // logf output to stderr (script queue, a ## 21. Project layout +All library source files live at the repo root; no nested package +directory. The naming convention is `theme.identifier.go` — `ls core.*` +or `ls ui.*` instantly groups related sources. + ``` -pncdsl/ -├── main.go # entry point: pncdsl.Run(domain.Build()) -├── pncdsl/ # the library — theme-prefixed file names -│ ├── core.doc.go # package docs -│ ├── core.manager.go # Manager[T Named], Named, TypeLabel -│ ├── core.game.go # Game aggregate, runtime state, helpers -│ ├── core.engine.go # ebiten.Game adapter (Update/Draw/Layout) -│ ├── core.dsl.go # Run() -│ ├── core.errors.go # sentinel errors -│ │ -│ ├── util.geometry.go # Point, Rectangle, Polygon, Shape -│ ├── util.timer.go # Timer helper -│ ├── util.log.go # DebugLog + logf -│ │ -│ ├── asset.def.go # Asset, AssetKind -│ ├── asset.manager.go # AssetManager alias + lazy loader -│ ├── asset.audio.go # AudioPlayer (stub) -│ ├── asset.text.go # drawText / wrapText helpers -│ │ -│ ├── scene.def.go # Scene, SceneActor -│ ├── scene.manager.go # SceneManager alias -│ ├── scene.hotspot.go # Hotspot, CursorKind -│ ├── scene.trigger.go # Trigger (stub) -│ ├── scene.transition.go # fade-to-black overlay (internal) -│ ├── scene.camera.go # Camera (stub identity transform) -│ │ -│ ├── item.def.go # Item -│ ├── item.manager.go # ItemManager alias -│ ├── item.inventory.go # Inventory -│ │ -│ ├── actor.def.go # Character -│ ├── actor.manager.go # CharacterManager alias -│ ├── actor.animation.go # AnimationClip (stub) -│ │ -│ ├── dialog.def.go # Dialogue, DialogueNode, DialogueChoice -│ ├── dialog.manager.go # DialogueManager alias -│ │ -│ ├── action.def.go # Action, Runner, Ctx, Status + every built-in action -│ ├── action.condition.go # Condition interface + combinators -│ ├── action.script.go # Script entity -│ ├── action.manager.go # ScriptManager alias -│ │ -│ ├── state.def.go # State (flags, vars, visited, talked) -│ ├── state.save.go # Save/Load (stub) -│ │ -│ ├── input.def.go # Input (consume-on-use) -│ │ -│ ├── ui.widget.go # Widget interface, UICtx, Size, Align -│ ├── ui.manager.go # UIManager alias + reversed/ordered iterators -│ ├── ui.theme.go # Theme + ThemeManager -│ ├── ui.theme_presets.go # 4 preset themes -│ ├── ui.defaults.go # RegisterDefaultUI/RadialVerbUI/RichUI -│ ├── ui.verb.go # Verb + VerbManager -│ ├── ui.verb_bar.go # VerbBar widget -│ ├── ui.verb_radial.go # RadialVerbs widget -│ ├── ui.inventory.go # InventoryBar widget -│ ├── ui.status.go # StatusLine widget -│ ├── ui.speech.go # SpeechBubble widget -│ ├── ui.dialog_box.go # DialogBox widget -│ ├── ui.end_card.go # EndCard widget -│ ├── ui.cursor.go # Cursor widget -│ ├── ui.hotspot_debug.go # HotspotDebug widget -│ ├── ui.panel.go # Panel widget -│ ├── ui.top_bar.go # TopBar widget -│ ├── ui.character_panel.go # CharacterPanel widget + CharStat -│ └── ui.chat_log.go # ChatLog widget +pncdsl/ # module git.teletype.hu/games/pncdsl +├── go.mod +├── go.sum │ -├── domain/ # the concrete game — see DEMO.md -├── assets/ # optional image / audio files -├── PLAN.md # original design document -├── UIPLAN.md # widget-system design -├── DEMO.md # walkthrough of the bundled game -├── GFX.md # asset prompts for image AIs +├── core.doc.go # package docs +├── core.manager.go # Manager[T Named], Named, TypeLabel +├── core.game.go # Game aggregate, runtime state, helpers +├── core.engine.go # ebiten.Game adapter (Update/Draw/Layout) +├── core.dsl.go # Run() +├── core.errors.go # sentinel errors +│ +├── util.geometry.go # Point, Rectangle, Polygon, Shape +├── util.timer.go # Timer helper +├── util.log.go # DebugLog + logf +│ +├── asset.def.go # Asset, AssetKind +├── asset.manager.go # AssetManager alias + lazy loader +├── asset.audio.go # AudioPlayer (stub) +├── asset.text.go # drawText / wrapText helpers +│ +├── scene.def.go # Scene, SceneActor +├── scene.manager.go # SceneManager alias +├── scene.hotspot.go # Hotspot, CursorKind +├── scene.trigger.go # Trigger (stub) +├── scene.transition.go # fade-to-black overlay (internal) +├── scene.camera.go # Camera (stub identity transform) +│ +├── item.def.go # Item +├── item.manager.go # ItemManager alias +├── item.inventory.go # Inventory +│ +├── actor.def.go # Character +├── actor.manager.go # CharacterManager alias +├── actor.animation.go # AnimationClip (stub) +│ +├── dialog.def.go # Dialogue, DialogueNode, DialogueChoice +├── dialog.manager.go # DialogueManager alias +│ +├── action.def.go # Action, Runner, Ctx, Status + every built-in action +├── action.condition.go # Condition interface + combinators +├── action.script.go # Script entity +├── action.manager.go # ScriptManager alias +│ +├── state.def.go # State (flags, vars, visited, talked) +├── state.save.go # Save/Load (stub) +│ +├── input.def.go # Input (consume-on-use) +│ +├── ui.widget.go # Widget interface, UICtx, Size, Align +├── ui.manager.go # UIManager alias + reversed/ordered iterators +├── ui.theme.go # Theme + ThemeManager +├── ui.theme_presets.go # 4 preset themes +├── ui.defaults.go # RegisterDefaultUI/RadialVerbUI/RichUI +├── ui.verb.go # Verb + VerbManager +├── ui.verb_bar.go # VerbBar widget +├── ui.verb_radial.go # RadialVerbs widget +├── ui.inventory.go # InventoryBar widget +├── ui.status.go # StatusLine widget +├── ui.speech.go # SpeechBubble widget +├── ui.dialog_box.go # DialogBox widget +├── ui.end_card.go # EndCard widget +├── ui.cursor.go # Cursor widget +├── ui.hotspot_debug.go # HotspotDebug widget +├── ui.panel.go # Panel widget +├── ui.top_bar.go # TopBar widget +├── ui.character_panel.go # CharacterPanel widget + CharStat +├── ui.chat_log.go # ChatLog widget +│ +├── UIPLAN.md # widget-system design rationale ├── LICENSE.md # MIT └── README.md # this file ``` -Files under `domain/` follow `theme.identifier.go` (e.g. -`scene.kitchen.go`, `item.key.go`) — `ls domain/scene.*` instantly lists -every location. +The companion demo project (`pncdsl-demo`) lives in its own repo at +. It consumes this +package via `require git.teletype.hu/games/pncdsl …` in its `go.mod` — +no in-tree coupling. --- diff --git a/pncdsl/action.condition.go b/action.condition.go similarity index 100% rename from pncdsl/action.condition.go rename to action.condition.go diff --git a/pncdsl/action.def.go b/action.def.go similarity index 100% rename from pncdsl/action.def.go rename to action.def.go diff --git a/pncdsl/action.manager.go b/action.manager.go similarity index 100% rename from pncdsl/action.manager.go rename to action.manager.go diff --git a/pncdsl/action.script.go b/action.script.go similarity index 100% rename from pncdsl/action.script.go rename to action.script.go diff --git a/pncdsl/actor.animation.go b/actor.animation.go similarity index 100% rename from pncdsl/actor.animation.go rename to actor.animation.go diff --git a/pncdsl/actor.def.go b/actor.def.go similarity index 100% rename from pncdsl/actor.def.go rename to actor.def.go diff --git a/pncdsl/actor.manager.go b/actor.manager.go similarity index 100% rename from pncdsl/actor.manager.go rename to actor.manager.go diff --git a/pncdsl/asset.audio.go b/asset.audio.go similarity index 100% rename from pncdsl/asset.audio.go rename to asset.audio.go diff --git a/pncdsl/asset.def.go b/asset.def.go similarity index 100% rename from pncdsl/asset.def.go rename to asset.def.go diff --git a/pncdsl/asset.manager.go b/asset.manager.go similarity index 100% rename from pncdsl/asset.manager.go rename to asset.manager.go diff --git a/pncdsl/asset.text.go b/asset.text.go similarity index 100% rename from pncdsl/asset.text.go rename to asset.text.go diff --git a/assets/bg/bedroom.png b/assets/bg/bedroom.png deleted file mode 100644 index 5f30155..0000000 Binary files a/assets/bg/bedroom.png and /dev/null differ diff --git a/assets/bg/kitchen.png b/assets/bg/kitchen.png deleted file mode 100644 index bd3d5b3..0000000 Binary files a/assets/bg/kitchen.png and /dev/null differ diff --git a/assets/spr/player.png b/assets/spr/player.png deleted file mode 100644 index 2d85d45..0000000 Binary files a/assets/spr/player.png and /dev/null differ diff --git a/pncdsl/core.doc.go b/core.doc.go similarity index 100% rename from pncdsl/core.doc.go rename to core.doc.go diff --git a/pncdsl/core.dsl.go b/core.dsl.go similarity index 100% rename from pncdsl/core.dsl.go rename to core.dsl.go diff --git a/pncdsl/core.engine.go b/core.engine.go similarity index 100% rename from pncdsl/core.engine.go rename to core.engine.go diff --git a/pncdsl/core.errors.go b/core.errors.go similarity index 100% rename from pncdsl/core.errors.go rename to core.errors.go diff --git a/pncdsl/core.game.go b/core.game.go similarity index 100% rename from pncdsl/core.game.go rename to core.game.go diff --git a/pncdsl/core.manager.go b/core.manager.go similarity index 100% rename from pncdsl/core.manager.go rename to core.manager.go diff --git a/pncdsl/dialog.def.go b/dialog.def.go similarity index 100% rename from pncdsl/dialog.def.go rename to dialog.def.go diff --git a/pncdsl/dialog.manager.go b/dialog.manager.go similarity index 100% rename from pncdsl/dialog.manager.go rename to dialog.manager.go diff --git a/domain/build_test.go b/domain/build_test.go deleted file mode 100644 index c96cbb4..0000000 --- a/domain/build_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package domain - -import "testing" - -// TestBuildValidates is the headless smoke test described in PLAN.md: -// it composes the full game and asks the library to cross-check every -// name reference between managers. No window opens. -func TestBuildValidates(t *testing.T) { - g := Build() - if err := g.Validate(); err != nil { - t.Fatalf("validate: %v", err) - } - for _, name := range []string{"bedroom", "kitchen"} { - if !g.SceneManager.Has(name) { - t.Errorf("missing scene %q", name) - } - } - for _, name := range []string{"key", "beans", "mug"} { - if !g.ItemManager.Has(name) { - t.Errorf("missing item %q", name) - } - } - if !g.DialogueManager.Has("cat_morning") { - t.Errorf("missing dialogue cat_morning") - } - if !g.ScriptManager.Has("intro") || !g.ScriptManager.Has("victory") { - t.Errorf("missing intro/victory script") - } -} diff --git a/domain/character.cat.go b/domain/character.cat.go deleted file mode 100644 index 2e075f7..0000000 --- a/domain/character.cat.go +++ /dev/null @@ -1,18 +0,0 @@ -package domain - -import ( - "image/color" - - p "pncdsl/pncdsl" -) - -func defineCat(g *p.Game) { - g.CharacterManager.Register(p.Character{ - Name: "cat", - Sprite: "spr/cat", - Speed: 40, - SpeechColor: color.RGBA{200, 200, 255, 255}, - W: 34, - H: 20, - }) -} diff --git a/domain/character.player.go b/domain/character.player.go deleted file mode 100644 index be4e9a5..0000000 --- a/domain/character.player.go +++ /dev/null @@ -1,18 +0,0 @@ -package domain - -import ( - "image/color" - - p "pncdsl/pncdsl" -) - -func definePlayer(g *p.Game) { - g.CharacterManager.Register(p.Character{ - Name: "player", - Sprite: "spr/player", - Speed: 80, - SpeechColor: color.RGBA{255, 230, 160, 255}, - W: 28, - H: 62, - }) -} diff --git a/domain/dialog.cat_morning.go b/domain/dialog.cat_morning.go deleted file mode 100644 index 208ed33..0000000 --- a/domain/dialog.cat_morning.go +++ /dev/null @@ -1,45 +0,0 @@ -package domain - -import p "pncdsl/pncdsl" - -func defineCatMorning(g *p.Game) { - g.DialogueManager.Register(p.Dialogue{ - Name: "cat_morning", - Start: "start", - Nodes: []p.DialogueNode{ - { - Name: "start", - Lines: []p.DialogueLine{ - {Speaker: "cat", Text: "Mióóóóóóóóu. Késő van."}, - }, - Choices: []p.DialogueChoice{ - { - Text: "Mindjárt megy a kávé.", - Actions: []p.Action{p.SetFlag(FlagPromisedCoffee), p.EndDialogue()}, - }, - { - Text: "Te etted meg a babot?", - Show: p.Not(p.Flag(FlagCupboardOpen)), - Actions: []p.Action{p.GotoNode("beans")}, - }, - { - Text: "Hagyj békén.", - Actions: []p.Action{p.EndDialogue()}, - }, - }, - }, - { - Name: "beans", - Lines: []p.DialogueLine{ - {Speaker: "cat", Text: "A szekrényben van. Mindig ott szokott lenni."}, - }, - Choices: []p.DialogueChoice{ - { - Text: "Köszi.", - Actions: []p.Action{p.EndDialogue()}, - }, - }, - }, - }, - }) -} diff --git a/domain/domain.asset.go b/domain/domain.asset.go deleted file mode 100644 index e2724dd..0000000 --- a/domain/domain.asset.go +++ /dev/null @@ -1,31 +0,0 @@ -package domain - -import p "pncdsl/pncdsl" - -// registerAssets declares every asset the game references. Files don't -// need to exist on disk — the library will substitute deterministic -// colored placeholders for anything missing. -func registerAssets(g *p.Game) { - imgs := []string{ - "bg/bedroom", "bg/kitchen", - "spr/key", "spr/beans", "spr/mug", - "spr/player", "spr/cat", - } - for _, name := range imgs { - g.AssetManager.Register(p.Asset{ - Name: name, - Path: "assets/" + name + ".png", - Kind: p.AssetImage, - }) - } - audios := []string{ - "mus/wakeup", "mus/calm", "snd/coffee_done", - } - for _, name := range audios { - g.AssetManager.Register(p.Asset{ - Name: name, - Path: "assets/" + name + ".ogg", - Kind: p.AssetAudio, - }) - } -} diff --git a/domain/domain.flag.go b/domain/domain.flag.go deleted file mode 100644 index e924f36..0000000 --- a/domain/domain.flag.go +++ /dev/null @@ -1,10 +0,0 @@ -package domain - -const ( - FlagKeyTaken = "key_taken" - FlagCupboardOpen = "cupboard_open" - FlagMugTaken = "mug_taken" - FlagCoffeeHasBeans = "coffee_has_beans" - FlagCoffeeHasMug = "coffee_has_mug" - FlagPromisedCoffee = "promised_coffee" -) diff --git a/domain/game.go b/domain/game.go deleted file mode 100644 index 7f241c1..0000000 --- a/domain/game.go +++ /dev/null @@ -1,46 +0,0 @@ -package domain - -import p "pncdsl/pncdsl" - -// Build assembles the full Reggeli Kávé game by registering every entity -// into its matching manager on a fresh *Game. The order matters only for -// readability — Validate() runs after Build to cross-check references. -func Build() *p.Game { - g := p.NewGame("Reggeli Kávé", 320, 200) - - registerAssets(g) - - definePlayer(g) - defineCat(g) - - defineKey(g) - defineBeans(g) - defineMug(g) - - defineCatMorning(g) - - defineIntroScript(g) - defineVictoryScript(g) - - defineBedroom(g) - defineKitchen(g) - - // Story-rich HUD: top bar + NPC character panel + chat log + permanent - // verb wheel + small inventory. The player panel is skipped — the - // player avatar is always visible in the scene, so the floating card - // in the corner just gets in the way. Pass "player" as the second - // arg to put it back. - p.RegisterRichUI(g, "", "cat") - g.MaxLogLines = 64 - - // initial HUD vars - g.State.SetVar("score", 0) - g.State.SetVar("time", "Nap 1 - 07:23") - g.State.SetVar("player.state", "Ébren") - g.State.SetVar("player.mood", "Álmos") - g.State.SetVar("cat.state", "Várakozik") - g.State.SetVar("cat.mood", "Türelmetlen") - - g.StartAt("bedroom").OnStart(p.RunScript("intro")) - return g -} diff --git a/domain/item.beans.go b/domain/item.beans.go deleted file mode 100644 index 7fe9bd5..0000000 --- a/domain/item.beans.go +++ /dev/null @@ -1,11 +0,0 @@ -package domain - -import p "pncdsl/pncdsl" - -func defineBeans(g *p.Game) { - g.ItemManager.Register(p.Item{ - Name: "beans", - Sprite: "spr/beans", - Description: "őrölt kávébab", - }) -} diff --git a/domain/item.key.go b/domain/item.key.go deleted file mode 100644 index 3531808..0000000 --- a/domain/item.key.go +++ /dev/null @@ -1,20 +0,0 @@ -package domain - -import p "pncdsl/pncdsl" - -func defineKey(g *p.Game) { - g.ItemManager.Register(p.Item{ - Name: "key", - Sprite: "spr/key", - Description: "rozsdás kulcs", - OnUseWith: map[string]p.Action{ - "cupboard": p.Seq( - p.Say("player", "Klikk."), - p.SetFlag(FlagCupboardOpen), - p.Give("beans"), - p.TakeAway("key"), - p.Say("player", "Bab van benne. Pont, ami kell."), - ), - }, - }) -} diff --git a/domain/item.mug.go b/domain/item.mug.go deleted file mode 100644 index 3a3058f..0000000 --- a/domain/item.mug.go +++ /dev/null @@ -1,11 +0,0 @@ -package domain - -import p "pncdsl/pncdsl" - -func defineMug(g *p.Game) { - g.ItemManager.Register(p.Item{ - Name: "mug", - Sprite: "spr/mug", - Description: "kedvenc bögréje", - }) -} diff --git a/domain/scene.bedroom.go b/domain/scene.bedroom.go deleted file mode 100644 index d8e96b5..0000000 --- a/domain/scene.bedroom.go +++ /dev/null @@ -1,61 +0,0 @@ -package domain - -import p "pncdsl/pncdsl" - -func defineBedroom(g *p.Game) { - g.SceneManager.Register(p.Scene{ - Name: "bedroom", - Title: "Hálószoba", - Background: "bg/bedroom", - Music: "mus/wakeup", - Actors: []p.SceneActor{ - {CharacterName: "player", At: p.Point{X: 160, Y: 145}}, - }, - Hotspots: []p.Hotspot{ - { - Name: "bed", - Area: p.Rect(20, 70, 90, 60), - Label: "ágy", - OnLook: p.Say("player", "A párnám még meleg."), - OnUse: p.Say("player", "Most keltem fel. Nem alszom vissza."), - }, - { - Name: "nightstand", - Area: p.Rect(115, 80, 35, 50), - Label: "éjjeliszekrény", - OnLook: p.If(p.Flag(FlagKeyTaken), - p.Say("player", "Üres a tetején."), - p.Say("player", "Egy rozsdás kulcs az éjjeliszekrényemen."), - ), - OnTake: p.If(p.Flag(FlagKeyTaken), - p.Say("player", "Már elvittem."), - p.Seq( - p.Walk("player", p.Point{X: 130, Y: 145}), - p.Give("key"), - p.SetFlag(FlagKeyTaken), - p.Say("player", "Pont jól jöhet."), - ), - ), - OnUse: p.If(p.Flag(FlagKeyTaken), - p.Say("player", "Már elvittem a kulcsot."), - p.Seq( - p.Walk("player", p.Point{X: 130, Y: 145}), - p.Give("key"), - p.SetFlag(FlagKeyTaken), - p.Say("player", "Megvan a kulcs."), - ), - ), - }, - { - Name: "door", - Area: p.Rect(260, 50, 40, 90), - Label: "ajtó", - OnLook: p.Say("player", "Az ajtó a konyhába vezet."), - OnUse: p.Seq( - p.Walk("player", p.Point{X: 260, Y: 145}), - p.GoTo("kitchen"), - ), - }, - }, - }) -} diff --git a/domain/scene.kitchen.go b/domain/scene.kitchen.go deleted file mode 100644 index 70a03aa..0000000 --- a/domain/scene.kitchen.go +++ /dev/null @@ -1,115 +0,0 @@ -package domain - -import p "pncdsl/pncdsl" - -func defineKitchen(g *p.Game) { - g.SceneManager.Register(p.Scene{ - Name: "kitchen", - Title: "Konyha", - Background: "bg/kitchen", - Music: "mus/calm", - Actors: []p.SceneActor{ - {CharacterName: "player", At: p.Point{X: 60, Y: 145}}, - {CharacterName: "cat", At: p.Point{X: 215, Y: 142}}, - }, - Hotspots: []p.Hotspot{ - { - Name: "cupboard", - Area: p.Rect(30, 40, 80, 80), - Label: "szekrény", - OnLook: p.Say("player", "Konyhaszekrény. Zárva."), - OnUse: p.If(p.Flag(FlagCupboardOpen), - p.Say("player", "Már nyitva van."), - p.If(p.HasItem("key"), - p.Seq( - p.Walk("player", p.Point{X: 70, Y: 145}), - p.Say("player", "Klikk."), - p.SetFlag(FlagCupboardOpen), - p.Give("beans"), - p.TakeAway("key"), - ), - p.Say("player", "Zárva. Kéne egy kulcs."), - ), - ), - OnUseWith: map[string]p.Action{ - "key": p.Seq( - p.Walk("player", p.Point{X: 70, Y: 145}), - p.Say("player", "Klikk."), - p.SetFlag(FlagCupboardOpen), - p.Give("beans"), - p.TakeAway("key"), - ), - }, - }, - { - Name: "shelf", - Area: p.Rect(115, 45, 35, 50), - Label: "polc", - OnLook: p.Say("player", "A polc tele bögrékkel."), - OnTake: p.If(p.Flag(FlagMugTaken), - p.Say("player", "Tiszta már mind elfogyott."), - p.Seq( - p.Walk("player", p.Point{X: 125, Y: 145}), - p.Give("mug"), - p.SetFlag(FlagMugTaken), - p.Say("player", "Egy bögre, kérem."), - ), - ), - OnUse: p.If(p.Flag(FlagMugTaken), - p.Say("player", "Már elvettem egyet."), - p.Seq( - p.Walk("player", p.Point{X: 125, Y: 145}), - p.Give("mug"), - p.SetFlag(FlagMugTaken), - p.Say("player", "Egy bögre, kérem."), - ), - ), - }, - { - Name: "coffee_machine", - Area: p.Rect(160, 75, 50, 55), - Label: "kávéfőző", - OnLook: p.Say("player", "A jó öreg kávéfőző."), - OnUse: p.If(p.And(p.Flag(FlagCoffeeHasBeans), p.Flag(FlagCoffeeHasMug)), - p.RunScript("victory"), - p.Say("player", "Még hiányzik valami. Bab? Bögre?"), - ), - OnUseWith: map[string]p.Action{ - "beans": p.Seq( - p.Walk("player", p.Point{X: 180, Y: 145}), - p.SetFlag(FlagCoffeeHasBeans), - p.TakeAway("beans"), - p.Say("player", "Bab a helyén."), - p.If(p.And(p.Flag(FlagCoffeeHasBeans), p.Flag(FlagCoffeeHasMug)), - p.RunScript("victory")), - ), - "mug": p.Seq( - p.Walk("player", p.Point{X: 180, Y: 145}), - p.SetFlag(FlagCoffeeHasMug), - p.TakeAway("mug"), - p.Say("player", "Bögre a helyén."), - p.If(p.And(p.Flag(FlagCoffeeHasBeans), p.Flag(FlagCoffeeHasMug)), - p.RunScript("victory")), - ), - }, - }, - { - Name: "cat", - Area: p.Rect(195, 118, 35, 22), - Label: "Cirmos", - OnLook: p.Say("player", "Cirmos, a házikedvencem."), - OnTalk: p.RunDialogue("cat_morning"), - }, - { - Name: "door", - Area: p.Rect(0, 40, 22, 90), - Label: "ajtó", - OnLook: p.Say("player", "Vissza a hálóba."), - OnUse: p.Seq( - p.Walk("player", p.Point{X: 20, Y: 145}), - p.GoTo("bedroom"), - ), - }, - }, - }) -} diff --git a/domain/script.intro.go b/domain/script.intro.go deleted file mode 100644 index 5eece53..0000000 --- a/domain/script.intro.go +++ /dev/null @@ -1,16 +0,0 @@ -package domain - -import p "pncdsl/pncdsl" - -func defineIntroScript(g *p.Game) { - g.ScriptManager.Register(p.Script{ - Name: "intro", - Actions: p.Seq( - p.Wait(0.4), - p.Say("player", "Brr, hideg van."), - p.Say("player", "Egy kávé kéne, mielőtt szétszakad a fejem."), - p.Walk("player", p.Point{X: 200, Y: 145}), - p.SetVar("player.mood", "Eltökélt"), - ), - }) -} diff --git a/domain/script.victory.go b/domain/script.victory.go deleted file mode 100644 index 8f87391..0000000 --- a/domain/script.victory.go +++ /dev/null @@ -1,20 +0,0 @@ -package domain - -import p "pncdsl/pncdsl" - -func defineVictoryScript(g *p.Game) { - g.ScriptManager.Register(p.Script{ - Name: "victory", - Actions: p.Seq( - p.Say("player", "Készen is van!"), - p.PlaySound("snd/coffee_done"), - p.Wait(0.6), - p.Say("cat", "Mióóóóu. *boldog macska*"), - p.Say("player", "Reggeli kávé: megmentve."), - p.SetVar("score", 100), - p.SetVar("player.mood", "Boldog"), - p.SetVar("cat.mood", "Boldog"), - p.ShowEnd("Vége — kösz, hogy játszottál!"), - ), - }) -} diff --git a/go.mod b/go.mod index 824c43e..06d732f 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,13 @@ -module pncdsl +module git.teletype.hu/games/pncdsl go 1.26.3 +require github.com/hajimehoshi/ebiten/v2 v2.9.9 + require ( github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 // indirect github.com/ebitengine/hideconsole v1.0.0 // indirect github.com/ebitengine/purego v0.9.0 // indirect - github.com/hajimehoshi/ebiten/v2 v2.9.9 // indirect github.com/jezek/xgb v1.1.1 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect diff --git a/go.sum b/go.sum index 07593fd..03c113f 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/hajimehoshi/ebiten/v2 v2.9.9 h1:JdDag6Ndj12iD4lxQGG8kbsrh7ssj4Sbzth6r github.com/hajimehoshi/ebiten/v2 v2.9.9/go.mod h1:DAt4tnkYYpCvu3x9i1X/nK/vOruNXIlYq/tBXxnhrXM= github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA= +golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= diff --git a/pncdsl/input.def.go b/input.def.go similarity index 100% rename from pncdsl/input.def.go rename to input.def.go diff --git a/pncdsl/item.def.go b/item.def.go similarity index 100% rename from pncdsl/item.def.go rename to item.def.go diff --git a/pncdsl/item.inventory.go b/item.inventory.go similarity index 100% rename from pncdsl/item.inventory.go rename to item.inventory.go diff --git a/pncdsl/item.manager.go b/item.manager.go similarity index 100% rename from pncdsl/item.manager.go rename to item.manager.go diff --git a/main.go b/main.go deleted file mode 100644 index e59e75c..0000000 --- a/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "log" - - "pncdsl/domain" - "pncdsl/pncdsl" -) - -func main() { - if err := pncdsl.Run(domain.Build()); err != nil { - log.Fatal(err) - } -} diff --git a/pncdsl/scene.camera.go b/scene.camera.go similarity index 100% rename from pncdsl/scene.camera.go rename to scene.camera.go diff --git a/pncdsl/scene.def.go b/scene.def.go similarity index 100% rename from pncdsl/scene.def.go rename to scene.def.go diff --git a/pncdsl/scene.hotspot.go b/scene.hotspot.go similarity index 100% rename from pncdsl/scene.hotspot.go rename to scene.hotspot.go diff --git a/pncdsl/scene.manager.go b/scene.manager.go similarity index 100% rename from pncdsl/scene.manager.go rename to scene.manager.go diff --git a/pncdsl/scene.transition.go b/scene.transition.go similarity index 100% rename from pncdsl/scene.transition.go rename to scene.transition.go diff --git a/pncdsl/scene.trigger.go b/scene.trigger.go similarity index 100% rename from pncdsl/scene.trigger.go rename to scene.trigger.go diff --git a/pncdsl/state.def.go b/state.def.go similarity index 100% rename from pncdsl/state.def.go rename to state.def.go diff --git a/pncdsl/state.save.go b/state.save.go similarity index 100% rename from pncdsl/state.save.go rename to state.save.go diff --git a/pncdsl/ui.character_panel.go b/ui.character_panel.go similarity index 100% rename from pncdsl/ui.character_panel.go rename to ui.character_panel.go diff --git a/pncdsl/ui.chat_log.go b/ui.chat_log.go similarity index 100% rename from pncdsl/ui.chat_log.go rename to ui.chat_log.go diff --git a/pncdsl/ui.cursor.go b/ui.cursor.go similarity index 100% rename from pncdsl/ui.cursor.go rename to ui.cursor.go diff --git a/pncdsl/ui.defaults.go b/ui.defaults.go similarity index 100% rename from pncdsl/ui.defaults.go rename to ui.defaults.go diff --git a/pncdsl/ui.dialog_box.go b/ui.dialog_box.go similarity index 100% rename from pncdsl/ui.dialog_box.go rename to ui.dialog_box.go diff --git a/pncdsl/ui.end_card.go b/ui.end_card.go similarity index 100% rename from pncdsl/ui.end_card.go rename to ui.end_card.go diff --git a/pncdsl/ui.hotspot_debug.go b/ui.hotspot_debug.go similarity index 100% rename from pncdsl/ui.hotspot_debug.go rename to ui.hotspot_debug.go diff --git a/pncdsl/ui.inventory.go b/ui.inventory.go similarity index 100% rename from pncdsl/ui.inventory.go rename to ui.inventory.go diff --git a/pncdsl/ui.manager.go b/ui.manager.go similarity index 100% rename from pncdsl/ui.manager.go rename to ui.manager.go diff --git a/pncdsl/ui.panel.go b/ui.panel.go similarity index 100% rename from pncdsl/ui.panel.go rename to ui.panel.go diff --git a/pncdsl/ui.speech.go b/ui.speech.go similarity index 100% rename from pncdsl/ui.speech.go rename to ui.speech.go diff --git a/pncdsl/ui.status.go b/ui.status.go similarity index 100% rename from pncdsl/ui.status.go rename to ui.status.go diff --git a/pncdsl/ui.theme.go b/ui.theme.go similarity index 100% rename from pncdsl/ui.theme.go rename to ui.theme.go diff --git a/pncdsl/ui.theme_presets.go b/ui.theme_presets.go similarity index 100% rename from pncdsl/ui.theme_presets.go rename to ui.theme_presets.go diff --git a/pncdsl/ui.top_bar.go b/ui.top_bar.go similarity index 100% rename from pncdsl/ui.top_bar.go rename to ui.top_bar.go diff --git a/pncdsl/ui.verb.go b/ui.verb.go similarity index 100% rename from pncdsl/ui.verb.go rename to ui.verb.go diff --git a/pncdsl/ui.verb_bar.go b/ui.verb_bar.go similarity index 100% rename from pncdsl/ui.verb_bar.go rename to ui.verb_bar.go diff --git a/pncdsl/ui.verb_radial.go b/ui.verb_radial.go similarity index 100% rename from pncdsl/ui.verb_radial.go rename to ui.verb_radial.go diff --git a/pncdsl/ui.widget.go b/ui.widget.go similarity index 100% rename from pncdsl/ui.widget.go rename to ui.widget.go diff --git a/pncdsl/util.geometry.go b/util.geometry.go similarity index 100% rename from pncdsl/util.geometry.go rename to util.geometry.go diff --git a/pncdsl/util.log.go b/util.log.go similarity index 100% rename from pncdsl/util.log.go rename to util.log.go diff --git a/pncdsl/util.timer.go b/util.timer.go similarity index 100% rename from pncdsl/util.timer.go rename to util.timer.go