Files
Jander_Semester2/Semesterprojekt/docs/superpowers/specs/2026-05-31-endings-design.md
Jean-Luc Makiola 0f40ee637d 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>
2026-05-31 22:44:01 +02:00

117 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).