From 4ab5ad9d4293c67448a89fda6a047a5d6b79f90e Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 1 Jun 2026 17:38:17 +0200 Subject: [PATCH] feat(loader): load combinations.yaml; demo matches+torch -> lit_torch Co-Authored-By: Claude Sonnet 4.6 --- .../jeanluc/adventure/loader/WorldLoader.java | 16 ++++++++- .../main/resources/world/combinations.yaml | 7 ++++ .../src/main/resources/world/items.yaml | 16 +++++++++ .../src/main/resources/world/rooms.yaml | 4 +-- .../loader/CombinationLoadingTest.java | 33 +++++++++++++++++++ .../adventure/loader/WorldLoaderTest.java | 4 ++- .../test/resources/world/combinations.yaml | 7 ++++ .../src/test/resources/world/items.yaml | 16 +++++++++ 8 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 Semesterprojekt/src/main/resources/world/combinations.yaml create mode 100644 Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/CombinationLoadingTest.java create mode 100644 Semesterprojekt/src/test/resources/world/combinations.yaml diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/WorldLoader.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/WorldLoader.java index b5c0eb1..c2ee7ef 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/WorldLoader.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/WorldLoader.java @@ -5,12 +5,14 @@ import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import thb.jeanluc.adventure.loader.dto.CombinationDto; import thb.jeanluc.adventure.loader.dto.EndingDto; import thb.jeanluc.adventure.loader.dto.GameDto; import thb.jeanluc.adventure.loader.dto.ItemDto; import thb.jeanluc.adventure.loader.dto.NpcDto; import thb.jeanluc.adventure.loader.dto.QuestDto; import thb.jeanluc.adventure.loader.dto.RoomDto; +import thb.jeanluc.adventure.model.Combination; import thb.jeanluc.adventure.model.Ending; import thb.jeanluc.adventure.model.Npc; import thb.jeanluc.adventure.model.Player; @@ -75,6 +77,8 @@ public class WorldLoader { List roomDtos = readList(basePath + "/rooms.yaml", RoomDto.class); List questDtos = readListOptional(basePath + "/quests.yaml", QuestDto.class); List endingDtos = readListOptional(basePath + "/endings.yaml", EndingDto.class); + List combinationDtos = + readListOptional(basePath + "/combinations.yaml", CombinationDto.class); GameDto gameDto = readSingle(basePath + "/game.yaml", GameDto.class); requireUniqueIds("item", itemDtos.stream().map(ItemDto::id).toList()); @@ -104,6 +108,16 @@ public class WorldLoader { endings.add(EndingFactory.fromDto(dto)); } + Map combinations = new HashMap<>(); + for (CombinationDto dto : combinationDtos) { + Combination c = CombinationFactory.fromDto(dto, items); + String key = c.key(); + if (combinations.containsKey(key)) { + throw new WorldLoadException("Duplicate combination for pair '" + key + "'"); + } + combinations.put(key, c); + } + ReferenceResolver resolver = new ReferenceResolver(items, npcs, rooms); resolver.resolveRooms(roomDtos); resolver.resolveNpcs(npcDtos); @@ -115,7 +129,7 @@ public class WorldLoader { Player player = new Player(start, gold); World world = new World(rooms, items, npcs, - gameDto.title(), gameDto.welcomeMessage(), quests, endings); + gameDto.title(), gameDto.welcomeMessage(), quests, endings, combinations); log.info("World '{}' loaded: {} rooms, {} items, {} npcs", gameDto.title(), rooms.size(), items.size(), npcs.size()); return new LoadResult(world, player); diff --git a/Semesterprojekt/src/main/resources/world/combinations.yaml b/Semesterprojekt/src/main/resources/world/combinations.yaml new file mode 100644 index 0000000..3bde52e --- /dev/null +++ b/Semesterprojekt/src/main/resources/world/combinations.yaml @@ -0,0 +1,7 @@ +- a: matches + b: torch + consume: [matches, torch] + produce: lit_torch + response: | + You strike a match and touch it to the torch; it catches with a low hiss + and burns steady. You now hold a lit torch. diff --git a/Semesterprojekt/src/main/resources/world/items.yaml b/Semesterprojekt/src/main/resources/world/items.yaml index 1320cdb..1e77b41 100644 --- a/Semesterprojekt/src/main/resources/world/items.yaml +++ b/Semesterprojekt/src/main/resources/world/items.yaml @@ -47,3 +47,19 @@ You ease the door shut again. effects: - { setFlag: fled } + +- type: plain + id: matches + name: Box of Matches + description: A small box of dry matches. They rattle when you shake it. + +- type: plain + id: torch + name: Unlit Torch + description: A pitch-soaked torch, waiting for a flame. + +- type: plain + id: lit_torch + name: Lit Torch + description: A burning torch, throwing back the dark. + light: true diff --git a/Semesterprojekt/src/main/resources/world/rooms.yaml b/Semesterprojekt/src/main/resources/world/rooms.yaml index dd9226c..c6d06e0 100644 --- a/Semesterprojekt/src/main/resources/world/rooms.yaml +++ b/Semesterprojekt/src/main/resources/world/rooms.yaml @@ -23,7 +23,7 @@ exits: south: kitchen west: library - items: [] + items: [torch] npcs: [] - id: library @@ -33,7 +33,7 @@ An old chest stands in the corner. exits: east: hallway - items: [shovel] + items: [shovel, matches] npcs: [] - id: cellar diff --git a/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/CombinationLoadingTest.java b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/CombinationLoadingTest.java new file mode 100644 index 0000000..260b36b --- /dev/null +++ b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/CombinationLoadingTest.java @@ -0,0 +1,33 @@ +package thb.jeanluc.adventure.loader; + +import org.junit.jupiter.api.Test; +import thb.jeanluc.adventure.game.Combinations; +import thb.jeanluc.adventure.game.GameContext; +import thb.jeanluc.adventure.game.GameSession; +import thb.jeanluc.adventure.io.TestIO; +import thb.jeanluc.adventure.model.Combination; + +import static org.assertj.core.api.Assertions.assertThat; + +class CombinationLoadingTest { + + @Test + void realWorldLoadsTheTorchRecipe() { + WorldLoader.LoadResult loaded = new WorldLoader().load(); + assertThat(loaded.world().getCombinations()) + .containsKey(Combination.key("matches", "torch")); + assertThat(loaded.world().getItems()).containsKeys("matches", "torch", "lit_torch"); + } + + @Test + void torchRecipeWorksEndToEnd() { + WorldLoader.LoadResult loaded = new WorldLoader().load(); + GameContext ctx = new GameContext( + new GameSession(loaded.world(), loaded.player(), "test"), new TestIO()); + // grab the demo items straight from the registry, regardless of room + ctx.getPlayer().addItem(ctx.getWorld().getItems().get("matches")); + ctx.getPlayer().addItem(ctx.getWorld().getItems().get("torch")); + Combinations.tryUse(ctx, "matches", "torch"); + assertThat(ctx.getPlayer().hasItem("lit_torch")).isTrue(); + } +} diff --git a/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/WorldLoaderTest.java b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/WorldLoaderTest.java index dc7055d..ab46f00 100644 --- a/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/WorldLoaderTest.java +++ b/Semesterprojekt/src/test/java/thb/jeanluc/adventure/loader/WorldLoaderTest.java @@ -4,6 +4,8 @@ import org.junit.jupiter.api.Test; import thb.jeanluc.adventure.model.Direction; import thb.jeanluc.adventure.model.NpcReaction; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; class WorldLoaderTest { @@ -14,7 +16,7 @@ class WorldLoaderTest { assertThat(result.world().getTitle()).isEqualTo("Test Manor"); assertThat(result.world().getRooms().keySet()).containsExactlyInAnyOrder("kitchen", "hallway"); - assertThat(result.world().getItems().keySet()).containsExactlyInAnyOrder("letter", "lamp", "key"); + assertThat(result.world().getItems().keySet()).containsAll(List.of("letter", "lamp", "key")); assertThat(result.world().getNpcs().keySet()).containsExactly("old_man"); assertThat(result.player().getCurrentRoom().getId()).isEqualTo("kitchen"); diff --git a/Semesterprojekt/src/test/resources/world/combinations.yaml b/Semesterprojekt/src/test/resources/world/combinations.yaml new file mode 100644 index 0000000..3bde52e --- /dev/null +++ b/Semesterprojekt/src/test/resources/world/combinations.yaml @@ -0,0 +1,7 @@ +- a: matches + b: torch + consume: [matches, torch] + produce: lit_torch + response: | + You strike a match and touch it to the torch; it catches with a low hiss + and burns steady. You now hold a lit torch. diff --git a/Semesterprojekt/src/test/resources/world/items.yaml b/Semesterprojekt/src/test/resources/world/items.yaml index eae04c9..a082c4a 100644 --- a/Semesterprojekt/src/test/resources/world/items.yaml +++ b/Semesterprojekt/src/test/resources/world/items.yaml @@ -17,3 +17,19 @@ id: key name: Key description: A key. + +- type: plain + id: matches + name: Box of Matches + description: A small box of dry matches. + +- type: plain + id: torch + name: Unlit Torch + description: A pitch-soaked torch. + +- type: plain + id: lit_torch + name: Lit Torch + description: A burning torch. + light: true