Files
Jander_Semester2/Semesterprojekt/docs/loading-flow.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.4 KiB

Loading-Flow

Wie aus 4 YAML-Dateien eine spielbare World mit aufgelösten Referenzen wird.

Phasen

flowchart TD
    P1["<b>Phase 1: YAML → DTOs</b><br/>Jackson liest items.yaml, npcs.yaml,<br/>rooms.yaml, game.yaml in Records.<br/>Nur Daten, IDs als Strings."]
    P2["<b>Phase 2: Domain-Shells erzeugen</b><br/>Pro DTO ein leeres Domain-Objekt.<br/>itemRegistry, npcRegistry, roomRegistry."]
    P3["<b>Phase 3: Referenzen auflösen</b><br/>Room: items-ids → Item-Objekte<br/>Room: npcs-ids → Npc-Objekte<br/>Room: exits-Strings → Direction + Room-Ref<br/>Npc: reactions auflösen (gives, consumes)"]
    P4["<b>Phase 4: Validierung</b><br/>Validierungsregeln aus yaml-schemas.md.<br/>Wirft WorldLoadException bei Verstoss."]
    P5["<b>Phase 5: Player + World zusammenstellen</b><br/>Player mit startRoom + startGold.<br/>World für den Game-Loop bereit."]

    P1 --> P2 --> P3 --> P4 --> P5

Klassen-Aufteilung

Klasse Verantwortung
WorldLoader Orchestriert die Phasen, public API: World load()
ReferenceResolver Phase 3 isoliert (testbar)
WorldValidator Phase 4 isoliert (testbar)
WorldLoadException Eigene RuntimeException mit aussagekräftiger Message

Beispiel-Skizze

public class WorldLoader {

    private final ObjectMapper yaml = new ObjectMapper(new YAMLFactory());

    public World load() {
        // Phase 1
        List<ItemDto> itemDtos = readList("/world/items.yaml", ItemDto.class);
        List<NpcDto> npcDtos = readList("/world/npcs.yaml", NpcDto.class);
        List<RoomDto> roomDtos = readList("/world/rooms.yaml", RoomDto.class);
        GameDto gameDto = readSingle("/world/game.yaml", GameDto.class);

        // Phase 2: Registries
        Map<String, Item> items = itemDtos.stream()
            .collect(Collectors.toMap(ItemDto::id, ItemFactory::fromDto));
        Map<String, Npc> npcs = npcDtos.stream()
            .collect(Collectors.toMap(NpcDto::id, NpcFactory::fromDto));
        Map<String, Room> rooms = roomDtos.stream()
            .collect(Collectors.toMap(RoomDto::id, RoomFactory::shellFromDto));

        // Phase 3: Referenzen auflösen
        ReferenceResolver resolver = new ReferenceResolver(items, npcs, rooms);
        resolver.resolveRooms(roomDtos);
        resolver.resolveNpcs(npcDtos);

        // Phase 4: validieren
        new WorldValidator(items, npcs, rooms, gameDto).validate();

        // Phase 5
        Player player = new Player(rooms.get(gameDto.startRoom()), gameDto.startGold());
        return new World(rooms, items, npcs, player, gameDto);
    }
}

Warum getrennte Phasen?

  • Testbarkeit: Resolver und Validator können isoliert mit fertigen DTOs gefüttert werden, ohne YAML auf der Platte
  • Fehlerlokalisierung: „Resolver hat versagt" vs. „Validator hat versagt" sind unterschiedliche Diagnosen
  • Zirkuläre Referenzen (Raum A → Raum B → Raum A) werden problemlos: in Phase 2 sind beide Shells da, Phase 3 verlinkt
  • Fail-fast: Spiel startet nicht mit kaputter Welt — besser als NullPointer beim ersten go north

Tests pro Phase

Test Inhalt
WorldLoaderTest Happy-Path: lädt Test-Fixtures aus src/test/resources/world/
ReferenceResolverTest DTOs als Eingabe, prüft Auflösung
WorldValidatorTest Pro Validierungsregel ein Test, der den Fehler triggert
WorldValidatorTest.happy() Vollständig valide Welt darf nicht werfen