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>
4.6 KiB
4.6 KiB
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 + optionaleendings.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 undstop().- 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)
public record Ending(String id, String title, boolean victory,
List<Condition> when, String text) { /* when: List.copyOf */ }
endings.yaml (geordnete Liste, erstes passendes gewinnt):
- 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)→ erstesEnding, dessenwhenviaConditions.allgilt, sonstnull. 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():
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.fromDtobautEnding(whenviaConditionDto.toModelList).WorldLoaderliestendings.yamloptional (readListOptional), baut eine geordneteList<Ending>(Reihenfolge der YAML-Liste),requireUniqueIds("ending", …).Worldbekommt Feldendings(List) + rückwärtskompatible 5- und 6-Arg-Konstruktoren (leere Quests/Endings), damit bestehendenew 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_securedwird gesetzt, wenn dierestore_power-Quest endet (bereits vorhanden) → Sieg-Ende. - fled: neuer schaltbarer Gegenstand Front Door (
effects: setFlag fled) in der Küche → Flucht-Ende.victorysteht vorfled, 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).