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.
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
package thb.jeanluc.adventure.loader;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import thb.jeanluc.adventure.loader.dto.NpcDto;
|
||||
import thb.jeanluc.adventure.loader.dto.ReactionDto;
|
||||
import thb.jeanluc.adventure.loader.dto.RoomDto;
|
||||
import thb.jeanluc.adventure.model.Direction;
|
||||
import thb.jeanluc.adventure.model.Npc;
|
||||
import thb.jeanluc.adventure.model.Room;
|
||||
import thb.jeanluc.adventure.model.item.Item;
|
||||
import thb.jeanluc.adventure.model.item.PlainItem;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class ReferenceResolverTest {
|
||||
|
||||
private static Map<String, Item> oneItem(String id) {
|
||||
Map<String, Item> m = new HashMap<>();
|
||||
m.put(id, PlainItem.builder().id(id).name(id).description("d").build());
|
||||
return m;
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRooms_wiresExitsAndItems() {
|
||||
Map<String, Room> rooms = new HashMap<>();
|
||||
rooms.put("a", new Room("a", "A", "d"));
|
||||
rooms.put("b", new Room("b", "B", "d"));
|
||||
Map<String, Item> items = oneItem("key");
|
||||
|
||||
new ReferenceResolver(items, new HashMap<>(), rooms).resolveRooms(List.of(
|
||||
new RoomDto("a", "A", "d", Map.of("north", "b"), List.of("key"), List.of()),
|
||||
new RoomDto("b", "B", "d", Map.of("south", "a"), List.of(), List.of())
|
||||
));
|
||||
|
||||
assertThat(rooms.get("a").getExit(Direction.NORTH)).contains(rooms.get("b"));
|
||||
assertThat(rooms.get("a").findItem("key")).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRooms_unknownExitTarget_throws() {
|
||||
Map<String, Room> rooms = new HashMap<>();
|
||||
rooms.put("a", new Room("a", "A", "d"));
|
||||
|
||||
assertThatThrownBy(() ->
|
||||
new ReferenceResolver(Map.of(), Map.of(), rooms).resolveRooms(List.of(
|
||||
new RoomDto("a", "A", "d", Map.of("north", "ghost"), List.of(), List.of())
|
||||
)))
|
||||
.isInstanceOf(WorldLoadException.class)
|
||||
.hasMessageContaining("ghost");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRooms_unknownDirection_throws() {
|
||||
Map<String, Room> rooms = new HashMap<>();
|
||||
rooms.put("a", new Room("a", "A", "d"));
|
||||
|
||||
assertThatThrownBy(() ->
|
||||
new ReferenceResolver(Map.of(), Map.of(), rooms).resolveRooms(List.of(
|
||||
new RoomDto("a", "A", "d", Map.of("up", "a"), List.of(), List.of())
|
||||
)))
|
||||
.isInstanceOf(WorldLoadException.class)
|
||||
.hasMessageContaining("up");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveNpcs_wiresReactionItems() {
|
||||
Map<String, Item> items = oneItem("lamp");
|
||||
items.put("key", PlainItem.builder().id("key").name("Key").description("d").build());
|
||||
Map<String, Npc> npcs = new HashMap<>();
|
||||
npcs.put("man", Npc.shell("man", "Man", "d", "hi"));
|
||||
|
||||
new ReferenceResolver(items, npcs, Map.of()).resolveNpcs(List.of(
|
||||
new NpcDto("man", "Man", "d", "hi", List.of(
|
||||
new ReactionDto("lamp", "thx", "key", "lamp")
|
||||
))
|
||||
));
|
||||
|
||||
var reaction = npcs.get("man").reactionFor("lamp").orElseThrow();
|
||||
assertThat(reaction.getGives()).isEqualTo(items.get("key"));
|
||||
assertThat(reaction.getConsumes()).isEqualTo(items.get("lamp"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveNpcs_duplicateTrigger_throws() {
|
||||
Map<String, Item> items = oneItem("lamp");
|
||||
Map<String, Npc> npcs = new HashMap<>();
|
||||
npcs.put("man", Npc.shell("man", "Man", "d", "hi"));
|
||||
|
||||
assertThatThrownBy(() ->
|
||||
new ReferenceResolver(items, npcs, Map.of()).resolveNpcs(List.of(
|
||||
new NpcDto("man", "Man", "d", "hi", List.of(
|
||||
new ReactionDto("lamp", "a", null, null),
|
||||
new ReactionDto("lamp", "b", null, null)
|
||||
))
|
||||
)))
|
||||
.isInstanceOf(WorldLoadException.class)
|
||||
.hasMessageContaining("duplicate");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveNpcs_unknownGivesItem_throws() {
|
||||
Map<String, Item> items = oneItem("lamp");
|
||||
Map<String, Npc> npcs = new HashMap<>();
|
||||
npcs.put("man", Npc.shell("man", "Man", "d", "hi"));
|
||||
|
||||
assertThatThrownBy(() ->
|
||||
new ReferenceResolver(items, npcs, Map.of()).resolveNpcs(List.of(
|
||||
new NpcDto("man", "Man", "d", "hi", List.of(
|
||||
new ReactionDto("lamp", "a", "ghost", null)
|
||||
))
|
||||
)))
|
||||
.isInstanceOf(WorldLoadException.class)
|
||||
.hasMessageContaining("ghost");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package thb.jeanluc.adventure.loader;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import thb.jeanluc.adventure.model.Direction;
|
||||
import thb.jeanluc.adventure.model.NpcReaction;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class WorldLoaderTest {
|
||||
|
||||
@Test
|
||||
void load_happyPath_buildsWorldFromTestFixtures() {
|
||||
WorldLoader.LoadResult result = new WorldLoader().load();
|
||||
|
||||
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().getNpcs().keySet()).containsExactly("old_man");
|
||||
|
||||
assertThat(result.player().getCurrentRoom().getId()).isEqualTo("kitchen");
|
||||
assertThat(result.player().getGold()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void load_resolvesExitsBidirectionally_whenYamlDeclaresThem() {
|
||||
var loaded = new WorldLoader().load().world();
|
||||
var kitchen = loaded.getRooms().get("kitchen");
|
||||
var hallway = loaded.getRooms().get("hallway");
|
||||
|
||||
assertThat(kitchen.getExit(Direction.NORTH)).contains(hallway);
|
||||
assertThat(hallway.getExit(Direction.SOUTH)).contains(kitchen);
|
||||
}
|
||||
|
||||
@Test
|
||||
void load_resolvesNpcReactionsToItemReferences() {
|
||||
var loaded = new WorldLoader().load().world();
|
||||
var oldMan = loaded.getNpcs().get("old_man");
|
||||
|
||||
assertThat(oldMan.getReactions()).containsKey("lamp");
|
||||
NpcReaction r = oldMan.reactionFor("lamp").orElseThrow();
|
||||
assertThat(r.getConsumes()).isEqualTo(loaded.getItems().get("lamp"));
|
||||
assertThat(r.getGives()).isEqualTo(loaded.getItems().get("key"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package thb.jeanluc.adventure.loader;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import thb.jeanluc.adventure.loader.dto.GameDto;
|
||||
import thb.jeanluc.adventure.model.Npc;
|
||||
import thb.jeanluc.adventure.model.Room;
|
||||
import thb.jeanluc.adventure.model.item.Item;
|
||||
import thb.jeanluc.adventure.model.item.PlainItem;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class WorldValidatorTest {
|
||||
|
||||
private Map<String, Item> items() {
|
||||
Map<String, Item> m = new HashMap<>();
|
||||
m.put("lamp", PlainItem.builder().id("lamp").name("Lamp").description("d").build());
|
||||
return m;
|
||||
}
|
||||
|
||||
private Map<String, Room> rooms() {
|
||||
Map<String, Room> m = new HashMap<>();
|
||||
m.put("kitchen", new Room("kitchen", "K", "d"));
|
||||
return m;
|
||||
}
|
||||
|
||||
private Map<String, Npc> npcs(String greeting) {
|
||||
Map<String, Npc> m = new HashMap<>();
|
||||
m.put("man", Npc.shell("man", "Man", "d", greeting));
|
||||
return m;
|
||||
}
|
||||
|
||||
@Test
|
||||
void happyPath_doesNotThrow() {
|
||||
var v = new WorldValidator(items(), npcs("hi"), rooms(),
|
||||
new GameDto("t", "1", "kitchen", 0, "w"));
|
||||
assertThat(v).isNotNull();
|
||||
v.validate();
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidId_throws() {
|
||||
Map<String, Room> r = new HashMap<>();
|
||||
r.put("Kitchen-1", new Room("Kitchen-1", "K", "d"));
|
||||
var v = new WorldValidator(items(), npcs("hi"), r,
|
||||
new GameDto("t", "1", "kitchen", 0, "w"));
|
||||
assertThatThrownBy(v::validate)
|
||||
.isInstanceOf(WorldLoadException.class)
|
||||
.hasMessageContaining("Invalid id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unknownStartRoom_throws() {
|
||||
var v = new WorldValidator(items(), npcs("hi"), rooms(),
|
||||
new GameDto("t", "1", "ghost_room", 0, "w"));
|
||||
assertThatThrownBy(v::validate)
|
||||
.isInstanceOf(WorldLoadException.class)
|
||||
.hasMessageContaining("ghost_room");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyGreeting_throws() {
|
||||
var v = new WorldValidator(items(), npcs(""), rooms(),
|
||||
new GameDto("t", "1", "kitchen", 0, "w"));
|
||||
assertThatThrownBy(v::validate)
|
||||
.isInstanceOf(WorldLoadException.class)
|
||||
.hasMessageContaining("greeting");
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingStartRoomField_throws() {
|
||||
var v = new WorldValidator(items(), npcs("hi"), rooms(),
|
||||
new GameDto("t", "1", null, 0, "w"));
|
||||
assertThatThrownBy(v::validate)
|
||||
.isInstanceOf(WorldLoadException.class)
|
||||
.hasMessageContaining("startRoom");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user