feat(io,command): quests command, QuestText, per-turn quest tick
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import thb.jeanluc.adventure.command.impl.HelpCommand;
|
|||||||
import thb.jeanluc.adventure.command.impl.InventoryCommand;
|
import thb.jeanluc.adventure.command.impl.InventoryCommand;
|
||||||
import thb.jeanluc.adventure.command.impl.LookCommand;
|
import thb.jeanluc.adventure.command.impl.LookCommand;
|
||||||
import thb.jeanluc.adventure.command.impl.MapCommand;
|
import thb.jeanluc.adventure.command.impl.MapCommand;
|
||||||
|
import thb.jeanluc.adventure.command.impl.QuestsCommand;
|
||||||
import thb.jeanluc.adventure.command.impl.QuitCommand;
|
import thb.jeanluc.adventure.command.impl.QuitCommand;
|
||||||
import thb.jeanluc.adventure.command.impl.ReadCommand;
|
import thb.jeanluc.adventure.command.impl.ReadCommand;
|
||||||
import thb.jeanluc.adventure.command.impl.TakeCommand;
|
import thb.jeanluc.adventure.command.impl.TakeCommand;
|
||||||
@@ -70,6 +71,7 @@ public final class App {
|
|||||||
registry.register(new ReadCommand(), "read");
|
registry.register(new ReadCommand(), "read");
|
||||||
registry.register(new ExamineCommand(), "examine", "x", "inspect");
|
registry.register(new ExamineCommand(), "examine", "x", "inspect");
|
||||||
registry.register(new MapCommand(), "map", "m");
|
registry.register(new MapCommand(), "map", "m");
|
||||||
|
registry.register(new QuestsCommand(), "quests", "log", "journal");
|
||||||
registry.register(new TalkCommand(), "talk", "speak");
|
registry.register(new TalkCommand(), "talk", "speak");
|
||||||
registry.register(new GiveCommand(), "give");
|
registry.register(new GiveCommand(), "give");
|
||||||
registry.register(new HelpCommand(registry), "help", "?");
|
registry.register(new HelpCommand(registry), "help", "?");
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package thb.jeanluc.adventure.command.impl;
|
||||||
|
|
||||||
|
import thb.jeanluc.adventure.command.Command;
|
||||||
|
import thb.jeanluc.adventure.game.GameContext;
|
||||||
|
import thb.jeanluc.adventure.game.QuestEngine;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Shows the active and completed quests. Usage: {@code quests}. */
|
||||||
|
public class QuestsCommand implements Command {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(GameContext ctx, List<String> args) {
|
||||||
|
ctx.getIo().showQuests(QuestEngine.viewOf(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String help() {
|
||||||
|
return "quests - show your active and completed quests (alias: log, journal)";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,6 +60,7 @@ public class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void publishHud() {
|
private void publishHud() {
|
||||||
|
QuestEngine.tick(ctx);
|
||||||
ctx.getIo().setHud(new Hud(
|
ctx.getIo().setHud(new Hud(
|
||||||
ctx.getPlayer().getCurrentRoom().getName(),
|
ctx.getPlayer().getCurrentRoom().getName(),
|
||||||
ctx.getPlayer().getGold(),
|
ctx.getPlayer().getGold(),
|
||||||
@@ -70,6 +71,7 @@ public class Game {
|
|||||||
ctx.getPlayer().getVisitedRoomIds(),
|
ctx.getPlayer().getVisitedRoomIds(),
|
||||||
ctx.getPlayer().getCurrentRoom());
|
ctx.getPlayer().getCurrentRoom());
|
||||||
ctx.getIo().setMap(map);
|
ctx.getIo().setMap(map);
|
||||||
|
ctx.getIo().setQuests(QuestEngine.viewOf(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package thb.jeanluc.adventure.io;
|
|||||||
import thb.jeanluc.adventure.io.text.AsciiMap;
|
import thb.jeanluc.adventure.io.text.AsciiMap;
|
||||||
import thb.jeanluc.adventure.io.text.Hud;
|
import thb.jeanluc.adventure.io.text.Hud;
|
||||||
import thb.jeanluc.adventure.io.text.MapView;
|
import thb.jeanluc.adventure.io.text.MapView;
|
||||||
|
import thb.jeanluc.adventure.io.text.QuestText;
|
||||||
|
import thb.jeanluc.adventure.io.text.QuestView;
|
||||||
import thb.jeanluc.adventure.io.text.Renderings;
|
import thb.jeanluc.adventure.io.text.Renderings;
|
||||||
import thb.jeanluc.adventure.io.text.RoomView;
|
import thb.jeanluc.adventure.io.text.RoomView;
|
||||||
import thb.jeanluc.adventure.io.text.StyledText;
|
import thb.jeanluc.adventure.io.text.StyledText;
|
||||||
@@ -44,4 +46,14 @@ public interface GameIO {
|
|||||||
default void showMap(MapView view) {
|
default void showMap(MapView view) {
|
||||||
print(AsciiMap.render(view, true));
|
print(AsciiMap.render(view, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Per-turn push of the quest log. GUI repaints its box; console ignores. */
|
||||||
|
default void setQuests(QuestView view) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
/** On-demand quest log (the 'quests' command). Default renders styled text. */
|
||||||
|
default void showQuests(QuestView view) {
|
||||||
|
print(QuestText.render(view));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package thb.jeanluc.adventure.io.text;
|
||||||
|
|
||||||
|
/** Renders a {@link QuestView} as styled text for the console quest log. */
|
||||||
|
public final class QuestText {
|
||||||
|
|
||||||
|
private QuestText() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StyledText render(QuestView view) {
|
||||||
|
if (view.active().isEmpty() && view.completed().isEmpty()) {
|
||||||
|
return StyledText.of("You have no quests yet.");
|
||||||
|
}
|
||||||
|
StyledText.Builder b = StyledText.builder();
|
||||||
|
b.heading("QUESTS");
|
||||||
|
if (view.active().isEmpty()) {
|
||||||
|
b.plain("\n").dim(" (none active)");
|
||||||
|
}
|
||||||
|
for (QuestEntry e : view.active()) {
|
||||||
|
b.plain("\n").heading(e.title()).plain("\n").exit(" -> ").plain(e.objective());
|
||||||
|
}
|
||||||
|
if (!view.completed().isEmpty()) {
|
||||||
|
b.plain("\n\n").dim("Completed:");
|
||||||
|
for (String t : view.completed()) {
|
||||||
|
b.plain("\n").dim(" ✓ " + t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package thb.jeanluc.adventure.command.impl;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import thb.jeanluc.adventure.game.GameContext;
|
||||||
|
import thb.jeanluc.adventure.game.QuestEngine;
|
||||||
|
import thb.jeanluc.adventure.io.TestIO;
|
||||||
|
import thb.jeanluc.adventure.model.Condition;
|
||||||
|
import thb.jeanluc.adventure.model.Player;
|
||||||
|
import thb.jeanluc.adventure.model.Quest;
|
||||||
|
import thb.jeanluc.adventure.model.QuestStage;
|
||||||
|
import thb.jeanluc.adventure.model.Room;
|
||||||
|
import thb.jeanluc.adventure.model.World;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class QuestsCommandTest {
|
||||||
|
@Test
|
||||||
|
void showsActiveObjective() {
|
||||||
|
Quest q = new Quest("q", "My Quest", true,
|
||||||
|
List.of(new QuestStage("Do the thing",
|
||||||
|
List.of(new Condition(Condition.Type.FLAG, "x")), List.of())), List.of());
|
||||||
|
World w = new World(Map.of(), Map.of(), Map.of(), "t", "w", Map.of("q", q));
|
||||||
|
GameContext ctx = new GameContext(w, new Player(new Room("k", "K", "d"), 0), new TestIO());
|
||||||
|
QuestEngine.tick(ctx); // auto-start
|
||||||
|
|
||||||
|
new QuestsCommand().execute(ctx, List.of());
|
||||||
|
|
||||||
|
String out = ((TestIO) ctx.getIo()).allOutput();
|
||||||
|
assertThat(out).contains("My Quest").contains("Do the thing");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user