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) <noreply@anthropic.com>
This commit is contained in:
@@ -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<Condition> 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<ConditionDto> when, String text)`.
|
||||||
|
- `EndingFactory.fromDto` baut `Ending` (`when` via `ConditionDto.toModelList`).
|
||||||
|
- `WorldLoader` liest `endings.yaml` **optional** (`readListOptional`), baut eine
|
||||||
|
**geordnete** `List<Ending>` (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).
|
||||||
Reference in New Issue
Block a user