feat(loader): load combinations.yaml; demo matches+torch -> lit_torch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,12 +5,14 @@ import com.fasterxml.jackson.databind.type.CollectionType;
|
|||||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.EndingDto;
|
||||||
import thb.jeanluc.adventure.loader.dto.GameDto;
|
import thb.jeanluc.adventure.loader.dto.GameDto;
|
||||||
import thb.jeanluc.adventure.loader.dto.ItemDto;
|
import thb.jeanluc.adventure.loader.dto.ItemDto;
|
||||||
import thb.jeanluc.adventure.loader.dto.NpcDto;
|
import thb.jeanluc.adventure.loader.dto.NpcDto;
|
||||||
import thb.jeanluc.adventure.loader.dto.QuestDto;
|
import thb.jeanluc.adventure.loader.dto.QuestDto;
|
||||||
import thb.jeanluc.adventure.loader.dto.RoomDto;
|
import thb.jeanluc.adventure.loader.dto.RoomDto;
|
||||||
|
import thb.jeanluc.adventure.model.Combination;
|
||||||
import thb.jeanluc.adventure.model.Ending;
|
import thb.jeanluc.adventure.model.Ending;
|
||||||
import thb.jeanluc.adventure.model.Npc;
|
import thb.jeanluc.adventure.model.Npc;
|
||||||
import thb.jeanluc.adventure.model.Player;
|
import thb.jeanluc.adventure.model.Player;
|
||||||
@@ -75,6 +77,8 @@ public class WorldLoader {
|
|||||||
List<RoomDto> roomDtos = readList(basePath + "/rooms.yaml", RoomDto.class);
|
List<RoomDto> roomDtos = readList(basePath + "/rooms.yaml", RoomDto.class);
|
||||||
List<QuestDto> questDtos = readListOptional(basePath + "/quests.yaml", QuestDto.class);
|
List<QuestDto> questDtos = readListOptional(basePath + "/quests.yaml", QuestDto.class);
|
||||||
List<EndingDto> endingDtos = readListOptional(basePath + "/endings.yaml", EndingDto.class);
|
List<EndingDto> endingDtos = readListOptional(basePath + "/endings.yaml", EndingDto.class);
|
||||||
|
List<CombinationDto> combinationDtos =
|
||||||
|
readListOptional(basePath + "/combinations.yaml", CombinationDto.class);
|
||||||
GameDto gameDto = readSingle(basePath + "/game.yaml", GameDto.class);
|
GameDto gameDto = readSingle(basePath + "/game.yaml", GameDto.class);
|
||||||
|
|
||||||
requireUniqueIds("item", itemDtos.stream().map(ItemDto::id).toList());
|
requireUniqueIds("item", itemDtos.stream().map(ItemDto::id).toList());
|
||||||
@@ -104,6 +108,16 @@ public class WorldLoader {
|
|||||||
endings.add(EndingFactory.fromDto(dto));
|
endings.add(EndingFactory.fromDto(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, Combination> 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);
|
ReferenceResolver resolver = new ReferenceResolver(items, npcs, rooms);
|
||||||
resolver.resolveRooms(roomDtos);
|
resolver.resolveRooms(roomDtos);
|
||||||
resolver.resolveNpcs(npcDtos);
|
resolver.resolveNpcs(npcDtos);
|
||||||
@@ -115,7 +129,7 @@ public class WorldLoader {
|
|||||||
Player player = new Player(start, gold);
|
Player player = new Player(start, gold);
|
||||||
|
|
||||||
World world = new World(rooms, items, npcs,
|
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",
|
log.info("World '{}' loaded: {} rooms, {} items, {} npcs",
|
||||||
gameDto.title(), rooms.size(), items.size(), npcs.size());
|
gameDto.title(), rooms.size(), items.size(), npcs.size());
|
||||||
return new LoadResult(world, player);
|
return new LoadResult(world, player);
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -47,3 +47,19 @@
|
|||||||
You ease the door shut again.
|
You ease the door shut again.
|
||||||
effects:
|
effects:
|
||||||
- { setFlag: fled }
|
- { 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
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
exits:
|
exits:
|
||||||
south: kitchen
|
south: kitchen
|
||||||
west: library
|
west: library
|
||||||
items: []
|
items: [torch]
|
||||||
npcs: []
|
npcs: []
|
||||||
|
|
||||||
- id: library
|
- id: library
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
An old chest stands in the corner.
|
An old chest stands in the corner.
|
||||||
exits:
|
exits:
|
||||||
east: hallway
|
east: hallway
|
||||||
items: [shovel]
|
items: [shovel, matches]
|
||||||
npcs: []
|
npcs: []
|
||||||
|
|
||||||
- id: cellar
|
- id: cellar
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import org.junit.jupiter.api.Test;
|
|||||||
import thb.jeanluc.adventure.model.Direction;
|
import thb.jeanluc.adventure.model.Direction;
|
||||||
import thb.jeanluc.adventure.model.NpcReaction;
|
import thb.jeanluc.adventure.model.NpcReaction;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
class WorldLoaderTest {
|
class WorldLoaderTest {
|
||||||
@@ -14,7 +16,7 @@ class WorldLoaderTest {
|
|||||||
|
|
||||||
assertThat(result.world().getTitle()).isEqualTo("Test Manor");
|
assertThat(result.world().getTitle()).isEqualTo("Test Manor");
|
||||||
assertThat(result.world().getRooms().keySet()).containsExactlyInAnyOrder("kitchen", "hallway");
|
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.world().getNpcs().keySet()).containsExactly("old_man");
|
||||||
|
|
||||||
assertThat(result.player().getCurrentRoom().getId()).isEqualTo("kitchen");
|
assertThat(result.player().getCurrentRoom().getId()).isEqualTo("kitchen");
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -17,3 +17,19 @@
|
|||||||
id: key
|
id: key
|
||||||
name: Key
|
name: Key
|
||||||
description: A 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
|
||||||
|
|||||||
Reference in New Issue
Block a user