Files
Jander_Semester2/Semesterprojekt/docs/architecture.md
Jean-Luc Makiola 83643a192f semesterprojekt: implement full text adventure (phases 1-7)
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.
2026-05-25 21:37:59 +02:00

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.exits ist EnumMap<Direction, Room> (typisicher, schnell) statt Map<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.