remove demo game
This commit is contained in:
252
DEMO.md
252
DEMO.md
@@ -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.<name>.go` with a `define<Name>(g *p.Game)`
|
|
||||||
function that calls `g.SceneManager.Register(...)`. Add a call in
|
|
||||||
`Build()`. If any hotspot uses `GoTo("<name>")`, `Validate()` will
|
|
||||||
cross-check the link for you.
|
|
||||||
- **New item**: same pattern, `item.<name>.go`, register into
|
|
||||||
`g.ItemManager`. Map keys inside `OnUseWith` must exactly match the
|
|
||||||
target hotspot's `Name` field.
|
|
||||||
- **New dialog**: `dialog.<name>.go` — `DialogueChoice.Show` accepts
|
|
||||||
anything that implements `Condition` (`Flag`, `HasItem`, `Not(...)`,
|
|
||||||
etc.).
|
|
||||||
- **New cutscene**: `script.<name>.go`, register into
|
|
||||||
`g.ScriptManager`, then fire it anywhere with
|
|
||||||
`p.RunScript("<name>")`.
|
|
||||||
|
|
||||||
## 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.<name>.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/<name>.png` or
|
|
||||||
`assets/spr/<name>.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).
|
|
||||||
206
README.md
206
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
|
cutscenes, HUD, themes and lazy asset loading; the domain only declares
|
||||||
content.
|
content.
|
||||||
|
|
||||||
This document is the full library reference. For a 5-minute tour of the
|
This document is the full library reference. The companion `pncdsl-demo`
|
||||||
shipped demo, see [`DEMO.md`](DEMO.md).
|
repo at <ssh://git@git.teletype.hu:2222/games/pncdsl-demo> 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
|
items, scenes, characters, dialogues, scripts, assets, verbs, widgets and
|
||||||
themes alike.
|
themes alike.
|
||||||
|
|
||||||
A complete example game ("Morning Coffee") lives in `domain/` and is
|
A complete example game ("Morning Coffee") lives in the separate
|
||||||
described in [`DEMO.md`](DEMO.md).
|
[`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
|
**Prerequisites.** Go 1.24+ (for generic type aliases) and a working OpenGL
|
||||||
context. Ebitengine handles windowing and the render loop.
|
context. Ebitengine handles windowing and the render loop.
|
||||||
|
|
||||||
**Run the bundled demo:**
|
**Use the library in your own project:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone <repo>
|
go mod init my-game
|
||||||
cd pncdsl
|
go get git.teletype.hu/games/pncdsl
|
||||||
go run .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
A window opens at 1280×800 (the library uses a 320×200 internal resolution
|
If the private host can't be reached over HTTPS, point Go at the SSH
|
||||||
and Ebitengine scales it 4×). No asset files are required — placeholders
|
endpoint:
|
||||||
are generated for anything missing under `assets/`. Press **F1** at any
|
|
||||||
time to toggle the hotspot debug overlay.
|
```bash
|
||||||
|
git config --global \
|
||||||
|
url."ssh://git@git.teletype.hu:2222/".insteadOf "https://git.teletype.hu/"
|
||||||
|
export GOPRIVATE=git.teletype.hu/*
|
||||||
|
```
|
||||||
|
|
||||||
**Minimum game:**
|
**Minimum game:**
|
||||||
|
|
||||||
@@ -111,7 +123,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"pncdsl/pncdsl"
|
"git.teletype.hu/games/pncdsl"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -1519,10 +1531,13 @@ should crash loudly in development.
|
|||||||
## 20. Testing
|
## 20. Testing
|
||||||
|
|
||||||
```bash
|
```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
|
```go
|
||||||
func TestBuildValidates(t *testing.T) {
|
func TestBuildValidates(t *testing.T) {
|
||||||
@@ -1530,16 +1545,14 @@ func TestBuildValidates(t *testing.T) {
|
|||||||
if err := g.Validate(); err != nil {
|
if err := g.Validate(); err != nil {
|
||||||
t.Fatalf("validate: %v", err)
|
t.Fatalf("validate: %v", err)
|
||||||
}
|
}
|
||||||
// ... assert specific managers contain expected entries ...
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
It calls `Build()` (which registers every entity) and `Validate()` (which
|
Drop a similar test into any project that consumes the library — it
|
||||||
cross-checks every name reference). No Ebiten window is opened — perfect
|
opens no window and finishes in milliseconds, so it's a cheap CI gate.
|
||||||
for CI.
|
|
||||||
|
|
||||||
Library-internal unit tests are not shipped yet; the manager + action
|
Library-internal unit tests are not shipped yet; the building blocks are
|
||||||
primitives are intentionally small enough to verify through smoke runs
|
small enough to verify through smoke runs
|
||||||
of the demo for the time being.
|
of the demo for the time being.
|
||||||
|
|
||||||
### 20.1 Useful runtime debug switches
|
### 20.1 Useful runtime debug switches
|
||||||
@@ -1553,87 +1566,88 @@ pncdsl.DebugLog = true // logf output to stderr (script queue, a
|
|||||||
|
|
||||||
## 21. Project layout
|
## 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/
|
pncdsl/ # module git.teletype.hu/games/pncdsl
|
||||||
├── main.go # entry point: pncdsl.Run(domain.Build())
|
├── go.mod
|
||||||
├── pncdsl/ # the library — theme-prefixed file names
|
├── go.sum
|
||||||
│ ├── 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
|
|
||||||
│
|
│
|
||||||
├── domain/ # the concrete game — see DEMO.md
|
├── core.doc.go # package docs
|
||||||
├── assets/ # optional image / audio files
|
├── core.manager.go # Manager[T Named], Named, TypeLabel
|
||||||
├── PLAN.md # original design document
|
├── core.game.go # Game aggregate, runtime state, helpers
|
||||||
├── UIPLAN.md # widget-system design
|
├── core.engine.go # ebiten.Game adapter (Update/Draw/Layout)
|
||||||
├── DEMO.md # walkthrough of the bundled game
|
├── core.dsl.go # Run()
|
||||||
├── GFX.md # asset prompts for image AIs
|
├── 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
|
├── LICENSE.md # MIT
|
||||||
└── README.md # this file
|
└── README.md # this file
|
||||||
```
|
```
|
||||||
|
|
||||||
Files under `domain/` follow `theme.identifier.go` (e.g.
|
The companion demo project (`pncdsl-demo`) lives in its own repo at
|
||||||
`scene.kitchen.go`, `item.key.go`) — `ls domain/scene.*` instantly lists
|
<ssh://git@git.teletype.hu:2222/games/pncdsl-demo>. It consumes this
|
||||||
every location.
|
package via `require git.teletype.hu/games/pncdsl …` in its `go.mod` —
|
||||||
|
no in-tree coupling.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.0 MiB |
@@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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()},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
)
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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."),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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"),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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!"),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
5
go.mod
5
go.mod
@@ -1,12 +1,13 @@
|
|||||||
module pncdsl
|
module git.teletype.hu/games/pncdsl
|
||||||
|
|
||||||
go 1.26.3
|
go 1.26.3
|
||||||
|
|
||||||
|
require github.com/hajimehoshi/ebiten/v2 v2.9.9
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 // indirect
|
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 // indirect
|
||||||
github.com/ebitengine/hideconsole v1.0.0 // indirect
|
github.com/ebitengine/hideconsole v1.0.0 // indirect
|
||||||
github.com/ebitengine/purego v0.9.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
|
github.com/jezek/xgb v1.1.1 // indirect
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.36.0 // indirect
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
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/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 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
|
||||||
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
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 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
|
|||||||
14
main.go
14
main.go
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user