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,38 @@
|
||||
package thb.jeanluc.adventure.command;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CommandParserTest {
|
||||
|
||||
private final CommandParser parser = new CommandParser();
|
||||
|
||||
@Test
|
||||
void parse_blankInput_yieldsEmptyVerb() {
|
||||
assertThat(parser.parse("").verb()).isEmpty();
|
||||
assertThat(parser.parse(" ").verb()).isEmpty();
|
||||
assertThat(parser.parse(null).verb()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void parse_lowercases() {
|
||||
ParsedCommand p = parser.parse("Go NORTH");
|
||||
assertThat(p.verb()).isEqualTo("go");
|
||||
assertThat(p.args()).containsExactly("north");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parse_dropsFillers() {
|
||||
ParsedCommand p = parser.parse("go to the north");
|
||||
assertThat(p.verb()).isEqualTo("go");
|
||||
assertThat(p.args()).containsExactly("north");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parse_giveItemToNpc_keepsBoth() {
|
||||
ParsedCommand p = parser.parse("give lamp to old_man");
|
||||
assertThat(p.verb()).isEqualTo("give");
|
||||
assertThat(p.args()).containsExactly("lamp", "old_man");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package thb.jeanluc.adventure.command;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import thb.jeanluc.adventure.game.GameContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CommandRegistryTest {
|
||||
|
||||
private static class StubCommand implements Command {
|
||||
@Override public void execute(GameContext ctx, List<String> args) {}
|
||||
@Override public String help() { return "stub"; }
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_andFindByEachAlias() {
|
||||
CommandRegistry r = new CommandRegistry();
|
||||
StubCommand cmd = new StubCommand();
|
||||
r.register(cmd, "go", "move", "walk");
|
||||
|
||||
assertThat(r.find("go")).contains(cmd);
|
||||
assertThat(r.find("MOVE")).contains(cmd);
|
||||
assertThat(r.find("walk")).contains(cmd);
|
||||
}
|
||||
|
||||
@Test
|
||||
void find_unknownVerb_returnsEmpty() {
|
||||
assertThat(new CommandRegistry().find("nope")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void distinctCommands_deduplicatesAliases() {
|
||||
CommandRegistry r = new CommandRegistry();
|
||||
StubCommand a = new StubCommand();
|
||||
StubCommand b = new StubCommand();
|
||||
r.register(a, "x", "y");
|
||||
r.register(b, "z");
|
||||
|
||||
assertThat(r.distinctCommands()).containsExactlyInAnyOrder(a, b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package thb.jeanluc.adventure.command.impl;
|
||||
|
||||
import thb.jeanluc.adventure.game.GameContext;
|
||||
import thb.jeanluc.adventure.io.TestIO;
|
||||
import thb.jeanluc.adventure.model.Direction;
|
||||
import thb.jeanluc.adventure.model.Npc;
|
||||
import thb.jeanluc.adventure.model.NpcReaction;
|
||||
import thb.jeanluc.adventure.model.Player;
|
||||
import thb.jeanluc.adventure.model.Room;
|
||||
import thb.jeanluc.adventure.model.item.Item;
|
||||
import thb.jeanluc.adventure.model.item.PlainItem;
|
||||
import thb.jeanluc.adventure.model.item.ReadableItem;
|
||||
import thb.jeanluc.adventure.model.item.SwitchableItem;
|
||||
|
||||
/**
|
||||
* Tiny world used by the command tests. Two rooms (kitchen → hallway),
|
||||
* a few items, and one NPC with one reaction.
|
||||
*/
|
||||
public final class CommandTestSupport {
|
||||
|
||||
private CommandTestSupport() {}
|
||||
|
||||
public static class World {
|
||||
public final Room kitchen;
|
||||
public final Room hallway;
|
||||
public final Item lamp;
|
||||
public final Item letter;
|
||||
public final Item key;
|
||||
public final Item shovel;
|
||||
public final Npc oldMan;
|
||||
public final Player player;
|
||||
public final TestIO io = new TestIO();
|
||||
public final GameContext ctx;
|
||||
|
||||
public World() {
|
||||
kitchen = new Room("kitchen", "Kitchen", "kitchen desc");
|
||||
hallway = new Room("hallway", "Hallway", "hallway desc");
|
||||
kitchen.addExit(Direction.NORTH, hallway);
|
||||
hallway.addExit(Direction.SOUTH, kitchen);
|
||||
|
||||
lamp = SwitchableItem.builder().id("lamp").name("Lamp").description("a lamp")
|
||||
.state(false).onText("on").offText("off").build();
|
||||
letter = ReadableItem.builder().id("letter").name("Letter").description("a letter")
|
||||
.readText("dear reader").build();
|
||||
key = PlainItem.builder().id("key").name("Key").description("brass key").build();
|
||||
shovel = PlainItem.builder().id("shovel").name("Shovel").description("rusty").build();
|
||||
|
||||
kitchen.addItem(lamp);
|
||||
kitchen.addItem(letter);
|
||||
|
||||
oldMan = Npc.shell("old_man", "Old Man", "stooped", "greetings");
|
||||
oldMan.putReaction("lamp", NpcReaction.builder()
|
||||
.consumes(lamp).gives(key).response("Take the key.").build());
|
||||
kitchen.addNpc(oldMan);
|
||||
|
||||
player = new Player(kitchen, 0);
|
||||
ctx = new GameContext(null, player, io);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package thb.jeanluc.adventure.command.impl;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class GoCommandTest {
|
||||
|
||||
@Test
|
||||
void execute_validDirection_movesPlayer() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
|
||||
new GoCommand().execute(w.ctx, List.of("north"));
|
||||
|
||||
assertThat(w.player.getCurrentRoom()).isEqualTo(w.hallway);
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_noExit_complains() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
|
||||
new GoCommand().execute(w.ctx, List.of("south"));
|
||||
|
||||
assertThat(w.player.getCurrentRoom()).isEqualTo(w.kitchen);
|
||||
assertThat(w.io.allOutput()).contains("can't go south");
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_unknownDirection_complains() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
|
||||
new GoCommand().execute(w.ctx, List.of("upwards"));
|
||||
|
||||
assertThat(w.player.getCurrentRoom()).isEqualTo(w.kitchen);
|
||||
assertThat(w.io.allOutput()).contains("upwards");
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_noArg_complains() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
|
||||
new GoCommand().execute(w.ctx, List.of());
|
||||
|
||||
assertThat(w.io.allOutput()).contains("Go where");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package thb.jeanluc.adventure.command.impl;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class InventoryCommandTest {
|
||||
|
||||
@Test
|
||||
void empty_writesEmptyMessage() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new InventoryCommand().execute(w.ctx, List.of());
|
||||
assertThat(w.io.lastOutput()).contains("not carrying");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withItems_listsByName() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new TakeCommand().execute(w.ctx, List.of("letter"));
|
||||
new TakeCommand().execute(w.ctx, List.of("lamp"));
|
||||
|
||||
new InventoryCommand().execute(w.ctx, List.of());
|
||||
|
||||
assertThat(w.io.lastOutput()).contains("Letter").contains("Lamp");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package thb.jeanluc.adventure.command.impl;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LookCommandTest {
|
||||
|
||||
@Test
|
||||
void look_writesNameDescriptionItemsNpcsAndExits() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
|
||||
new LookCommand().execute(w.ctx, List.of());
|
||||
|
||||
String text = w.io.lastOutput();
|
||||
assertThat(text).contains("Kitchen");
|
||||
assertThat(text).contains("kitchen desc");
|
||||
assertThat(text).contains("Lamp").contains("Letter");
|
||||
assertThat(text).contains("Old Man");
|
||||
assertThat(text).contains("north");
|
||||
}
|
||||
|
||||
@Test
|
||||
void look_noItemsNoNpcs_stillWorks() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
w.player.setCurrentRoom(w.hallway);
|
||||
|
||||
new LookCommand().execute(w.ctx, List.of());
|
||||
|
||||
String text = w.io.lastOutput();
|
||||
assertThat(text).contains("Hallway");
|
||||
assertThat(text).contains("south");
|
||||
assertThat(text).doesNotContain("You see");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package thb.jeanluc.adventure.command.impl;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TakeDropCommandTest {
|
||||
|
||||
@Test
|
||||
void take_movesItemFromRoomToInventory() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
|
||||
new TakeCommand().execute(w.ctx, List.of("lamp"));
|
||||
|
||||
assertThat(w.player.hasItem("lamp")).isTrue();
|
||||
assertThat(w.kitchen.findItem("lamp")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void take_unknownItem_complains() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new TakeCommand().execute(w.ctx, List.of("dragon"));
|
||||
assertThat(w.io.allOutput()).contains("no 'dragon'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void drop_movesItemFromInventoryToRoom() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new TakeCommand().execute(w.ctx, List.of("lamp"));
|
||||
|
||||
new DropCommand().execute(w.ctx, List.of("lamp"));
|
||||
|
||||
assertThat(w.player.hasItem("lamp")).isFalse();
|
||||
assertThat(w.kitchen.findItem("lamp")).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void drop_notInInventory_complains() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new DropCommand().execute(w.ctx, List.of("lamp"));
|
||||
assertThat(w.io.allOutput()).contains("not carrying");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package thb.jeanluc.adventure.command.impl;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TalkGiveCommandTest {
|
||||
|
||||
@Test
|
||||
void talk_existingNpc_writesGreeting() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new TalkCommand().execute(w.ctx, List.of("old_man"));
|
||||
assertThat(w.io.lastOutput()).contains("Old Man").contains("greetings");
|
||||
}
|
||||
|
||||
@Test
|
||||
void talk_missingNpc_complains() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new TalkCommand().execute(w.ctx, List.of("ghost"));
|
||||
assertThat(w.io.allOutput()).contains("no 'ghost'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void give_matchingReaction_swapsItems() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new TakeCommand().execute(w.ctx, List.of("lamp"));
|
||||
|
||||
new GiveCommand().execute(w.ctx, List.of("lamp", "old_man"));
|
||||
|
||||
assertThat(w.player.hasItem("lamp")).isFalse();
|
||||
assertThat(w.player.hasItem("key")).isTrue();
|
||||
assertThat(w.io.lastOutput()).contains("Take the key");
|
||||
}
|
||||
|
||||
@Test
|
||||
void give_noReaction_complains() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new TakeCommand().execute(w.ctx, List.of("letter"));
|
||||
|
||||
new GiveCommand().execute(w.ctx, List.of("letter", "old_man"));
|
||||
|
||||
assertThat(w.player.hasItem("letter")).isTrue();
|
||||
assertThat(w.io.lastOutput()).contains("does not react");
|
||||
}
|
||||
|
||||
@Test
|
||||
void give_unknownNpc_complains() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new TakeCommand().execute(w.ctx, List.of("lamp"));
|
||||
|
||||
new GiveCommand().execute(w.ctx, List.of("lamp", "ghost"));
|
||||
|
||||
assertThat(w.io.allOutput()).contains("no 'ghost'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void give_itemNotHeld_complains() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new GiveCommand().execute(w.ctx, List.of("lamp", "old_man"));
|
||||
assertThat(w.io.allOutput()).contains("not carrying");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package thb.jeanluc.adventure.command.impl;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class UseReadExamineCommandTest {
|
||||
|
||||
@Test
|
||||
void use_readableInInventory_writesText() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new TakeCommand().execute(w.ctx, List.of("letter"));
|
||||
|
||||
new UseCommand().execute(w.ctx, List.of("letter"));
|
||||
|
||||
assertThat(w.io.lastOutput()).isEqualTo("dear reader");
|
||||
}
|
||||
|
||||
@Test
|
||||
void use_unknownItem_complains() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new UseCommand().execute(w.ctx, List.of("dragon"));
|
||||
assertThat(w.io.allOutput()).contains("no 'dragon'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void read_nonReadable_complains() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new TakeCommand().execute(w.ctx, List.of("lamp"));
|
||||
|
||||
new ReadCommand().execute(w.ctx, List.of("lamp"));
|
||||
|
||||
assertThat(w.io.lastOutput()).contains("nothing to read");
|
||||
}
|
||||
|
||||
@Test
|
||||
void examine_item_writesDescription() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new ExamineCommand().execute(w.ctx, List.of("letter"));
|
||||
assertThat(w.io.lastOutput()).isEqualTo("a letter");
|
||||
}
|
||||
|
||||
@Test
|
||||
void examine_npc_writesDescription() {
|
||||
CommandTestSupport.World w = new CommandTestSupport.World();
|
||||
new ExamineCommand().execute(w.ctx, List.of("old_man"));
|
||||
assertThat(w.io.lastOutput()).isEqualTo("stooped");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package thb.jeanluc.adventure.game;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import thb.jeanluc.adventure.command.CommandParser;
|
||||
import thb.jeanluc.adventure.command.CommandRegistry;
|
||||
import thb.jeanluc.adventure.command.impl.GoCommand;
|
||||
import thb.jeanluc.adventure.command.impl.LookCommand;
|
||||
import thb.jeanluc.adventure.command.impl.QuitCommand;
|
||||
import thb.jeanluc.adventure.io.TestIO;
|
||||
import thb.jeanluc.adventure.model.Direction;
|
||||
import thb.jeanluc.adventure.model.Player;
|
||||
import thb.jeanluc.adventure.model.Room;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class GameTest {
|
||||
|
||||
@Test
|
||||
void run_dispatchesCommands_andQuitsCleanly() {
|
||||
Room kitchen = new Room("kitchen", "Kitchen", "kd");
|
||||
Room hallway = new Room("hallway", "Hallway", "hd");
|
||||
kitchen.addExit(Direction.NORTH, hallway);
|
||||
Player player = new Player(kitchen, 0);
|
||||
TestIO io = new TestIO()
|
||||
.enqueue("go north")
|
||||
.enqueue("quit");
|
||||
GameContext ctx = new GameContext(null, player, io);
|
||||
|
||||
CommandRegistry registry = new CommandRegistry();
|
||||
registry.register(new GoCommand(), "go");
|
||||
registry.register(new LookCommand(), "look");
|
||||
QuitCommand quit = new QuitCommand();
|
||||
registry.register(quit, "quit");
|
||||
|
||||
Game game = new Game(ctx, registry, new CommandParser());
|
||||
quit.bind(game);
|
||||
game.run();
|
||||
|
||||
assertThat(player.getCurrentRoom()).isEqualTo(hallway);
|
||||
assertThat(io.allOutput()).contains("Goodbye");
|
||||
}
|
||||
|
||||
@Test
|
||||
void run_unknownVerb_writesHint() {
|
||||
Player player = new Player(new Room("r", "R", "d"), 0);
|
||||
TestIO io = new TestIO().enqueue("dance").enqueue("quit");
|
||||
GameContext ctx = new GameContext(null, player, io);
|
||||
|
||||
CommandRegistry registry = new CommandRegistry();
|
||||
QuitCommand quit = new QuitCommand();
|
||||
registry.register(quit, "quit");
|
||||
Game game = new Game(ctx, registry, new CommandParser());
|
||||
quit.bind(game);
|
||||
game.run();
|
||||
|
||||
assertThat(io.allOutput()).contains("don't understand 'dance'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package thb.jeanluc.adventure.io;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Simple in-memory {@link GameIO} for tests: queued inputs, captured outputs.
|
||||
*/
|
||||
public class TestIO implements GameIO {
|
||||
|
||||
private final Deque<String> inputs = new ArrayDeque<>();
|
||||
private final List<String> outputs = new ArrayList<>();
|
||||
|
||||
public TestIO enqueue(String line) {
|
||||
inputs.add(line);
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> outputs() {
|
||||
return outputs;
|
||||
}
|
||||
|
||||
public String lastOutput() {
|
||||
return outputs.isEmpty() ? null : outputs.getLast();
|
||||
}
|
||||
|
||||
public String allOutput() {
|
||||
return String.join("\n", outputs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readLine() {
|
||||
return inputs.pollFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String s) {
|
||||
outputs.add(s);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package thb.jeanluc.adventure.model;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class DirectionTest {
|
||||
|
||||
@Test
|
||||
void getOpposite_pairsAreSymmetric() {
|
||||
for (Direction d : Direction.values()) {
|
||||
assertThat(d.getOpposite().getOpposite()).isEqualTo(d);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromString_caseInsensitive_returnsEnum() {
|
||||
assertThat(Direction.fromString("north")).isEqualTo(Direction.NORTH);
|
||||
assertThat(Direction.fromString("EAST")).isEqualTo(Direction.EAST);
|
||||
assertThat(Direction.fromString(" west ")).isEqualTo(Direction.WEST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromString_unknown_throws() {
|
||||
assertThatThrownBy(() -> Direction.fromString("up"))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("up");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromString_null_throws() {
|
||||
assertThatThrownBy(() -> Direction.fromString(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLabel_isLowercase() {
|
||||
assertThat(Direction.NORTH.getLabel()).isEqualTo("north");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package thb.jeanluc.adventure.model;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import thb.jeanluc.adventure.model.item.Item;
|
||||
import thb.jeanluc.adventure.model.item.PlainItem;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class NpcTest {
|
||||
|
||||
@Test
|
||||
void shell_isMutableForReactions() {
|
||||
Npc man = Npc.shell("man", "Man", "d", "hi");
|
||||
Item lamp = PlainItem.builder().id("lamp").name("Lamp").description("d").build();
|
||||
|
||||
man.putReaction("lamp", NpcReaction.builder()
|
||||
.consumes(lamp).response("thanks").build());
|
||||
|
||||
assertThat(man.reactionFor("lamp")).isPresent();
|
||||
assertThat(man.reactionFor("nope")).isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package thb.jeanluc.adventure.model;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import thb.jeanluc.adventure.model.item.Item;
|
||||
import thb.jeanluc.adventure.model.item.PlainItem;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class PlayerTest {
|
||||
|
||||
@Test
|
||||
void constructor_nullRoom_throws() {
|
||||
assertThatThrownBy(() -> new Player(null, 0))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructor_negativeGold_throws() {
|
||||
Room r = new Room("r", "R", "d");
|
||||
assertThatThrownBy(() -> new Player(r, -1))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addItem_andRemoveItem_areConsistent() {
|
||||
Player p = new Player(new Room("r", "R", "d"), 0);
|
||||
Item it = PlainItem.builder().id("key").name("Key").description("d").build();
|
||||
|
||||
p.addItem(it);
|
||||
assertThat(p.hasItem("key")).isTrue();
|
||||
|
||||
assertThat(p.removeItem("key")).contains(it);
|
||||
assertThat(p.hasItem("key")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void inventory_preservesInsertionOrder() {
|
||||
Player p = new Player(new Room("r", "R", "d"), 0);
|
||||
p.addItem(PlainItem.builder().id("a").name("A").description("d").build());
|
||||
p.addItem(PlainItem.builder().id("b").name("B").description("d").build());
|
||||
p.addItem(PlainItem.builder().id("c").name("C").description("d").build());
|
||||
|
||||
assertThat(p.getInventory().keySet()).containsExactly("a", "b", "c");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setCurrentRoom_updatesPosition() {
|
||||
Room a = new Room("a", "A", "d");
|
||||
Room b = new Room("b", "B", "d");
|
||||
Player p = new Player(a, 0);
|
||||
|
||||
p.setCurrentRoom(b);
|
||||
|
||||
assertThat(p.getCurrentRoom()).isEqualTo(b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package thb.jeanluc.adventure.model;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import thb.jeanluc.adventure.model.item.Item;
|
||||
import thb.jeanluc.adventure.model.item.PlainItem;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class RoomTest {
|
||||
|
||||
@Test
|
||||
void addExit_withNewDirection_storesNeighbour() {
|
||||
Room a = new Room("a", "A", "first");
|
||||
Room b = new Room("b", "B", "second");
|
||||
|
||||
a.addExit(Direction.NORTH, b);
|
||||
|
||||
assertThat(a.getExit(Direction.NORTH)).contains(b);
|
||||
assertThat(a.getExit(Direction.SOUTH)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void addItem_storesUnderItemId() {
|
||||
Room room = new Room("r", "R", "d");
|
||||
Item lamp = PlainItem.builder().id("lamp").name("Lamp").description("d").build();
|
||||
|
||||
room.addItem(lamp);
|
||||
|
||||
assertThat(room.findItem("lamp")).contains(lamp);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeItem_returnsAndDetachesItem() {
|
||||
Room room = new Room("r", "R", "d");
|
||||
Item lamp = PlainItem.builder().id("lamp").name("Lamp").description("d").build();
|
||||
room.addItem(lamp);
|
||||
|
||||
var removed = room.removeItem("lamp");
|
||||
|
||||
assertThat(removed).contains(lamp);
|
||||
assertThat(room.findItem("lamp")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeItem_unknownId_returnsEmpty() {
|
||||
Room room = new Room("r", "R", "d");
|
||||
assertThat(room.removeItem("nope")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void addNpc_andFindById() {
|
||||
Room room = new Room("r", "R", "d");
|
||||
Npc npc = Npc.shell("man", "Man", "d", "hi");
|
||||
|
||||
room.addNpc(npc);
|
||||
|
||||
assertThat(room.findNpc("man")).contains(npc);
|
||||
}
|
||||
|
||||
@Test
|
||||
void items_preserveInsertionOrder() {
|
||||
Room room = new Room("r", "R", "d");
|
||||
room.addItem(PlainItem.builder().id("a").name("A").description("d").build());
|
||||
room.addItem(PlainItem.builder().id("b").name("B").description("d").build());
|
||||
room.addItem(PlainItem.builder().id("c").name("C").description("d").build());
|
||||
|
||||
assertThat(room.getItems().keySet()).containsExactly("a", "b", "c");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package thb.jeanluc.adventure.model.item;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import thb.jeanluc.adventure.game.GameContext;
|
||||
import thb.jeanluc.adventure.io.TestIO;
|
||||
import thb.jeanluc.adventure.model.Player;
|
||||
import thb.jeanluc.adventure.model.Room;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ItemTest {
|
||||
|
||||
private GameContext newCtx(TestIO io) {
|
||||
Player p = new Player(new Room("r", "R", "d"), 0);
|
||||
return new GameContext(null, p, io);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readableItem_use_writesReadText() {
|
||||
TestIO io = new TestIO();
|
||||
ReadableItem letter = ReadableItem.builder()
|
||||
.id("letter").name("Letter").description("d")
|
||||
.readText("hello world").build();
|
||||
|
||||
letter.use(newCtx(io));
|
||||
|
||||
assertThat(io.lastOutput()).isEqualTo("hello world");
|
||||
}
|
||||
|
||||
@Test
|
||||
void switchableItem_use_togglesAndPrintsAppropriateText() {
|
||||
TestIO io = new TestIO();
|
||||
SwitchableItem lamp = SwitchableItem.builder()
|
||||
.id("lamp").name("Lamp").description("d")
|
||||
.state(false).onText("on").offText("off").build();
|
||||
|
||||
lamp.use(newCtx(io));
|
||||
assertThat(lamp.isOn()).isTrue();
|
||||
assertThat(io.outputs()).containsExactly("on");
|
||||
|
||||
lamp.use(newCtx(io));
|
||||
assertThat(lamp.isOn()).isFalse();
|
||||
assertThat(io.outputs()).endsWith("off");
|
||||
}
|
||||
|
||||
@Test
|
||||
void switchableItem_initialStateTrue_startsOn() {
|
||||
SwitchableItem lamp = SwitchableItem.builder()
|
||||
.id("lamp").name("Lamp").description("d")
|
||||
.state(true).onText("on").offText("off").build();
|
||||
|
||||
assertThat(lamp.isOn()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void plainItem_use_writesGenericMessage() {
|
||||
TestIO io = new TestIO();
|
||||
PlainItem shovel = PlainItem.builder()
|
||||
.id("shovel").name("Shovel").description("d").build();
|
||||
|
||||
shovel.use(newCtx(io));
|
||||
|
||||
assertThat(io.lastOutput()).contains("Shovel");
|
||||
assertThat(io.lastOutput()).contains("can't use");
|
||||
}
|
||||
}
|
||||
6
Semesterprojekt/src/test/resources/world/game.yaml
Normal file
6
Semesterprojekt/src/test/resources/world/game.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
title: Test Manor
|
||||
version: "test"
|
||||
startRoom: kitchen
|
||||
startGold: 5
|
||||
welcomeMessage: |
|
||||
Welcome to the test.
|
||||
19
Semesterprojekt/src/test/resources/world/items.yaml
Normal file
19
Semesterprojekt/src/test/resources/world/items.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
- type: readable
|
||||
id: letter
|
||||
name: Letter
|
||||
description: A note.
|
||||
readText: |
|
||||
hello
|
||||
|
||||
- type: switchable
|
||||
id: lamp
|
||||
name: Lamp
|
||||
description: A lamp.
|
||||
initialState: false
|
||||
onText: on
|
||||
offText: off
|
||||
|
||||
- type: plain
|
||||
id: key
|
||||
name: Key
|
||||
description: A key.
|
||||
10
Semesterprojekt/src/test/resources/world/npcs.yaml
Normal file
10
Semesterprojekt/src/test/resources/world/npcs.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
- id: old_man
|
||||
name: Old Man
|
||||
description: stooped
|
||||
greeting: |
|
||||
greetings
|
||||
reactions:
|
||||
- onReceive: lamp
|
||||
response: thanks
|
||||
gives: key
|
||||
consumes: lamp
|
||||
15
Semesterprojekt/src/test/resources/world/rooms.yaml
Normal file
15
Semesterprojekt/src/test/resources/world/rooms.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
- id: kitchen
|
||||
name: Kitchen
|
||||
description: kitchen desc
|
||||
exits:
|
||||
north: hallway
|
||||
items: [letter, lamp]
|
||||
npcs: [old_man]
|
||||
|
||||
- id: hallway
|
||||
name: Hallway
|
||||
description: hallway desc
|
||||
exits:
|
||||
south: kitchen
|
||||
items: []
|
||||
npcs: []
|
||||
Reference in New Issue
Block a user