# Architektur ## Schichten ```mermaid flowchart TD IO["io (Konsole / GUI)
Interaktion mit Spieler"] GAME["game (Engine/Loop)
Spielfluss, hält World + Player"] CMD["command (Commands)
go, take, use, talk, ..."] MODEL["model (Domain)
Room, Item, Player, Npc, World"] LOADER["loader (YAML + DTO)
liest Ressourcen, validiert"] IO -- "liest/schreibt via GameIO" --> GAME GAME -- "dispatcht via CommandRegistry" --> CMD CMD -- "mutiert" --> MODEL LOADER -- "baut auf aus DTOs" --> MODEL ``` ## Package-Struktur ```mermaid flowchart LR root["thb.jeanluc.adventure"] root --> app["App.java
AppGui.java"] root --> model root --> loader root --> command root --> game root --> io model --> model_files["World, Room,
Player, Npc, Direction"] model --> item item --> item_files["Item (abstract)
ReadableItem
SwitchableItem
PlainItem"] loader --> loader_files["WorldLoader
ReferenceResolver
WorldValidator
*Factory"] loader --> dto dto --> dto_files["RoomDto, ItemDto,
NpcDto, GameDto,
QuestDto, EndingDto, …"] command --> cmd_core["Command (Interface)
CommandRegistry
CommandParser"] command --> impl impl --> impl_files["GoCommand, TakeCommand,
DropCommand, UseCommand,
ReadCommand, ExamineCommand,
InventoryCommand, LookCommand,
TalkCommand, GiveCommand,
MapCommand, QuestsCommand,
SaveCommand, MenuCommand,
HelpCommand"] game --> game_files["Game (Loop)
GameContext/Session/State
QuestEngine, EndingEngine,
EscortEngine, Light,
Pathfinder, MapLayout"] io --> io_files["GameIO (Interface)
ConsoleIO
SwingIO
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. | | DTO | Domain | |---|---|---| | Typ | `record` | Lombok-Klasse | | Mutabilität | immutable | mutable wo nötig | | Felder | nur Daten + String-IDs | Objekt-Referenzen, EnumMap, etc. | | Zweck | Jackson-Mapping | Spielablauf | | Tests | Loader-Tests | Domain-Tests, brauchen kein YAML | **Warum getrennt?** - Domain-Klassen müssen nicht mit Jackson-Annotations verschmutzt werden - `Room.exits` ist `EnumMap` (typisicher, schnell) statt `Map` (was zum YAML passt) - Validierung passiert beim Übergang DTO→Domain (siehe [loading-flow.md](loading-flow.md)) - Domain-Tests können Objekte direkt im Code bauen, ohne YAML-Fixtures ## Game-Loop (vereinfacht) ```java while (running) { String input = io.readLine(); if (input == null) break; // EOF ParsedCommand parsed = parser.parse(input); 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 } ``` ## IO-Abstraktion Konsole und GUI teilen sich `GameIO`: ```java public interface GameIO { 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 options) { … } default void shutdown() { … } } ``` 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`, die vom JTextField-ActionListener gefüllt wird.