feat(game): Quest model, QuestLog, START_QUEST effect
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ public final class Effects {
|
||||
}
|
||||
case REMOVE_ITEM -> ctx.getPlayer().removeItem(e.arg());
|
||||
case SAY -> ctx.getIo().write(e.arg());
|
||||
case START_QUEST -> ctx.getQuestLog().start(e.arg());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,4 +26,7 @@ public class GameContext {
|
||||
|
||||
/** Mutable world flags. Created fresh per context; not a constructor arg. */
|
||||
private final GameState state = new GameState();
|
||||
|
||||
/** Runtime quest progress. Created fresh per context; not a constructor arg. */
|
||||
private final QuestLog questLog = new QuestLog();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package thb.jeanluc.adventure.game;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** Runtime quest progress: current stage per active quest, plus completed ids. */
|
||||
public class QuestLog {
|
||||
|
||||
private final Map<String, Integer> stageIndex = new LinkedHashMap<>();
|
||||
private final Set<String> completed = new LinkedHashSet<>();
|
||||
|
||||
public void start(String id) {
|
||||
if (!completed.contains(id)) {
|
||||
stageIndex.putIfAbsent(id, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isActive(String id) {
|
||||
return stageIndex.containsKey(id);
|
||||
}
|
||||
|
||||
public boolean isCompleted(String id) {
|
||||
return completed.contains(id);
|
||||
}
|
||||
|
||||
public int stageIndex(String id) {
|
||||
return stageIndex.getOrDefault(id, 0);
|
||||
}
|
||||
|
||||
public void advance(String id) {
|
||||
stageIndex.merge(id, 1, Integer::sum);
|
||||
}
|
||||
|
||||
public void complete(String id) {
|
||||
stageIndex.remove(id);
|
||||
completed.add(id);
|
||||
}
|
||||
|
||||
public Set<String> active() {
|
||||
return Collections.unmodifiableSet(stageIndex.keySet());
|
||||
}
|
||||
|
||||
public Set<String> completed() {
|
||||
return Collections.unmodifiableSet(completed);
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,5 @@ package thb.jeanluc.adventure.model;
|
||||
|
||||
/** A single state mutation or message. */
|
||||
public record Effect(Type type, String arg) {
|
||||
public enum Type { SET_FLAG, CLEAR_FLAG, GIVE_ITEM, REMOVE_ITEM, SAY }
|
||||
public enum Type { SET_FLAG, CLEAR_FLAG, GIVE_ITEM, REMOVE_ITEM, SAY, START_QUEST }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package thb.jeanluc.adventure.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** A multi-stage quest. Stage completion is condition-driven; rewards are effects. */
|
||||
public record Quest(String id, String title, boolean autoStart,
|
||||
List<QuestStage> stages, List<Effect> onComplete) {
|
||||
public Quest {
|
||||
stages = stages == null ? List.of() : List.copyOf(stages);
|
||||
onComplete = onComplete == null ? List.of() : List.copyOf(onComplete);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package thb.jeanluc.adventure.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** One stage of a {@link Quest}: an objective, a completion condition, optional rewards. */
|
||||
public record QuestStage(String objective, List<Condition> completion, List<Effect> onComplete) {
|
||||
public QuestStage {
|
||||
completion = completion == null ? List.of() : List.copyOf(completion);
|
||||
onComplete = onComplete == null ? List.of() : List.copyOf(onComplete);
|
||||
}
|
||||
}
|
||||
@@ -53,4 +53,11 @@ class EffectsTest {
|
||||
Effects.apply(new Effect(Effect.Type.SAY, "boo"), ctx);
|
||||
assertThat(((TestIO) ctx.getIo()).allOutput()).contains("boo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void startQuestStartsQuestInLog() {
|
||||
GameContext ctx = ctx(Map.of());
|
||||
Effects.apply(new Effect(Effect.Type.START_QUEST, "restore_power"), ctx);
|
||||
assertThat(ctx.getQuestLog().isActive("restore_power")).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package thb.jeanluc.adventure.game;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class QuestLogTest {
|
||||
@Test
|
||||
void startAdvanceComplete() {
|
||||
QuestLog log = new QuestLog();
|
||||
assertThat(log.isActive("q")).isFalse();
|
||||
log.start("q");
|
||||
assertThat(log.isActive("q")).isTrue();
|
||||
assertThat(log.stageIndex("q")).isEqualTo(0);
|
||||
log.advance("q");
|
||||
assertThat(log.stageIndex("q")).isEqualTo(1);
|
||||
log.complete("q");
|
||||
assertThat(log.isActive("q")).isFalse();
|
||||
assertThat(log.isCompleted("q")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startIgnoredOnceCompleted() {
|
||||
QuestLog log = new QuestLog();
|
||||
log.start("q");
|
||||
log.complete("q");
|
||||
log.start("q");
|
||||
assertThat(log.isActive("q")).isFalse();
|
||||
assertThat(log.isCompleted("q")).isTrue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user