docs: correct stale documentation and drop planning artifacts

The docs had drifted from the code and described things that never existed.

- architecture.md documented a QuitCommand class (quit/exit are aliases on
  MenuCommand) and a GameIO interface of read()/write(String); the real one is
  readLine() plus print(StyledText) with presentation hooks. Both corrected,
  along with the package diagram and the game-loop sketch.
- conventions.md listed a package tree missing save, menu, io.text and
  command.impl. Updated, including why MapLayout lives in game.
- data-structures.md claimed LinkedList was deliberately avoided (Pathfinder
  uses one for O(1) addFirst during path reconstruction), named the wrong queue
  for the Swing bridge, and listed an input history that was never built.
- Removed enhancement-ideas.md and implementation-status.md: a backlog and a
  phase tracker, neither of which documents the delivered project.

Also stop tracking the built jars. They still ship in the submission, but a
22 MB binary that changes on every build does not belong in git history.
This commit is contained in:
2026-07-14 12:40:09 +02:00
parent 526e3d4ee7
commit c205d1f49b
10 changed files with 92 additions and 424 deletions

View File

@@ -45,3 +45,6 @@ saves/
# src/main/resources/music/ is tracked and bundled. Anchored so it
# ignores the top-level source folder but not the resources path.
/music/
# Built jars (kept on disk for the submission ZIP, not tracked)
release/*.jar

View File

@@ -1,33 +1,36 @@
# Semesterprojekt Textadventure
Design- und Architekturdokumentation. Dient als Spec während der Implementierung.
Design- und Architekturdokumentation. Bedienung und Build stehen im
[Haupt-README](../README.md).
## Inhalt
| Datei | Inhalt |
|---|---|
| [architecture.md](architecture.md) | Package-Struktur, Schichten, DTO-vs-Domain-Trennung |
| [conventions.md](conventions.md) | Sprache, ID-Format, Naming, Lombok-Cheatsheet |
| [data-structures.md](data-structures.md) | Alle gewählten Collection-Typen mit Begründung |
| [conventions.md](conventions.md) | Sprache, ID-Format, Naming, Lombok-Cheatsheet |
| [item-model.md](item-model.md) | Item-Hierarchie (abstract + 3 Subtypen), Lombok-Inheritance |
| [yaml-schemas.md](yaml-schemas.md) | Schemas für `items.yaml`, `rooms.yaml`, `npcs.yaml`, `game.yaml` |
| [loading-flow.md](loading-flow.md) | Lade-Reihenfolge, Referenz-Auflösung, Validierung |
| [commands.md](commands.md) | Befehlsparser, Command-Pattern, Befehlsliste |
| [npcs.md](npcs.md) | NPC-Modell, Talk- und Give-Interaktion |
| [implementation-status.md](implementation-status.md) | Aktueller Stand, Phasen-Checkliste, festgelegte Entscheidungen |
| [yaml-schemas.md](yaml-schemas.md) | Schemas der YAML-Dateien unter `resources/world/` |
| [loading-flow.md](loading-flow.md) | Lade-Reihenfolge, Referenz-Auflösung, Validierung |
| [music.md](music.md) | Hintergrundmusik in der GUI (Bonus) |
## Pflicht vs. Optional (laut Aufgabenstellung)
- **Pflicht:** ≥4 Räume mit Navigation, ≥3 Gegenstände mit Inventar
- **Optional/Bonus:** NPCs, Swing-GUI
- **Pflicht:** ≥ 4 Räume mit Navigation, ≥ 3 Gegenstände mit Inventar
erfüllt mit 14 Räumen und 19 Gegenständen.
- **Optional/Bonus:** NPCs (3) und Swing-GUI — beides umgesetzt.
Beide optionalen Teile sind hier eingeplant.
Welche Klasse welche Anforderung umsetzt, steht als Tabelle im
[Haupt-README](../README.md).
## Technologie-Stack
- Java 25
- Jackson (YAML) für Daten-Loading
- Lombok für Boilerplate-Reduktion
- Java 21 (LTS)
- Jackson (YAML) für das Laden der Spielwelt
- Lombok zur Reduktion von Boilerplate
- JUnit 5 + AssertJ + Mockito für Tests
- Logback + SLF4J für Logging
- Swing für GUI (Bonus)
- SLF4J + Logback für Logging
- Swing für die GUI (Bonus)

View File

@@ -33,19 +33,22 @@ flowchart LR
model --> item
item --> item_files["Item (abstract)<br/>ReadableItem<br/>SwitchableItem<br/>PlainItem"]
loader --> loader_files["WorldLoader<br/>ReferenceResolver<br/>WorldValidator"]
loader --> loader_files["WorldLoader<br/>ReferenceResolver<br/>WorldValidator<br/>*Factory"]
loader --> dto
dto --> dto_files["RoomDto, ItemDto,<br/>NpcDto, GameDto"]
dto --> dto_files["RoomDto, ItemDto,<br/>NpcDto, GameDto,<br/>QuestDto, EndingDto, …"]
command --> cmd_core["Command (Interface)<br/>CommandRegistry<br/>CommandParser"]
command --> impl
impl --> impl_files["GoCommand, TakeCommand,<br/>DropCommand, UseCommand,<br/>InventoryCommand, LookCommand,<br/>TalkCommand, GiveCommand,<br/>HelpCommand, QuitCommand"]
impl --> impl_files["GoCommand, TakeCommand,<br/>DropCommand, UseCommand,<br/>ReadCommand, ExamineCommand,<br/>InventoryCommand, LookCommand,<br/>TalkCommand, GiveCommand,<br/>MapCommand, QuestsCommand,<br/>SaveCommand, MenuCommand,<br/>HelpCommand"]
game --> game_files["Game (Loop)<br/>GameContext"]
game --> game_files["Game (Loop)<br/>GameContext/Session/State<br/>QuestEngine, EndingEngine,<br/>EscortEngine, Light,<br/>Pathfinder, MapLayout"]
io --> io_files["GameIO (Interface)<br/>ConsoleIO<br/>SwingIO"]
io --> io_files["GameIO (Interface)<br/>ConsoleIO<br/>SwingIO<br/>io.text (Styled Output)"]
```
> **Hinweis:** Es gibt **keine** `QuitCommand`-Klasse. `quit`/`exit`/`beenden` sind Aliase,
> die in `App` auf `MenuCommand` registriert werden (siehe `App.play`).
## DTO vs. Domain-Trennung
**Kernprinzip:** YAML wird in Records deserialisiert (DTOs), erst danach werden String-IDs zu Objekt-Referenzen aufgelöst.
@@ -67,15 +70,15 @@ flowchart LR
## Game-Loop (vereinfacht)
```java
while (!game.isOver()) {
String input = io.read();
while (running) {
String input = io.readLine();
if (input == null) break; // EOF
ParsedCommand parsed = parser.parse(input);
Command cmd = registry.get(parsed.verb());
if (cmd == null) {
io.write("Unbekannter Befehl.");
} else {
cmd.execute(context, parsed.args());
}
registry.find(parsed.verb()).ifPresentOrElse(
cmd -> cmd.execute(ctx, parsed.args()),
() -> io.write("I don't understand '" + parsed.verb() + "'."));
publishHud(); // Engines ticken, HUD/Map/Quests updaten
maybeEnd(); // Ending-Bedingungen prüfen
}
```
@@ -85,9 +88,25 @@ Konsole und GUI teilen sich `GameIO`:
```java
public interface GameIO {
String read(); // blockierende Leseoperation
void write(String text);
String readLine(); // blockierende Leseoperation
void print(StyledText text); // einzige Ausgabe-Primitive
// Convenience + Presentation-Hooks als default-Methoden:
default void write(String text) { } // Kurzform für ungestylten Text
default void showRoom(RoomView v) { }
default void setHud(Hud hud) { }
default void setMap(MapView map) { }
default void setQuests(QuestView quests) { }
default void setMusic(String track) { }
default int choose(String title, List<String> options) { }
default void shutdown() { }
}
```
Damit ist der Game-Loop **identisch** für beide Modi. `SwingIO` blockiert intern mit einer `BlockingQueue<String>`, die vom JTextField-ActionListener gefüllt wird.
Ausgabe läuft **immer** über `print(StyledText)` — eine Liste von `Span`s mit `Style`.
`ConsoleIO` rendert sie als ANSI-Farben (oder ASCII, je nach Settings), `SwingIO` als
`StyledDocument` im `JTextPane`. Die `default`-Methoden geben der GUI Extra-Hooks
(Karte, Quest-Panel, Musik), die die Konsole schlicht ignoriert oder als Text ausgibt.
Damit ist der Game-Loop **identisch** für beide Modi. `SwingIO` blockiert intern mit einer
`LinkedBlockingQueue<String>`, die vom JTextField-ActionListener gefüllt wird.

View File

@@ -32,24 +32,28 @@ Geltungsbereich: id ist eindeutig **innerhalb seiner Entitätsart**. Ein Item un
```
thb.jeanluc.adventure
├── App, AppGui <- Entry Points (Konsole / Swing)
├── model
│ ├── Direction
│ ├── Room
│ ├── Player
│ ├── Npc
│ ├── Direction, Room, Player, Npc
│ ├── Quest, QuestStage, Ending, Combination, Condition, Effect, …
│ └── item <- eigener Subpackage wegen Hierarchie
│ ├── Item (abstract)
│ ├── ReadableItem
│ ├── SwitchableItem
│ └── PlainItem
├── io
├── command
├── game
└── loader
└── dto
├── io <- ConsoleIO, SwingIO, GameIO, Music*, MapPanel, QuestPanel
│ └── text <- StyledText, Style, Span, Hud, *View (15 Klassen)
├── command <- Command, CommandParser, CommandRegistry, ParsedCommand
│ └── impl <- die 15 konkreten Kommandos
├── game <- Game-Loop, Engines (Quest, Ending, Escort), Light,
│ Pathfinder, MapLayout, GameState/Session/Context
├── loader
│ └── dto <- die YAML-Records
├── save <- SaveService, SaveCodec, SaveData, Settings*
└── menu <- MainMenu, SettingsMenu, MenuAction
```
**Wann ein Subpackage:** wenn eine Klassenfamilie ≥ 3 Klassen umfasst und eine eigene Abstraktion bildet (`item`, evtl. später `command.impl`). Sonst flach lassen.
**Wann ein Subpackage:** wenn eine Klassenfamilie ≥ 3 Klassen umfasst und eine eigene Abstraktion bildet. Erfüllt von `item` (4), `command.impl` (15), `io.text` (15), `loader.dto` (16), `save` (7), `menu` (3). Sonst flach lassen — `MapLayout` liegt deshalb in `game` neben `Pathfinder` und **nicht** in einem eigenen `map`-Package (eine Klasse reicht nicht).
## Naming

View File

@@ -15,13 +15,16 @@ Bewusste Wahl jeder Collection — der Dozent bewertet das laut Aufgabenstellung
| Spieler-Inventar | `LinkedHashMap<String, Item>` | O(1) |
| Befehlsregistry | `HashMap<String, Command>` | O(1) |
| NPC-Reaktionen | `HashMap<String, NpcReaction>` | O(1) |
| Eingabehistorie (optional) | `ArrayDeque<String>` | O(1) Front/Back |
| Besuchte Räume (für die Karte) | `LinkedHashSet<String>` | O(1) + Besuchsreihenfolge |
| BFS-Queue (Pathfinder, MapLayout) | `ArrayDeque<Room>` | O(1) FIFO |
| BFS-Pfadrekonstruktion | `LinkedList<Room>` | O(1) `addFirst` |
| GUI-Eingabebrücke (EDT → Worker) | `LinkedBlockingQueue<String>` | blockierend, unbegrenzt |
## Begründungen im Detail
### `EnumMap<Direction, Room>` für Raum-Ausgänge
- **Direction** ist Enum (`NORTH`, `SOUTH`, `EAST`, `WEST`, evtl. `UP`, `DOWN`)
- **Direction** ist Enum (`NORTH`, `SOUTH`, `EAST`, `WEST`)
- `EnumMap` ist array-backed, kein Hashing nötig → schneller und kompakter als `HashMap`
- Iteration in Enum-Deklarationsreihenfolge (stabil)
@@ -49,11 +52,19 @@ Entscheidung "keine Stapel" (1 Item pro id) macht das Map-basierte Modell sauber
- Entspricht dem expliziten Tipp aus der Aufgabenstellung
- Vermeidet wachsendes `switch`-Statement
### `ArrayDeque<String>` für Historie (optional)
### `ArrayDeque<Room>` als BFS-Queue
- Falls Up-Arrow in der GUI gewünscht oder Befehlsverlauf
- `ArrayDeque` ist `LinkedList` praktisch immer überlegen (bessere Cache-Lokalität, weniger Overhead)
- Beidseitige O(1)-Operationen
- `Pathfinder` (Auto-Travel `go to <raum>`) und `MapLayout` (Karten-Layout) sind beides
Breitensuchen → brauchen eine **FIFO-Queue**
- `ArrayDeque` ist die Standardwahl dafür: O(1) an beiden Enden, keine Node-Allokation
wie bei `LinkedList`, bessere Cache-Lokalität
### `LinkedList<Room>` für die Pfadrekonstruktion
Die eine Stelle, an der `LinkedList` die richtige Wahl ist (`Pathfinder`):
Nach der BFS läuft man über `cameFrom` **rückwärts** vom Ziel zum Start und will den
Pfad in Vorwärtsreihenfolge. `addFirst()` ist bei `LinkedList` O(1) — bei `ArrayList`
wäre `add(0, x)` O(n), weil alles verschoben werden müsste.
## Bewusst NICHT gewählt
@@ -62,7 +73,7 @@ Entscheidung "keine Stapel" (1 Item pro id) macht das Map-basierte Modell sauber
| `ArrayList<Item>` für Inventar | O(n)-Lookup, Duplikat-Handling nötig |
| `HashMap<String, Item>` für Inventar | Anzeige-Reihenfolge instabil |
| `TreeMap` irgendwo | Keine sortierte Iteration nötig, O(log n) ohne Nutzen |
| `LinkedList` | `ArrayDeque` ist fast immer besser |
| `LinkedList` als Queue | dafür ist `ArrayDeque` besser (s.o.) — als `addFirst`-Liste in der Pfadrekonstruktion aber sehr wohl genutzt |
| `Vector` / `Hashtable` | Legacy, synchronisiert (nicht gebraucht), langsamer |
| `Map<String, String>` für Exits in Domain | Direction sollte Enum sein, nicht String |
@@ -70,4 +81,9 @@ Entscheidung "keine Stapel" (1 Item pro id) macht das Map-basierte Modell sauber
Single-threaded: Game-Loop liest, dispatcht, schreibt — keine parallelen Mutationen.
**Ausnahme:** Bei Swing-GUI läuft Input über den Event-Dispatch-Thread, der Game-Loop in einem Worker-Thread. Hier kommt `BlockingQueue<String>` (`ArrayBlockingQueue` reicht) als Brücke ins Spiel — siehe [architecture.md](architecture.md).
**Ausnahme:** Bei Swing-GUI läuft Input über den Event-Dispatch-Thread, der Game-Loop in
einem Worker-Thread. Als Brücke dient eine `LinkedBlockingQueue<String>` (`SwingIO`):
Der `JTextField`-ActionListener (EDT) legt die Zeile ab, der Worker blockiert in
`take()`. Unbegrenzt statt `ArrayBlockingQueue`, weil es keine sinnvolle Obergrenze für
getippte Zeilen gibt und ein volles `offer()` Eingaben verlieren würde — siehe
[architecture.md](architecture.md).

View File

@@ -1,239 +0,0 @@
# Enhancement-Ideen (Backlog)
> Roh-Sammlung von Ausbau-Ideen über die Abgabe hinaus. Lebendes Dokument
> noch kein Design, noch keine Festlegung. Wird in Teilprojekte zerlegt und
> nacheinander über den Brainstorming → Spec → Plan → Implementierung-Zyklus
> verfeinert.
Stand: 2026-05-31. Ausgangslage: Pflichtumfang ("bare minimum") erfüllt,
alle Phasen 17 grün. Ziel: deutlich mehr Tiefe und Politur.
## Leitziele
- **Reichere Ausgabe** statt nacktem Konsolentext sowohl Konsole als auch GUI.
- **GUI soll noch mehr bieten** als die Konsole (mehr Raum für visuelle Mittel).
- **~60 Minuten Spielzeit** anpeilen. Viel mehr Inhalt, viel mehr zu tun.
- Bestehendes **NPC-System zu einem tieferen Quest-System** ausbauen.
## Themenblöcke
### 1. Reichere Präsentation / Ausgabe-Schicht ✅ umgesetzt (Branch `feature/presentation-layer`)
> Umgesetzt: semantisches Modell (`io.text`), `ConsoleIO` (ANSI + Box-Drawing,
> Color/Glyph-Modi), `SwingIO` (4 Regionen, `JTextPane`, gebündelter Font,
> HiDPI-Skalierung + Zoom `Strg +/-/0`), HUD pro Zug, Welcome-Banner. 79 Tests grün.
> Spec/Plan unter `docs/superpowers/`. Karte/Menü/Save/Musik weiterhin offen.
- Weg vom reinen `write(String)`: formatierte Ausgabe (Überschriften, Trenner,
Hervorhebungen für Items/NPCs/Richtungen).
- **Konsole**: ANSI-Farben, Box-Drawing-Zeichen für Rahmen, evtl. ASCII-Art /
Banner für Räume oder Spielstart.
- **GUI (Swing)**: mehr als ein `JTextArea` Farben, Stile, evtl. Panels,
Statusanzeige, Bilder/Icons.
**Festgelegt (Zielbild Konsole):** Stufe **B als Baseline** (Boxed Heading,
gelabelte Sektionen, persistente HUD-Zeile: Ort · Gold · Zug · Licht), **ASCII-Art
nur für große Momente** (Spielstart, Finale, Schlüsselräume). Semantische
Farb-Rollen: Items / NPCs / Exits / Heading / Gefahr.
**Designprinzipien:**
- **Nicht überladen / Progressive Disclosure** Raumeintritt zeigt nur das
Wesentliche (Ort, kurze Beschreibung, Auffälliges); Details auf Abruf
(`examine`, `look`).
- **Info auf Regionen verteilen statt Text stapeln** die **Karte ersetzt die
„Exits:"-Zeile**. GUI: persistentes Map-Panel macht die Exit-Zeile überflüssig.
Konsole: kompakte Mini-Map (bei `map` bzw. knapp beim Raumeintritt).
### 2. Nerd Fonts / Glyphen das Installationsproblem
- Nerd-Font-Glyphen (Icons) sehen gut aus, müssen aber auf dem System des
Spielers installiert sein, sonst Tofu-Kästchen.
- Offene Frage: gute Lösung finden, damit es überall funktioniert.
- Erste Gedanken:
- **GUI**: Font ließe sich *mitliefern* (`.ttf` in Resources, via
`Font.createFont` laden) → Installationsproblem entfällt für die GUI.
- **Konsole**: Font nicht kontrollierbar → gestufte Fallbacks
(reines ASCII → Unicode-Box-Drawing → Nerd-Glyphen) statt harter Annahme.
### 3. Karte / Map ✅ umgesetzt (Branch `feature/map`)
> Umgesetzt: `MapLayout` (BFS-Gitter-Layout + Fog of War), `MapView`-Modell,
> GUI-`MapPanel` (Graphics2D, ersetzt die Exit-Liste), Konsolen-ASCII via
> `AsciiMap` + `map`-Befehl, Besuchte-Räume-Tracking auf `Player`, Map-Push pro Zug.
> Item/NPC-Marker und Türen-Styling offen (Quest-Teilprojekt). Spec/Plan unter
> `docs/superpowers/`.
- Übersichtskarte der Räume in **beiden** Modi.
- Konsole: ASCII-Map. GUI: gezeichnete Map (Grid/Graph).
- Offene Fragen: nur bereits besuchte Räume zeigen? aktuelle Position markieren?
fester Grundriss oder dynamisch aus den Exits berechnet?
### 4. Gameplay-Ausbau (Inhalt)
- Deutlich mehr Räume, Items, NPCs.
- Mehr zu *tun*: Rätsel, Mechaniken, Abhängigkeiten zwischen Objekten.
- Zielgröße: ~60 Min. Spielzeit (laut Annahme machbar, da überwiegend Text).
### 5. Quest-System (NPC-Ausbau)
> ✅ Fundament umgesetzt (Branch `feature/quest-foundation`): World-Flags +
> Condition/Effect-Engine, verschlossene Exits, zustandsabhängige Beschreibungen,
> bedingte Dialoge, Reaktionen mit requires/effects, Schalter-Effekte. Demo:
> Generator schaltet Strom → Keller-Tür öffnet + Raum wird beleuchtet.
>
> ✅ #3.2 umgesetzt (Branch `feature/quests`): condition-driven Quests
> (Quest/QuestStage), QuestEngine mit Auto-Advance + Ansagen, START_QUEST-Effekt,
> Konsolen-Befehl `quests` und GUI-Quest-Box unter der Karte. Demo-Quest
> `restore_power`.
>
> ✅ #3.3 umgesetzt (Branch `feature/endings`): priorisierte, condition-driven
> Enden (`endings.yaml`) + End-Screen mit Summary (Züge, Quests X/Y, Rang).
> Demo: Sieg-Ende (`manor_secured`) und Flucht-Ende (Front Door → `fled`).
> **Damit ist das Quest-System (#3) komplett.** Offen: Licht/Dunkelheit &
> Item-Kombination sowie #4 Content-Ausbau.
- Aktuelles NPC-System: Begrüßung + Geschenk-Reaktion (Item → Item).
- Ausbau Richtung: mehrstufige Quests, Bedingungen, NPC-"Memory" (Zustand),
Quest-Log, evtl. Win-Condition daran gekoppelt.
**GUI-Layout (festgelegt):** Quest-Log als **dauerhaft sichtbare Box unterhalb der
Karte** im rechten Seitenbereich (Map oben, Quests darunter). Gehört zu Quest-
Teilprojekt #2 (Quest-Log); das Fundament #1 reserviert/plant den Slot.
### 5b. Spielkern: zentraler Bogen + Mechanik-Spine
**Leitidee (bestätigt):** Bewusst *kondensiert* wenige Mechaniken, die sich
vielfach kombinieren, statt vieler Einzel-Gimmicks. Ein 60-Min-Spiel soll
*designt* wirken, nicht gestreckt.
**Hauptziel:** *Den Strom wieder anschalten* (`power_on`). Der Weg dahin ist eine
**Quest-Kette, die den Spieler Raum für Raum führt** (Raum X freischalten → führt
zu Raum Y → … → Generator/Sicherungskasten). Das Hauptziel selbst ist das Ende
des Bogens; einzelne Teilziele schalten je den nächsten Bereich frei.
**Mechanik-Spine (5 Bausteine, die sich kombinieren):**
1. **World-Flags / Zustand** Rückgrat (`power_on`, `cellar_drained`,
`ghost_banished`). Beschreibungen, Exits und NPC-Reaktionen lesen/schreiben sie.
2. **Verschlossene Exits & Schalter** Türen brauchen Schlüssel *oder* Flag;
Hebel/Ventile setzen Flags und öffnen Bereiche.
3. **Licht & Dunkelheit** dunkle Räume brauchen eine brennende Lichtquelle,
sonst keine Sicht auf Exits/Items (Atmosphäre, einfach umzusetzen).
> ✅ Umgesetzt (Branch `feature/light-darkness`): `Room.dark` + `Item.light`,
> `Light`-Helper, Gating in go/look/take/examine (Modell B: kein Eintritt ohne
> Licht), HUD-Licht verdrahtet. Demo: dunkler Dungeon braucht die brennende
> Lampe für den Generator.
4. **Item-Kombination** z.B. `match + candle → lit candle`. Achtung: v1.0 hat
bewusst argloses `use X` ohne Targets gewählt → Command-Grammatik muss erweitert
werden (jetzt erlaubt, da über MVP hinaus).
> ✅ umgesetzt (Branch `feature/use-x-on-y`): `use X on Y` als datengetriebene
> Rezepte (`combinations.yaml`: requires/consume/produce/effects/response/failText),
> Operanden aus Inventar oder Raum, reihenfolge-unabhängiger Paar-Key. Kein
> Parser-Eingriff (`on`/`with`/`to` sind bereits Filler). Selbst-Kombination
> (a==b) wird beim Laden abgelehnt. Demo: `matches` + `torch` → `lit_torch`
> (dauerhafte Lichtquelle).
5. **Quest-Ketten + mehrere Enden** Teilziele referenzieren Flags; Kette
abschließen = Win; *welche* Flags gesetzt sind, entscheidet das Ende.
**Beispiel-Puzzle (zeigt das Stapeln):** *Strom wiederherstellen* Sicherung im
gefluteten Keller; erst Ventil → `cellar_drained`; Sicherung einsetzen → Schalter
`power_on` → oberes Stockwerk/Tür öffnet sich. Ein Puzzle, drei Mechaniken,
schaltet einen ganzen Flügel frei.
*Weitere Puzzle-Ideen im Backlog (optional, nach Bedarf):* Zahlenschloss mit
Code aus Lore, Caesar-Chiffre-Brief (`decode`), Geist bannen (Reliquien-Fetch),
Melodie-/Sequenz-Rätsel, dunkler Gang. Resource-Meter (Sanity/Öl) **offen**
vorerst puzzle-fokussiert ohne Survival-Druck.
### 6. Hauptmenü & Settings
> ✅ umgesetzt (Branch `feature/main-menu-save-load`). Hauptmenü-Shell oberhalb
> des Game-Loops (Neues Spiel / Laden / Einstellungen / Beenden) über neue
> `GameIO.choose`-Primitive (Konsole: nummeriertes Menü; GUI: Buttons).
> **Minimale Settings**: nur Farb-Modus + Glyphen-Modus (ASCII/Unicode), live
> umschaltbar, in `saves/settings.json` persistiert. Musik-/Typewriter-Toggles
> weiter zurückgestellt (Features existieren noch nicht).
- **Hauptmenü** vor der Spielschleife: *Neues Spiel*, *Spiel laden*, *Einstellungen*, *Beenden*.
- **Spielstände-Liste**: gespeicherte Spiele mit Metadaten (Name, Raum, Zugzahl,
Zeitstempel) anzeigen und hineinladen.
- **Settings-Untermenü**: z.B. Farben an/aus, Typewriter-Tempo, Musik-Lautstärke
an/aus, ASCII vs. Unicode.
- Sollte in beiden Modi funktionieren: Konsole = nummeriertes Textmenü, GUI =
Buttons/Liste. Architektur: Menü-Schicht *oberhalb* des Game-Loops.
### 7. Speichern / Laden
> ✅ umgesetzt (Branch `feature/main-menu-save-load`). JSON-Spielstand als
> Delta über die frisch aus YAML geladene Welt (`SaveData`/`SaveCodec`/
> `SaveService`, atomare Writes). **Ein aktiver Slot**: manuelles `save`,
> Autosave (Quest-Completion + alle 10 Züge) und `quit`/`menu` (speichern +
> zurück ins Menü) schreiben denselben Slot; Laden nur über das Menü.
> `saves/` ist gitignored.
- Spielstand serialisieren (Player-Zustand, Inventar, World-Flags, besuchte Räume,
Quest-Fortschritt) Format z.B. YAML/JSON.
- Mehrere Speicherstände, benennbar; Anbindung an die Spielstände-Liste im Menü.
### 8. Musik (GUI)
> ✅ umgesetzt (Branch `feature/music`): OGG Vorbis (vorbisspi/jorbis), pro Raum
> via `music:`-Feld in `rooms.yaml`, externer gitignored `music/`-Ordner (nicht im
> JAR). Idempotenter Track-Wechsel mit ~400 ms Fades; Räume ohne `music` blenden zu
> Stille; gleicher Track läuft nahtlos weiter. Music-Stufe Off/Low/Med/High in den
> Settings (Default Medium), persistiert. Konsole stumm. Testbarer `MusicController`
> über isoliertes `OggMusicBackend`; fehlende Datei → still, kein Absturz. Siehe
> `docs/music.md`.
- Hintergrundmusik **nur in der GUI**.
- **Pro Raum definierbar via YAML**: in `rooms.yaml` ein optionales Feld
(z.B. `music: <dateiname>`), Dateien in *einem* Ordner.
- **Dateigröße** ist ein Thema → Lösungsrichtung:
- Audiodateien **nicht** in JAR/Repo bündeln, sondern externer `music/`-Ordner;
Audio ggf. via `.gitignore` ausschließen + dokumentieren, wo Dateien hinkommen.
- **Komprimierte Formate** (OGG/MP3) statt WAV, **gestreamt** statt komplett geladen.
- Bibliothek erlaubt → kleinen Audio-Decoder einbinden (Java `javax.sound` kann
OGG/MP3 nicht nativ).
- Loop pro Track, sanfter Wechsel beim Raumwechsel, graceful fallback bei
fehlender Datei, Lautstärke aus den Settings.
- Architektur: `MusicPlayer`-Komponente hinter Interface; Konsole = No-Op.
## Festgelegte Erweiterungs-Entscheidungen
> ✅ **Pathfinding** umgesetzt (Branch `feature/pathfinding-go-to`): `go to <raum>`
> per BFS über besuchte Räume (Locks + Licht beachtet), via `GoCommand`
> (Richtung vs. Raumziel). Konsole still + Zusammenfassung (Start/Mitte/Ziel),
> GUI-Minikarte animiert pro Schritt (`GameIO.travelStep`, ~300 ms).
> **Trie-Autocomplete** weiterhin für eine spätere Runde zurückgestellt.
> ✅ **Tutorial** umgesetzt (Branch `feature/tutorial`): interaktiver Walkthrough
> beim Neuen Spiel (look→examine→take→inventory→use→go→go to), verb-/alias-basierte
> Abschluss-Erkennung (Bewegungs-Schritte verlangen echten Raumwechsel),
> Abschluss-Tipps, jederzeit per `skip` abbrechbar. Datengetrieben (`tutorial.yaml`,
> `TutorialLoader`), als `TutorialGuide` in den Game-Loop eingehängt; nur bei Neuem
> Spiel, nicht beim Laden.
| Entscheidung | Wert |
|---|---|
| Eigene `uebung`-Datenstrukturen verwenden? | **Nein** Standard-Collections (vom Prof bestätigt) |
| Algorithmen-Showcase | weiterhin willkommen, aber mit Standard-Datenstrukturen (z.B. BFS `go to <raum>`, Trie-Autocomplete) |
| Bibliotheken erlaubt? | **Ja** (Pflichtteil war bewusst ohne; Erweiterung darf welche nutzen) |
| Hauptmenü | ja: Neues Spiel / Laden / Settings / Beenden |
| Speichern/Laden | ja, mehrere benannte Spielstände |
| Musik | ja, **nur GUI**, pro Raum via YAML, externer Ordner, komprimiert + gestreamt |
## Offene Designfragen (zu klären)
- Wie weit darf die `GameIO`-Abstraktion aufgebohrt werden, ohne dass Konsole
und GUI auseinanderlaufen? (Gemeinsamer Loop ist ein Kernwert der Architektur.)
- Bleibt alles datengetrieben (YAML), auch Quests und Map-Layout?
- Reihenfolge / Abhängigkeiten der Teilprojekte.
---
*Notiz: Datengetriebenheit (YAML), getrennte DTO/Domain-Schicht und ein für
beide Frontends identischer Game-Loop sind etablierte Architekturwerte neue
Features sollten sie respektieren, nicht umgehen.*

View File

@@ -1,135 +0,0 @@
# Implementierungsstand & Reihenfolge
Stand: alle Phasen 17 implementiert, 67 Tests grün, End-to-End-Smoke-Test des Walking-Skeletons + YAML-Load erfolgreich.
## Phasen-Überblick
```mermaid
flowchart TD
P1["Phase 1<br/>Domain-Fundament"]
P2["Phase 2<br/>Command-Schicht"]
P3["Phase 3<br/>Walking Skeleton<br/>(Konsole + handgebaute Welt)"]
P4["Phase 4<br/>YAML-Loading + Validator"]
P5["Phase 5<br/>Restliche Commands"]
P6["Phase 6<br/>NPCs end-to-end"]
P7["Phase 7<br/>Swing-GUI (Bonus)"]
P1 --> P2 --> P3 --> P4 --> P5 --> P6 --> P7
```
## Checkliste Phase 1: Domain
- [x] `Direction` (`model.Direction`)
- [x] `Room` (`model.Room`)
- [x] `Item` abstract (`model.item.Item`) mit `abstract use(GameContext)`
- [x] `Npc` (`model.Npc`) inklusive `shell()`-Factory und `putReaction()`
- [x] `NpcReaction` (`model.NpcReaction`)
- [x] `GameIO` interface (`io.GameIO`)
- [x] `Player` (`model.Player`)
- [x] `World` (`model.World`)
- [x] `GameContext` (`game.GameContext`)
- [x] `ReadableItem` (`model.item.ReadableItem`)
- [x] `SwitchableItem` (`model.item.SwitchableItem`)
- [x] `PlainItem` (`model.item.PlainItem`)
## Checkliste Phase 2: Commands
- [x] `Command` interface (`command.Command`)
- [x] `ParsedCommand` (record)
- [x] `CommandRegistry` (`command.CommandRegistry`)
- [x] `CommandParser` (`command.CommandParser`) mit Filler-Words
- [x] `LookCommand`
- [x] `GoCommand`
- [x] `InventoryCommand`
## Checkliste Phase 3: Walking Skeleton
- [x] `ConsoleIO`
- [x] `Game` (Loop)
- [x] `App.main` (lädt sofort über YAML; der Walking-Skeleton-Zustand mit hartkodierter Welt wurde übersprungen, weil YAML-Load und Loop schon zusammen funktionieren)
- [x] Probelauf: `look`, `go north`, `inventory` (siehe `GameTest`, `LookCommandTest`)
## Checkliste Phase 4: YAML-Loading
- [x] DTOs: `GameDto`, `ItemDto`, `RoomDto`, `NpcDto`, `ReactionDto`
- [x] Test-Fixtures unter `src/test/resources/world/`
- [x] `WorldLoader` (Happy-Path)
- [x] `ReferenceResolver`
- [x] `WorldValidator` (eine Validierungsregel pro Test)
- [x] Echte Welt-YAMLs unter `src/main/resources/world/`
- [x] `App.main` läuft direkt gegen YAML-Load
## Checkliste Phase 5: Restliche Commands
- [x] `TakeCommand`
- [x] `DropCommand`
- [x] `UseCommand`
- [x] `ReadCommand`
- [x] `ExamineCommand`
- [x] `HelpCommand`
- [x] `QuitCommand`
## Checkliste Phase 6: NPCs
- [x] `Npc` voll ausgebaut (greeting, reactions)
- [x] `NpcReaction`
- [x] `TalkCommand`
- [x] `GiveCommand`
- [x] NPCs in `WorldLoader` integriert
- [x] End-to-End-Test: Lampe geben → Schlüssel bekommen (`TalkGiveCommandTest`)
## Checkliste Phase 7: Swing-GUI
- [x] `SwingIO` mit `LinkedBlockingQueue`-Brücke
- [x] `AppGui.main`
- [x] Game-Loop in Worker-Thread
## Build & Run
```sh
mvn test # 67 Tests
mvn -DskipTests exec:java -Dexec.mainClass=thb.jeanluc.adventure.App # Konsole
mvn -DskipTests exec:java -Dexec.mainClass=thb.jeanluc.adventure.AppGui # Swing
```
## Festgelegte Designentscheidungen
Nicht mehr offen, nicht nochmal diskutieren:
| Entscheidung | Wert |
|---|---|
| Item-Hierarchie | abstract Item + ReadableItem/SwitchableItem/PlainItem |
| Item-Package | `model.item` (Subpackage) |
| Switchable-State-Typ | `boolean` (kein Enum) |
| Switchable-Felder | nur `state` (Builder mappt YAML `initialState`); kein separates `initialState`-Feld am Domain-Objekt |
| use-Targets | argless `use X`, kein `use X on Y` |
| Item kennt Standort | nein, „dumme" Items |
| Hidden Items | nein |
| State→Raum-Beschreibung | nein im MVP |
| Room.description | `final`, immutable |
| Room.describe() | nicht auf Room, im LookCommand |
| Room.equals/hashCode | nicht überschreiben, Identity |
| Room-NPCs-Feld | von Anfang an drin |
| Bidirektionale Exits | manuell, kein Auto-Spiegeln |
| GameContext-Inhalt | minimal: World + Player + GameIO |
| IDs | lowercase snake_case slugs, kein UUID |
| ID-Regex | `^[a-z][a-z0-9_]*$` |
| YAML-Aufteilung | `game.yaml`, `items.yaml`, `rooms.yaml`, `npcs.yaml` |
| DTO ↔ Domain | getrennt, Resolver-Phase löst String-IDs zu Referenzen auf |
| Item-Type-Discriminator | YAML-Feld `type: plain|readable|switchable`, in `ItemFactory` als switch |
| Codebase-Sprache | Englisch (Identifier, YAML, User-Strings) |
| Doku-Sprache | Deutsch (Prose), Englisch (Code-Beispiele) |
| Diagramme | Mermaid |
| Lombok-Inheritance | `@SuperBuilder` |
| `@Data` | vermeiden, einzelne Annotations bevorzugen |
| Quit-Wiring | `QuitCommand.bind(Game)` nach Registry-Aufbau |
| Help-Quelle | `HelpCommand` zieht aus `CommandRegistry.distinctCommands()` |
| GameIO-Methodennamen | `readLine()` / `write(String)` (Java-üblich, konsistent) |
| Lombok-Version | 1.18.42 (1.18.36 ist nicht Java-26-kompatibel) |
## Offen / nicht im MVP
- **Win-Condition**: Spiel endet nur per `quit`. Optionale Erweiterung: Bedingung in `game.yaml` (`winRoom`, `requiredItem`).
- **Bedingte NPC-Reaktionen**, NPC-Memory, Quests — bewusst ausgelassen (siehe `npcs.md`).
- **Item-Aliases** (z.B. `lamp``oil_lamp`) — YAGNI bis konkreter Bedarf.
- **Eingabehistorie** in der GUI — `ArrayDeque` ist vorgesehen, nicht umgesetzt.