From 0f40ee637d50a2966ac7d06eb98156077c2a2f9d Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 31 May 2026 22:44:01 +0200 Subject: [PATCH] docs: spec for win-condition & endings (sub-project 3.3) Priority-ordered condition-driven endings + an end-of-game summary (turns, quests X/Y, rank). EndingEngine mirrors the quest engine; Game ends the loop when an ending fires. endings.yaml optional; World gains 5/6/7-arg back-compat constructors. Two reachable demo endings (victory, flee) with no new mechanics. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../specs/2026-05-31-endings-design.md | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 Semesterprojekt/docs/superpowers/specs/2026-05-31-endings-design.md diff --git a/Semesterprojekt/docs/superpowers/specs/2026-05-31-endings-design.md b/Semesterprojekt/docs/superpowers/specs/2026-05-31-endings-design.md new file mode 100644 index 0000000..e26a1e9 --- /dev/null +++ b/Semesterprojekt/docs/superpowers/specs/2026-05-31-endings-design.md @@ -0,0 +1,116 @@ +# Spec: Win-Condition & Enden (Teilprojekt 3.3) + +Stand: 2026-05-31. Capstone des Quest-Systems. Baut auf Fundament (3.1) + Quests +(3.2) auf: Conditions/Effects/Flags + QuestLog sind vorhanden. + +## 1. Kontext & Ziel + +Das Spiel endet bisher nur per `quit`. Ziel: **mehrere, priorisierte Enden**, +condition-driven (Ansatz wie Quests), plus ein **End-Screen mit Zusammenfassung** +(Züge, abgeschlossene Quests, Rang). Bestätigt: **A mit Summary**. + +## 2. Scope + +**In Scope:** +- `Ending`-Datenmodell + optionale `endings.yaml` (Reihenfolge = Priorität). +- `EndingEngine` (erstes passendes Ende ermitteln + End-Screen rendern). +- `Game`-Anbindung: pro Zug nach dem Quest-Tick prüfen; bei Treffer Ende + + Summary ausgeben und `stop()`. +- End-Summary: Züge, Quests X/Y, Rang. +- Loader: `EndingDto`, `EndingFactory`, `World.endings`. +- Demo: zwei spielbar erreichbare Enden ohne neue Mechanik. + +**Out of Scope:** Scoreboard/Persistenz; zugbasierte Conditions; Spielstand-Reset. + +## 3. Datenmodell (model) + +```java +public record Ending(String id, String title, boolean victory, + List when, String text) { /* when: List.copyOf */ } +``` +`endings.yaml` (geordnete Liste, erstes passendes gewinnt): +```yaml +- id: victory + title: "The Manor Reclaimed" + victory: true + when: [{ flag: manor_secured }] + text: | + The lights hold steady, the whispers fade. You've made the manor yours. +- id: fled + title: "Into the Night" + victory: false + when: [{ flag: fled }] + text: | + You bolt through the front door. Safe — but the manor keeps its secrets. +``` + +## 4. Engine (game) + +- `EndingEngine.triggered(GameContext ctx)` → erstes `Ending`, dessen `when` via + `Conditions.all` gilt, sonst `null`. Rein, testbar (Priorität/erster Treffer). +- `EndingEngine.render(Ending e, GameContext ctx, int turns)` → `StyledText`: + Titel-Banner + `text` + Summary. +- **Summary**: `Turns: N`, `Quests completed: X / Y` + (`X = questLog.completed().size()`, `Y = world.getQuests().size()`), plus + **Rang**: + - victory & alle Quests → „Master of the Manor" + - victory → „Manor Reclaimed" + - sonst → „Escaped — the manor keeps its secrets" + +## 5. Game-Anbindung + +In `Game.run` nach `publishHud()` (Initial **und** pro Schleifendurchlauf) ein +`maybeEnd()`: +```java +private void maybeEnd() { + Ending e = EndingEngine.triggered(ctx); + if (e != null) { + ctx.getIo().print(EndingEngine.render(e, ctx, turn)); + stop(); + } +} +``` +`stop()` setzt `running=false`; die Schleife endet sauber, kein zusätzlicher Prompt. +Konsole: Programm endet. GUI: Worker-Thread endet, Fenster bleibt mit End-Screen. + +## 6. Loading + +- `EndingDto(id, title, Boolean victory, List when, String text)`. +- `EndingFactory.fromDto` baut `Ending` (`when` via `ConditionDto.toModelList`). +- `WorldLoader` liest `endings.yaml` **optional** (`readListOptional`), baut eine + **geordnete** `List` (Reihenfolge der YAML-Liste), `requireUniqueIds("ending", …)`. +- `World` bekommt Feld `endings` (List) + **rückwärtskompatible 5- und + 6-Arg-Konstruktoren** (leere Quests/Endings), damit bestehende `new World(...)`- + Aufrufe (Tests, bisheriger Loader-Pfad) unverändert kompilieren. Der Loader nutzt + den vollen 7-Arg-Konstruktor. + +## 7. Fehlerbehandlung + +| Fall | Verhalten | +|---|---| +| `endings.yaml` fehlt | leere Liste → Spiel endet nur per `quit` (wie bisher) | +| mehrere Conditions treffen | erstes Ende in Listenreihenfolge gewinnt | +| Ending ohne `when` | trifft sofort (leere Condition-Liste = true) – Autoren sollten Reihenfolge beachten | +| keine Quests definiert | Summary zeigt `0 / 0`, Rang nach victory-Flag | + +## 8. Demonstration (zwei erreichbare Enden, keine neue Mechanik) + +- **victory**: `manor_secured` wird gesetzt, wenn die `restore_power`-Quest endet + (bereits vorhanden) → Sieg-Ende. +- **fled**: neuer schaltbarer Gegenstand **Front Door** (`effects: setFlag fled`) + in der Küche → Flucht-Ende. `victory` steht vor `fled`, also schlägt das + Sichern der Villa die Flucht. + +## 9. Testing + +- `EndingEngine.triggered`: Priorität/erster Treffer; kein Treffer → `null`. +- `EndingEngine.render`: enthält Titel, Text, Züge, Quests-Zähler, Rang. +- `EndingFactory.fromDto`: Mapping inkl. victory/when/text. +- End-to-End (Konsole): Villa sichern → Sieg-Ende + Summary, Spiel endet; + alternativ Front Door benutzen → Flucht-Ende. +- `Game`-Integration: nicht separat unit-getestet (Loop/IO); über Konsolen-Smoke. + +## 10. Offene Detailfragen (in Implementierung) + +- Genaue Banner-Optik des End-Screens (HEADING-Rahmen). +- Rang-Schwellen (zunächst nur victory + alle-Quests-Abfrage).