Walking skeleton through Swing GUI: YAML-driven world (4 rooms, 4 items, 1 NPC), HashMap command dispatch with parser, three-tier item hierarchy (readable / switchable / plain), and end-to-end NPC give/receive flow. 67 tests green.
3.0 KiB
3.0 KiB
Architektur
Schichten
flowchart TD
IO["io (Konsole / GUI)<br/>Interaktion mit Spieler"]
GAME["game (Engine/Loop)<br/>Spielfluss, hält World + Player"]
CMD["command (Commands)<br/>go, take, use, talk, ..."]
MODEL["model (Domain)<br/>Room, Item, Player, Npc, World"]
LOADER["loader (YAML + DTO)<br/>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
flowchart LR
root["thb.jeanluc.adventure"]
root --> app["App.java<br/>AppGui.java"]
root --> model
root --> loader
root --> command
root --> game
root --> io
model --> model_files["World, Room,<br/>Player, Npc, Direction"]
model --> item
item --> item_files["Item (abstract)<br/>ReadableItem<br/>SwitchableItem<br/>PlainItem"]
loader --> loader_files["WorldLoader<br/>ReferenceResolver<br/>WorldValidator"]
loader --> dto
dto --> dto_files["RoomDto, ItemDto,<br/>NpcDto, GameDto"]
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"]
game --> game_files["Game (Loop)<br/>GameContext"]
io --> io_files["GameIO (Interface)<br/>ConsoleIO<br/>SwingIO"]
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.exitsistEnumMap<Direction, Room>(typisicher, schnell) stattMap<String, String>(was zum YAML passt)- Validierung passiert beim Übergang DTO→Domain (siehe loading-flow.md)
- Domain-Tests können Objekte direkt im Code bauen, ohne YAML-Fixtures
Game-Loop (vereinfacht)
while (!game.isOver()) {
String input = io.read();
ParsedCommand parsed = parser.parse(input);
Command cmd = registry.get(parsed.verb());
if (cmd == null) {
io.write("Unbekannter Befehl.");
} else {
cmd.execute(context, parsed.args());
}
}
IO-Abstraktion
Konsole und GUI teilen sich GameIO:
public interface GameIO {
String read(); // blockierende Leseoperation
void write(String text);
}
Damit ist der Game-Loop identisch für beide Modi. SwingIO blockiert intern mit einer BlockingQueue<String>, die vom JTextField-ActionListener gefüllt wird.