diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/MenuCommand.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/MenuCommand.java index ee6145e..2eb4441 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/MenuCommand.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/MenuCommand.java @@ -16,10 +16,17 @@ import java.util.List; @RequiredArgsConstructor public class MenuCommand implements Command { + /** Persistence backend the active slot is written to before leaving. */ private final SaveService saves; + + /** Running game whose loop is stopped; null until {@link #bind(Game)} is called. */ private Game game; - /** Binds the running game (two-phase wiring, as the registry is built first). */ + /** + * Binds the running game (two-phase wiring, as the registry is built first). + * + * @param game the game loop to stop when this command runs + */ public void bind(Game game) { this.game = game; } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/SaveCommand.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/SaveCommand.java index 54ce7fd..5453711 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/SaveCommand.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/command/impl/SaveCommand.java @@ -12,6 +12,7 @@ import java.util.List; @RequiredArgsConstructor public class SaveCommand implements Command { + /** Persistence backend the active slot is written to. */ private final SaveService saves; @Override diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Conditions.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Conditions.java index 1e96113..adb64fe 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Conditions.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Conditions.java @@ -10,6 +10,13 @@ public final class Conditions { private Conditions() { } + /** + * Evaluates a single condition. + * + * @param c the condition to evaluate + * @param ctx the active game context + * @return true if the condition holds + */ public static boolean holds(Condition c, GameContext ctx) { return switch (c.type()) { case FLAG -> ctx.getState().isSet(c.arg()); @@ -18,7 +25,13 @@ public final class Conditions { }; } - /** True iff every condition holds. A null or empty list is vacuously true. */ + /** + * True iff every condition holds. A null or empty list is vacuously true. + * + * @param conditions the conditions to evaluate; may be null + * @param ctx the active game context + * @return true if all conditions hold + */ public static boolean all(List conditions, GameContext ctx) { if (conditions == null) { return true; diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Effects.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Effects.java index 4f2d738..b52dfbd 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Effects.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/Effects.java @@ -13,6 +13,13 @@ public final class Effects { private Effects() { } + /** + * Applies a single effect. An effect referencing an unknown item is logged + * and skipped rather than failing the turn. + * + * @param e the effect to apply + * @param ctx the active game context + */ public static void apply(Effect e, GameContext ctx) { switch (e.type()) { case SET_FLAG -> ctx.getState().set(e.arg()); @@ -31,7 +38,12 @@ public final class Effects { } } - /** Applies each effect in order. A null list is a no-op. */ + /** + * Applies each effect in order. A null list is a no-op. + * + * @param effects the effects to apply; may be null + * @param ctx the active game context + */ public static void applyAll(List effects, GameContext ctx) { if (effects == null) { return; diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/EndingEngine.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/EndingEngine.java index e70a46d..db369ff 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/EndingEngine.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/EndingEngine.java @@ -9,7 +9,12 @@ public final class EndingEngine { private EndingEngine() { } - /** First ending whose conditions hold, in list order; null if none. */ + /** + * First ending whose conditions hold, in list order; null if none. + * + * @param ctx the active game context + * @return the triggered ending, or null if no ending's conditions hold + */ public static Ending triggered(GameContext ctx) { for (Ending e : ctx.getWorld().getEndings()) { if (Conditions.all(e.when(), ctx)) { @@ -19,7 +24,14 @@ public final class EndingEngine { return null; } - /** Builds the ending screen: title banner, text, and a run summary. */ + /** + * Builds the ending screen: title banner, text, and a run summary. + * + * @param e the triggered ending + * @param ctx the active game context + * @param turns number of turns the run took + * @return the rendered end-of-game screen + */ public static StyledText render(Ending e, GameContext ctx, int turns) { int total = ctx.getWorld().getQuests().size(); int done = ctx.getQuestLog().completed().size(); @@ -34,6 +46,7 @@ public final class EndingEngine { return b.build(); } + /** Rank line for the summary: derived from the ending's outcome and quest completeness. */ private static String rank(Ending e, int total, int done) { if (e.victory() && total > 0 && done == total) { return "Master of the Manor"; diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/EscortEngine.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/EscortEngine.java index 4cd8848..c81f3c9 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/EscortEngine.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/EscortEngine.java @@ -39,7 +39,13 @@ public final class EscortEngine { private EscortEngine() { } - /** A recruitable twin and the state that tracks their escort. */ + /** + * A recruitable twin and the state that tracks their escort. + * + * @param npcId id of the twin's NPC in the world + * @param followingFlag flag that is set while this twin follows the player + * @param homeRoomId id of the room the twin waits in until recruited + */ private record Twin(String npcId, String followingFlag, String homeRoomId) { } @@ -87,6 +93,11 @@ public final class EscortEngine { return String.join(" and ", names) + " keep close at your shoulders."; } + /** + * Reunites the brothers once both follow the player into the reunion room: + * sets {@link #REUNITED_FLAG}, grants the reward, and prints the scene. Runs + * at most once per playthrough. + */ private static void maybeReunite(GameContext ctx) { if (ctx.getState().isSet(REUNITED_FLAG)) { return; @@ -107,6 +118,7 @@ public final class EscortEngine { .build()); } + /** Adds the reward item to the inventory unless the player already carries it. */ private static void grantReward(GameContext ctx) { Item reward = ctx.getWorld().getItems().get(REWARD_ITEM_ID); if (reward != null && !ctx.getPlayer().hasItem(REWARD_ITEM_ID)) { @@ -118,6 +130,7 @@ public final class EscortEngine { return ctx.getState().isSet(twin.followingFlag()); } + /** The twin's NPC name, falling back to their id if the NPC is missing from the world. */ private static String displayName(GameContext ctx, Twin twin) { Npc npc = ctx.getWorld().getNpcs().get(twin.npcId()); return npc != null ? npc.getName() : twin.npcId(); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameContext.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameContext.java index 9f41d13..61b7698 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameContext.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameContext.java @@ -11,47 +11,96 @@ import thb.jeanluc.adventure.model.World; */ public class GameContext { + /** The mutable state of the current playthrough. */ private final GameSession session; + + /** Channel commands read input from and write output to. */ private final GameIO io; /** Silent autosave hook, wired by the app; no-op by default (tests). */ private Runnable saveCallback = () -> { }; + /** + * Creates a context over an existing session. + * + * @param session the playthrough state + * @param io the IO channel + */ public GameContext(GameSession session, GameIO io) { this.session = session; this.io = io; } - /** Convenience for tests/legacy callers: wraps a fresh single-use session. */ + /** + * Convenience for tests/legacy callers: wraps a fresh single-use session. + * + * @param world the loaded world + * @param player the player character + * @param io the IO channel + */ public GameContext(World world, Player player, GameIO io) { this(new GameSession(world, player, "session"), io); } + /** + * Returns the session this context was built over. + * + * @return the playthrough state backing this context + */ public GameSession getSession() { return session; } + /** + * Delegates to the session. + * + * @return the loaded world + */ public World getWorld() { return session.getWorld(); } + /** + * Delegates to the session. + * + * @return the player character + */ public Player getPlayer() { return session.getPlayer(); } + /** + * Delegates to the session. + * + * @return the world flags + */ public GameState getState() { return session.getState(); } + /** + * Delegates to the session. + * + * @return the quest progress + */ public QuestLog getQuestLog() { return session.getQuestLog(); } + /** + * Returns the channel commands read from and write to. + * + * @return the IO channel + */ public GameIO getIo() { return io; } - /** Sets the silent autosave hook (called by interval/quest autosave). */ + /** + * Sets the silent autosave hook (called by interval/quest autosave). + * + * @param saveCallback the hook to run on {@link #save()} + */ public void setSaveCallback(Runnable saveCallback) { this.saveCallback = saveCallback; } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameSession.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameSession.java index fda1bfb..d0ead52 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameSession.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameSession.java @@ -13,23 +13,47 @@ import thb.jeanluc.adventure.model.World; @Getter public class GameSession { + /** The loaded world this playthrough runs on. */ private final World world; + + /** The player character, including inventory, position, and visited rooms. */ private final Player player; + + /** World flags set and cleared by effects and conditions. */ private final GameState state = new GameState(); + + /** Quest progress: active stages and completed quests. */ private final QuestLog questLog = new QuestLog(); + + /** Name of the save slot this session is bound to; save/load target. */ private final String slotName; + + /** Number of commands executed so far; shown in the HUD and the ending summary. */ private int turn; + /** + * Creates a session bound to a save slot. + * + * @param world the loaded world + * @param player the player character + * @param slotName the save slot this session reads from and writes to + */ public GameSession(World world, Player player, String slotName) { this.world = world; this.player = player; this.slotName = slotName; } + /** + * Overwrites the turn counter (used when restoring a save). + * + * @param turn the turn count to restore + */ public void setTurn(int turn) { this.turn = turn; } + /** Advances the turn counter by one. */ public void incrementTurn() { this.turn++; } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameState.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameState.java index b56d006..ff77ff8 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameState.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/GameState.java @@ -8,25 +8,51 @@ import java.util.Set; /** Named world flags. A flag is "set" (true) when present in the set. */ public class GameState { + /** Ids of the currently set flags. Insertion-ordered for stable saves. */ private final Set flags = new LinkedHashSet<>(); + /** + * Tests whether a flag is set. + * + * @param flag the flag id + * @return true if the flag is set + */ public boolean isSet(String flag) { return flags.contains(flag); } + /** + * Sets a flag. Setting an already-set flag has no effect. + * + * @param flag the flag id + */ public void set(String flag) { flags.add(flag); } + /** + * Clears a flag. Clearing an unset flag has no effect. + * + * @param flag the flag id + */ public void clear(String flag) { flags.remove(flag); } + /** + * All currently set flags. + * + * @return an unmodifiable view of the set flags + */ public Set all() { return Collections.unmodifiableSet(flags); } - /** Replaces all flags with the given collection (used by load). */ + /** + * Replaces all flags with the given collection (used by load). + * + * @param newFlags the flags to restore + */ public void restore(Collection newFlags) { flags.clear(); flags.addAll(newFlags); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/QuestEngine.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/QuestEngine.java index 46fc4cc..697e061 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/QuestEngine.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/QuestEngine.java @@ -16,6 +16,15 @@ public final class QuestEngine { private QuestEngine() { } + /** + * Per-turn quest progression: starts every auto-start quest that is neither + * active nor completed, then repeatedly advances active quests whose current + * stage's completion conditions hold, announcing each finished objective and + * quest. Repeats until nothing changes, so a stage whose conditions are met + * by another stage's effects still resolves in the same turn. + * + * @param ctx the active game context + */ public static void tick(GameContext ctx) { Map quests = ctx.getWorld().getQuests(); for (Quest q : quests.values()) { @@ -54,6 +63,7 @@ public final class QuestEngine { } } + /** Applies a quest's completion effects, marks it done, announces it, and autosaves. */ private static void finish(GameContext ctx, Quest q) { if (ctx.getQuestLog().isCompleted(q.id())) { return; @@ -65,6 +75,13 @@ public final class QuestEngine { ctx.save(); } + /** + * Builds the quest panel view: each active quest with its current objective, + * plus the titles of the completed quests. + * + * @param ctx the active game context + * @return the current quest view + */ public static QuestView viewOf(GameContext ctx) { Map quests = ctx.getWorld().getQuests(); List active = new ArrayList<>(); @@ -85,6 +102,7 @@ public final class QuestEngine { return new QuestView(active, completed); } + /** Stage count across all quests; bounds the iteration guard in {@link #tick}. */ private static int totalStages(Map quests) { int total = 0; for (Quest q : quests.values()) { diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/QuestLog.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/QuestLog.java index 8ba492d..a4c8bc5 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/QuestLog.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/QuestLog.java @@ -10,45 +10,97 @@ import java.util.Set; /** Runtime quest progress: current stage per active quest, plus completed ids. */ public class QuestLog { + /** Index of the current stage per active quest id. Insertion-ordered for stable saves. */ private final Map stageIndex = new LinkedHashMap<>(); + + /** Ids of quests that have been completed. Insertion-ordered for stable saves. */ private final Set completed = new LinkedHashSet<>(); + /** + * Activates a quest at its first stage. Already active or completed quests + * are left untouched. + * + * @param id the quest id + */ public void start(String id) { if (!completed.contains(id)) { stageIndex.putIfAbsent(id, 0); } } + /** + * Tests whether a quest is currently active. + * + * @param id the quest id + * @return true if the quest is active + */ public boolean isActive(String id) { return stageIndex.containsKey(id); } + /** + * Tests whether a quest has been completed. + * + * @param id the quest id + * @return true if the quest is completed + */ public boolean isCompleted(String id) { return completed.contains(id); } + /** + * Current stage index of a quest. + * + * @param id the quest id + * @return the stage index, or 0 if the quest is not active + */ public int stageIndex(String id) { return stageIndex.getOrDefault(id, 0); } + /** + * Moves a quest on to its next stage. + * + * @param id the quest id + */ public void advance(String id) { stageIndex.merge(id, 1, Integer::sum); } + /** + * Marks a quest completed and removes it from the active set. + * + * @param id the quest id + */ public void complete(String id) { stageIndex.remove(id); completed.add(id); } + /** + * Ids of all currently active quests. + * + * @return an unmodifiable view of the active quest ids + */ public Set active() { return Collections.unmodifiableSet(stageIndex.keySet()); } + /** + * Ids of all completed quests. + * + * @return an unmodifiable view of the completed quest ids + */ public Set completed() { return Collections.unmodifiableSet(completed); } - /** Replaces all progress with the given stage map and completed set (load). */ + /** + * Replaces all progress with the given stage map and completed set (load). + * + * @param stages stage index per active quest id + * @param completedIds ids of the completed quests + */ public void restore(Map stages, Collection completedIds) { stageIndex.clear(); stageIndex.putAll(stages); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/TutorialGuide.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/TutorialGuide.java index a96a2f7..bccd152 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/TutorialGuide.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/game/TutorialGuide.java @@ -18,25 +18,51 @@ import java.util.Optional; */ public final class TutorialGuide { + /** The walkthrough content: intro, steps, and closing tips. */ private final Tutorial tutorial; + + /** Command lookup, used to compare a typed verb against a step's expected command via aliases. */ private final CommandRegistry registry; + + /** Index of the step currently being taught. */ private int index; + + /** False once all steps are done or the walkthrough was skipped. */ private boolean active; + + /** True once {@link #begin(GameContext)} has printed the intro; guards against a second run. */ private boolean begun; + + /** Room the player stood in when the current step began; used to detect movement steps. */ private Room roomAtStepStart; + /** + * Creates a guide for the given walkthrough. The guide starts inactive if + * the tutorial has no steps. + * + * @param tutorial the walkthrough content + * @param registry command lookup for alias-aware verb comparison + */ public TutorialGuide(Tutorial tutorial, CommandRegistry registry) { this.tutorial = tutorial; this.registry = registry; this.active = !tutorial.isEmpty(); } - /** @return true while the walkthrough is still running. */ + /** + * Reports whether the guide still has steps left to hand out. + * + * @return true while the walkthrough is still running + */ public boolean isActive() { return active; } - /** Prints the intro and the first instruction (call once at loop start). */ + /** + * Prints the intro and the first instruction (call once at loop start). + * + * @param ctx the active game context + */ public void begin(GameContext ctx) { if (!active || begun) { return; @@ -48,7 +74,14 @@ public final class TutorialGuide { enterStep(ctx); } - /** Evaluates the current step against the command that was just executed. */ + /** + * Evaluates the current step against the command that was just executed: + * on success prints the confirmation and advances (ending the walkthrough + * after the last step), otherwise prints the step's hint. + * + * @param ctx the active game context + * @param parsed the command that was just executed + */ public void onCommand(GameContext ctx, ParsedCommand parsed) { if (!active) { return; @@ -70,7 +103,11 @@ public final class TutorialGuide { } } - /** Ends the tutorial early (the 'skip' command). */ + /** + * Ends the tutorial early (the 'skip' command). + * + * @param ctx the active game context + */ public void skip(GameContext ctx) { if (active) { ctx.getIo().write("Tutorial skipped."); @@ -78,11 +115,18 @@ public final class TutorialGuide { } } + /** Prints the current step's instruction and records the room it started in. */ private void enterStep(GameContext ctx) { roomAtStepStart = ctx.getPlayer().getCurrentRoom(); ctx.getIo().write(tutorial.steps().get(index).instruction()); } + /** + * Whether the executed command fulfils the step. The pseudo-expectations + * {@code go_direction} and {@code go_to_room} additionally require an actual + * room change; every other expectation matches on the command (aliases + * included) and the step's minimum argument count. + */ private boolean satisfies(TutorialStep step, GameContext ctx, ParsedCommand parsed) { String expect = step.expect(); if ("go_direction".equals(expect)) { @@ -98,6 +142,7 @@ public final class TutorialGuide { return sameCommand(p.verb(), "go") && !p.args().isEmpty(); } + /** Whether the first argument parses as a {@link Direction} (rather than a room name). */ private boolean firstIsDirection(ParsedCommand p) { if (p.args().isEmpty()) { return false; @@ -110,11 +155,13 @@ public final class TutorialGuide { } } + /** Whether the player left the room the current step started in. */ private boolean moved(GameContext ctx) { return roomAtStepStart != null && ctx.getPlayer().getCurrentRoom() != roomAtStepStart; } + /** Whether both verbs resolve to the same registered command, so aliases count as a match. */ private boolean sameCommand(String a, String b) { Optional ca = registry.find(a); Optional cb = registry.find(b); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/ConsoleIO.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/ConsoleIO.java index ec1afc6..e5c689a 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/ConsoleIO.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/ConsoleIO.java @@ -23,28 +23,70 @@ import java.util.List; public class ConsoleIO implements GameIO { /** Whether ANSI colour is emitted. */ - public enum ColorMode { ON, OFF, AUTO } + public enum ColorMode { + + /** Always emit ANSI escape codes. */ + ON, + + /** Never emit ANSI escape codes; plain text only. */ + OFF, + + /** Emit colour only when attached to a real console, not when piped. */ + AUTO + } /** Frame/glyph fidelity tier. */ - public enum GlyphMode { ASCII, UNICODE, GLYPH } + public enum GlyphMode { + /** Frames drawn with {@code +} and {@code -}, for terminals without Unicode. */ + ASCII, + + /** Frames drawn with Unicode box-drawing characters. */ + UNICODE, + + /** Highest tier: Unicode frames plus decorative glyphs. */ + GLYPH + } + + /** Total width of a room frame, in characters. */ private static final int FRAME_WIDTH = 44; + /** Source the player's commands are read from. */ private final BufferedReader in; + + /** Sink all output is written to. */ private final PrintStream out; + + /** Whether ANSI colour codes are currently emitted; resolved from a {@link ColorMode}. */ private boolean useColor; + + /** Frame/glyph fidelity tier currently in use. */ private GlyphMode glyphs; + /** Reads from {@code System.in} and writes to {@code System.out}, colour auto-detected. */ public ConsoleIO() { this(new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)), System.out, ColorMode.AUTO, GlyphMode.UNICODE); } - /** Backward-compatible stream constructor (colour off, unicode frames). */ + /** + * Backward-compatible stream constructor (colour off, unicode frames). + * + * @param in source of the player's commands + * @param out sink for all output + */ public ConsoleIO(BufferedReader in, PrintStream out) { this(in, out, ColorMode.OFF, GlyphMode.UNICODE); } + /** + * Creates a console IO bound to the given streams and fidelity settings. + * + * @param in source of the player's commands + * @param out sink for all output + * @param colorMode whether ANSI colour is emitted + * @param glyphs frame/glyph fidelity tier + */ public ConsoleIO(BufferedReader in, PrintStream out, ColorMode colorMode, GlyphMode glyphs) { this.in = in; this.out = out; @@ -52,7 +94,11 @@ public class ConsoleIO implements GameIO { setColorMode(colorMode); } - /** Re-resolves colour emission from the given mode (AUTO checks the console). */ + /** + * Re-resolves colour emission from the given mode (AUTO checks the console). + * + * @param mode the colour mode to apply + */ public void setColorMode(ColorMode mode) { this.useColor = switch (mode) { case ON -> true; @@ -61,7 +107,11 @@ public class ConsoleIO implements GameIO { }; } - /** Switches the frame/glyph fidelity tier live. */ + /** + * Switches the frame/glyph fidelity tier live. + * + * @param mode the tier to apply + */ public void setGlyphMode(GlyphMode mode) { this.glyphs = mode; } @@ -118,6 +168,14 @@ public class ConsoleIO implements GameIO { print(AsciiMap.render(view, glyphs != GlyphMode.ASCII)); } + /** + * Builds one indented, labelled line listing the given entries. + * + * @param label dim heading in front of the entries + * @param xs the entries + * @param st style applied to each entry + * @return the rendered line + */ private String section(String label, List xs, Style st) { StringBuilder sb = new StringBuilder(" ").append(paint(Style.DIM, label)).append(" "); for (int i = 0; i < xs.size(); i++) { @@ -129,6 +187,10 @@ public class ConsoleIO implements GameIO { return sb.toString(); } + /** + * @param title room name embedded in the top border + * @return the top border of a room frame, {@link #FRAME_WIDTH} characters wide + */ private String frameTop(String title) { String tl = glyphs == GlyphMode.ASCII ? "+" : "┌"; String tr = glyphs == GlyphMode.ASCII ? "+" : "┐"; @@ -141,6 +203,9 @@ public class ConsoleIO implements GameIO { return sb.append(tr).toString(); } + /** + * @return the bottom border of a room frame, {@link #FRAME_WIDTH} characters wide + */ private String frameBottom() { String bl = glyphs == GlyphMode.ASCII ? "+" : "└"; String br = glyphs == GlyphMode.ASCII ? "+" : "┘"; @@ -152,6 +217,13 @@ public class ConsoleIO implements GameIO { return sb.append(br).toString(); } + /** + * Wraps the text in the ANSI escape codes of the given role. + * + * @param st the semantic role + * @param s the text + * @return the text unchanged when colour is disabled, else the coloured text + */ private String paint(Style st, String s) { if (!useColor) { return s; diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/GameIO.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/GameIO.java index 33e1748..20d8668 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/GameIO.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/GameIO.java @@ -18,28 +18,52 @@ import java.util.List; */ public interface GameIO { - /** Blocks until a line of input is available; empty string at end of input. */ + /** + * Blocks until a line of input is available; empty string at end of input. + * + * @return the line the player entered + */ String readLine(); - /** Renders one styled output block. The only method implementors must provide. */ + /** + * Renders one styled output block. The only method implementors must provide. + * + * @param text the block to render + */ void print(StyledText text); - /** Convenience: a plain, unstyled line. */ + /** + * Convenience: a plain, unstyled line. + * + * @param s the text to write + */ default void write(String s) { print(StyledText.of(s)); } - /** Renders a room. Default flattens via {@link Renderings}; renderers may override. */ + /** + * Renders a room. Default flattens via {@link Renderings}; renderers may override. + * + * @param room the room snapshot to render + */ default void showRoom(RoomView room) { print(Renderings.roomToStyledText(room)); } - /** Updates the status/HUD region. No-op by default; renderers may override. */ + /** + * Updates the status/HUD region. No-op by default; renderers may override. + * + * @param hud the status snapshot + */ default void setHud(Hud hud) { // no-op } - /** Per-turn push of the current map. GUI repaints its panel; console ignores. */ + /** + * Per-turn push of the current map. GUI repaints its panel; console ignores. + * + * @param view the map snapshot + */ default void setMap(MapView view) { // no-op } @@ -55,27 +79,47 @@ public interface GameIO { // no animation in text mode } - /** On-demand map (the 'map' command). Default renders ASCII (Unicode frames). */ + /** + * On-demand map (the 'map' command). Default renders ASCII (Unicode frames). + * + * @param view the map snapshot + */ default void showMap(MapView view) { print(AsciiMap.render(view, true)); } - /** Per-turn push of the quest log. GUI repaints its box; console ignores. */ + /** + * Per-turn push of the quest log. GUI repaints its box; console ignores. + * + * @param view the quest snapshot + */ default void setQuests(QuestView view) { // no-op } - /** Per-turn current-room music track; null/blank = silence. Console no-op; GUI plays. */ + /** + * Per-turn current-room music track; null/blank = silence. Console no-op; GUI plays. + * + * @param track the current room's track name; null or blank means silence + */ default void setMusic(String track) { // no audio in text mode } - /** Applies the music volume/level. Console no-op; GUI sets it. */ + /** + * Applies the music volume/level. Console no-op; GUI sets it. + * + * @param level the level to apply + */ default void setMusicLevel(MusicLevel level) { // no audio in text mode } - /** On-demand quest log (the 'quests' command). Default renders styled text. */ + /** + * On-demand quest log (the 'quests' command). Default renders styled text. + * + * @param view the quest snapshot + */ default void showQuests(QuestView view) { print(QuestText.render(view)); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MapPanel.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MapPanel.java index 9171177..0b798ef 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MapPanel.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MapPanel.java @@ -31,30 +31,64 @@ import java.util.List; */ public class MapPanel extends JComponent { + /** Unscaled room width, in pixels. */ private static final int BASE_CELL_W = 96; + + /** Unscaled room height, in pixels. */ private static final int BASE_CELL_H = 46; + + /** Unscaled horizontal gap between two rooms, in pixels. */ private static final int BASE_GAP_X = 34; + + /** Unscaled vertical gap between two rooms, in pixels. */ private static final int BASE_GAP_Y = 30; + + /** Unscaled padding around the whole map, in pixels. */ private static final int BASE_PAD = 16; + + /** Unscaled size of the room-label font, in points. */ private static final float BASE_FONT = 11f; + /** Panel background. */ private static final Color BG = new Color(0x0e, 0x12, 0x18); + + /** Line colour of a corridor between two visited rooms. */ private static final Color CORRIDOR = new Color(0x46, 0x70, 0x8a); + + /** Outline colour of a {@link CellState#VISITED} room. */ private static final Color VISITED = new Color(0xcf, 0xd6, 0xe0); + + /** Outline colour of the {@link CellState#CURRENT} room. */ private static final Color CURRENT = new Color(0xe6, 0xc3, 0x4a); + + /** Outline colour of a {@link CellState#KNOWN} but unvisited room. */ private static final Color KNOWN = new Color(0x3a, 0x46, 0x55); + /** Font the room labels are derived from. */ private final Font baseFont; + + /** User zoom multiplier, applied on top of the fit-to-panel scale. */ private float zoom = 1f; + + /** The map currently drawn; empty until the first {@link #show(MapView)}. */ private MapView view = new MapView(List.of(), List.of()); + /** + * Creates an empty map panel; a map appears with the first {@link #show(MapView)}. + * + * @param font font the room labels are derived from + */ public MapPanel(Font font) { this.baseFont = font; setBackground(BG); setOpaque(true); } - /** Sets the user zoom multiplier (applied on top of the fit-to-panel scale). */ + /** + * Sets the user zoom multiplier (applied on top of the fit-to-panel scale). + * + * @param zoom the multiplier; clamped to a minimum of 0.4 + */ public void setZoom(float zoom) { SwingUtilities.invokeLater(() -> { this.zoom = Math.max(0.4f, zoom); @@ -63,7 +97,11 @@ public class MapPanel extends JComponent { }); } - /** Replaces the rendered map. */ + /** + * Replaces the rendered map. + * + * @param v the new map snapshot + */ public void show(MapView v) { SwingUtilities.invokeLater(() -> { this.view = v; @@ -72,7 +110,11 @@ public class MapPanel extends JComponent { }); } - /** The space the map should fit into: the enclosing scroll pane's viewport area. */ + /** + * The space the map should fit into: the enclosing scroll pane's viewport area. + * + * @return the viewport area, this panel's own size, or a fixed fallback + */ private Dimension available() { Container p = getParent(); if (p instanceof JViewport && p.getParent() instanceof JScrollPane sp) { @@ -87,6 +129,10 @@ public class MapPanel extends JComponent { return (s.width > 0 && s.height > 0) ? s : new Dimension(220, 320); } + /** + * @return the highest occupied column and row of the map grid, as + * {@code [maxX, maxY]} + */ private int[] gridBounds() { int maxX = 0; int maxY = 0; @@ -97,7 +143,13 @@ public class MapPanel extends JComponent { return new int[]{maxX, maxY}; } - /** Fit-to-panel scale times the user zoom. */ + /** + * Fit-to-panel scale times the user zoom. + * + * @param avail the space available to the map + * @param b grid bounds as returned by {@link #gridBounds()} + * @return the effective drawing scale + */ private float effScale(Dimension avail, int[] b) { double natW = 2.0 * BASE_PAD + (b[0] + 1) * BASE_CELL_W + b[0] * BASE_GAP_X; double natH = 2.0 * BASE_PAD + (b[1] + 1) * BASE_CELL_H + b[1] * BASE_GAP_Y; @@ -109,6 +161,11 @@ public class MapPanel extends JComponent { return (float) (fit * zoom); } + /** + * @param eff the effective drawing scale + * @param b grid bounds as returned by {@link #gridBounds()} + * @return the size of the drawn map at that scale, including padding + */ private Dimension content(float eff, int[] b) { int pad = Math.round(BASE_PAD * eff); int cw = Math.round(BASE_CELL_W * eff); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicBackend.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicBackend.java index 2958115..cdfa241 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicBackend.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicBackend.java @@ -3,13 +3,22 @@ package thb.jeanluc.adventure.io; /** Raw audio operations — the only part that touches sound hardware. */ public interface MusicBackend { - /** Fade the current track out, then start {@code track} (looping) and fade in to {@code gainDb}. */ + /** + * Fade the current track out, then start {@code track} (looping) and fade in to {@code gainDb}. + * + * @param track name of the track to play + * @param gainDb target volume in decibels + */ void fadeTo(String track, float gainDb); /** Fade the current track out and stop. */ void fadeOut(); - /** Set the playback volume immediately. */ + /** + * Set the playback volume immediately. + * + * @param gainDb target volume in decibels + */ void setGainDb(float gainDb); /** Stop playback and release resources. */ diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicController.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicController.java index 3a4b1f4..5eed852 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicController.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicController.java @@ -10,15 +10,31 @@ import java.util.Objects; */ public final class MusicController { + /** Backend the raw playback operations are delegated to. */ private final MusicBackend backend; + + /** Volume level currently selected in the Settings menu. */ private MusicLevel level = MusicLevel.MEDIUM; + + /** Track currently selected, or null when silent. */ private String current; // currently selected track, or null = silence + /** + * Creates a controller starting silent at {@link MusicLevel#MEDIUM}. + * + * @param backend backend that performs the actual playback + */ public MusicController(MusicBackend backend) { this.backend = backend; } - /** Called per turn with the current room's music field (may be null/blank). */ + /** + * Called per turn with the current room's music field (may be null/blank). + * Does nothing while the level is {@link MusicLevel#OFF} or the track is + * already playing. + * + * @param track the room's track name; null or blank means silence + */ public void room(String track) { if (!level.isOn()) { return; @@ -35,7 +51,13 @@ public final class MusicController { } } - /** Applies a new music level (volume / off). */ + /** + * Applies a new music level (volume / off). Switching to + * {@link MusicLevel#OFF} fades out; switching back on replays on the next + * {@link #room(String)} call. + * + * @param newLevel the level to apply + */ public void level(MusicLevel newLevel) { if (newLevel == this.level) { return; @@ -49,6 +71,7 @@ public final class MusicController { } } + /** Stops playback and releases the backend's resources. */ public void shutdown() { backend.shutdown(); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicLevel.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicLevel.java index 79d2091..8472630 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicLevel.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/MusicLevel.java @@ -2,9 +2,23 @@ package thb.jeanluc.adventure.io; /** Background-music volume level, cycled in the Settings menu. */ public enum MusicLevel { - OFF, LOW, MEDIUM, HIGH; + /** Music disabled; nothing is played. */ + OFF, - /** Target MASTER_GAIN in decibels. Irrelevant for {@link #OFF} (playback disabled). */ + /** Quiet background music. */ + LOW, + + /** Default volume. */ + MEDIUM, + + /** Full volume, without attenuation. */ + HIGH; + + /** + * Target MASTER_GAIN in decibels. Irrelevant for {@link #OFF} (playback disabled). + * + * @return the gain of this level + */ public float gainDb() { return switch (this) { case OFF -> Float.NEGATIVE_INFINITY; @@ -14,12 +28,20 @@ public enum MusicLevel { }; } - /** @return true when music should play. */ + /** + * Reports whether this level permits playback. + * + * @return true when music should play + */ public boolean isOn() { return this != OFF; } - /** Next level in the cycle (wraps HIGH → OFF). */ + /** + * Next level in the cycle (wraps HIGH → OFF). + * + * @return the following level + */ public MusicLevel next() { return values()[(ordinal() + 1) % values().length]; } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/QuestPanel.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/QuestPanel.java index e334dcd..9e33775 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/QuestPanel.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/QuestPanel.java @@ -18,6 +18,11 @@ import java.awt.Insets; /** Read-only styled view of the quest log, shown under the map. */ public class QuestPanel extends JTextPane { + /** + * Creates an empty, read-only panel; content arrives via {@link #show(QuestView)}. + * + * @param font font the panel text is derived from + */ public QuestPanel(Font font) { setEditable(false); setBackground(new Color(0x0b, 0x0e, 0x13)); @@ -26,6 +31,11 @@ public class QuestPanel extends JTextPane { setMargin(new Insets(8, 10, 8, 10)); } + /** + * Replaces the panel's content with the given quest log. + * + * @param view the quest snapshot to render + */ public void show(QuestView view) { SwingUtilities.invokeLater(() -> { StyledDocument doc = getStyledDocument(); @@ -46,6 +56,10 @@ public class QuestPanel extends JTextPane { }); } + /** + * @param s the semantic role + * @return the colour this panel paints that role in + */ private Color colorFor(Style s) { return switch (s) { case HEADING -> new Color(0xc9, 0x8a, 0xe0); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/AsciiMap.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/AsciiMap.java index a6bd119..4a077b6 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/AsciiMap.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/AsciiMap.java @@ -11,13 +11,26 @@ import java.util.Set; */ public final class AsciiMap { + /** Lower bound for the room-name field width, in characters. */ private static final int MIN_INNER = 7; // minimum room name field width + + /** Upper bound for the room-name field width, in characters. */ private static final int MAX_INNER = 14; // cap so the map stays compact + + /** Width of a horizontal corridor, in characters. */ private static final int GAP = 3; // horizontal corridor width + /** Utility class; not instantiable. */ private AsciiMap() { } + /** + * Renders the map as a styled grid of boxed rooms and corridor lines. + * + * @param view the map snapshot to draw + * @param unicode true for box-drawing characters, false for pure ASCII + * @return the rendered grid, or a placeholder when the map is empty + */ public static StyledText render(MapView view, boolean unicode) { if (view.cells().isEmpty()) { return StyledText.of("(no map yet)"); @@ -98,6 +111,13 @@ public final class AsciiMap { return b.build(); } + /** + * Appends the text in the style that matches the cell's fog-of-war state. + * + * @param b the builder to append to + * @param s the text + * @param state the cell's state + */ private static void styled(StyledText.Builder b, String s, CellState state) { switch (state) { case CURRENT -> b.heading(s); @@ -106,6 +126,11 @@ public final class AsciiMap { } } + /** + * @param c the cell + * @param inner width of the name field, in characters + * @return the room name, truncated to {@code inner} + */ private static String label(RoomCell c, int inner) { String n = c.name(); if (n.length() > inner) { @@ -114,6 +139,11 @@ public final class AsciiMap { return n; } + /** + * @param s the text + * @param width target width, at least the text's length + * @return the text padded with spaces so it sits centred in {@code width} + */ private static String center(String s, int width) { int total = width - s.length(); int left = total / 2; @@ -121,6 +151,11 @@ public final class AsciiMap { return " ".repeat(left) + s + " ".repeat(right); } + /** + * @param x column on the grid + * @param y row on the grid + * @return the two coordinates packed into one map/set key + */ private static long key(int x, int y) { return (((long) x) << 32) ^ (y & 0xffffffffL); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Banner.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Banner.java index f491446..b86223d 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Banner.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Banner.java @@ -3,10 +3,16 @@ package thb.jeanluc.adventure.io.text; /** Builds styled banners for big moments (game start, finale). */ public final class Banner { + /** Utility class; not instantiable. */ private Banner() { } - /** A framed, heading-styled welcome banner for the given title. */ + /** + * A framed, heading-styled welcome banner for the given title. + * + * @param title the game title to frame + * @return the banner + */ public static StyledText welcome(String title) { String bar = "=".repeat(Math.max(8, title.length() + 8)); return StyledText.builder() diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/CellState.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/CellState.java index b486940..985ac16 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/CellState.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/CellState.java @@ -1,4 +1,14 @@ package thb.jeanluc.adventure.io.text; /** Fog-of-war state of a room on the map. */ -public enum CellState { CURRENT, VISITED, KNOWN } +public enum CellState { + + /** The room the player is standing in. */ + CURRENT, + + /** A room the player has already entered. */ + VISITED, + + /** A room that is only known as the neighbour of a visited room, never entered. */ + KNOWN +} diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Connection.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Connection.java index decf156..e51b01a 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Connection.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Connection.java @@ -1,4 +1,9 @@ package thb.jeanluc.adventure.io.text; -/** A corridor between two placed rooms. */ +/** + * A corridor between two placed rooms. + * + * @param from cell the corridor starts at + * @param to cell the corridor leads to + */ public record Connection(RoomCell from, RoomCell to) {} diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Hud.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Hud.java index f4a1e7d..99bb975 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Hud.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Hud.java @@ -1,4 +1,11 @@ package thb.jeanluc.adventure.io.text; -/** Status snapshot for the HUD region. */ +/** + * Status snapshot for the HUD region. + * + * @param location display name of the room the player is in + * @param gold gold currently carried by the player + * @param turn number of turns played so far + * @param lightOn whether the player carries an active light source + */ public record Hud(String location, int gold, int turn, boolean lightOn) {} diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/MapView.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/MapView.java index 82c249e..ab84cf4 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/MapView.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/MapView.java @@ -2,8 +2,14 @@ package thb.jeanluc.adventure.io.text; import java.util.List; -/** Frontend-agnostic snapshot of the visible map: placed cells + connections. */ +/** + * Frontend-agnostic snapshot of the visible map: placed cells + connections. + * + * @param cells the rooms the player knows about, placed on the grid + * @param connections the corridors between those rooms + */ public record MapView(List cells, List connections) { + /** Defensively copies both lists, making the snapshot immutable. */ public MapView { cells = List.copyOf(cells); connections = List.copyOf(connections); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestEntry.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestEntry.java index 8b38613..b93250c 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestEntry.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestEntry.java @@ -1,4 +1,9 @@ package thb.jeanluc.adventure.io.text; -/** An active quest's title and its current objective. */ +/** + * An active quest's title and its current objective. + * + * @param title display name of the quest + * @param objective description of the step the player has to complete next + */ public record QuestEntry(String title, String objective) {} diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestText.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestText.java index da1814e..dc38a31 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestText.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestText.java @@ -3,9 +3,17 @@ package thb.jeanluc.adventure.io.text; /** Renders a {@link QuestView} as styled text for the console quest log. */ public final class QuestText { + /** Utility class; not instantiable. */ private QuestText() { } + /** + * Renders the quest log: active quests with their objective, then the + * completed titles. + * + * @param view the quest snapshot to render + * @return the rendered log, or a placeholder when there are no quests + */ public static StyledText render(QuestView view) { if (view.active().isEmpty() && view.completed().isEmpty()) { return StyledText.of("You have no quests yet."); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestView.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestView.java index 06cfd35..a8cca8a 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestView.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/QuestView.java @@ -2,8 +2,14 @@ package thb.jeanluc.adventure.io.text; import java.util.List; -/** Frontend-agnostic snapshot of the quest log. */ +/** + * Frontend-agnostic snapshot of the quest log. + * + * @param active quests in progress, with their current objective + * @param completed titles of the quests already finished + */ public record QuestView(List active, List completed) { + /** Defensively copies both lists, making the snapshot immutable. */ public QuestView { active = List.copyOf(active); completed = List.copyOf(completed); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Renderings.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Renderings.java index 9baf1aa..c67164e 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Renderings.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Renderings.java @@ -5,10 +5,16 @@ import java.util.List; /** Default, layout-free rendering of high-level views into {@link StyledText}. */ public final class Renderings { + /** Utility class; not instantiable. */ private Renderings() { } - /** Generic room rendering used by non-overriding {@code GameIO}s (e.g. tests). */ + /** + * Generic room rendering used by non-overriding {@code GameIO}s (e.g. tests). + * + * @param r the room snapshot to render + * @return name, description, items, NPCs and exits as styled text + */ public static StyledText roomToStyledText(RoomView r) { StyledText.Builder b = StyledText.builder(); b.heading(r.name()).plain("\n"); @@ -33,6 +39,13 @@ public final class Renderings { return b.build(); } + /** + * Appends the entries as a comma-separated list in the given style. + * + * @param b the builder to append to + * @param xs the entries + * @param st style applied to each entry + */ private static void appendList(StyledText.Builder b, List xs, Style st) { for (int i = 0; i < xs.size(); i++) { if (i > 0) { diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/RoomCell.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/RoomCell.java index 06ec898..615d7a8 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/RoomCell.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/RoomCell.java @@ -1,4 +1,12 @@ package thb.jeanluc.adventure.io.text; -/** A room placed on the map grid at integer coords, with its fog-of-war state. */ +/** + * A room placed on the map grid at integer coords, with its fog-of-war state. + * + * @param id unique slug of the room + * @param name display name shown inside the cell + * @param x column on the map grid, 0-based + * @param y row on the map grid, 0-based + * @param state how much the player knows about this room + */ public record RoomCell(String id, String name, int x, int y, CellState state) {} diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/RoomView.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/RoomView.java index 9b77854..3ebdd75 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/RoomView.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/RoomView.java @@ -2,9 +2,18 @@ package thb.jeanluc.adventure.io.text; import java.util.List; -/** Semantic snapshot of a room for rendering. Display-ready strings, no layout. */ +/** + * Semantic snapshot of a room for rendering. Display-ready strings, no layout. + * + * @param name display name of the room + * @param description room description text + * @param items display names of the items visible in the room + * @param npcs display names of the NPCs present in the room + * @param exits directions the player can leave by + */ public record RoomView(String name, String description, List items, List npcs, List exits) { + /** Defensively copies the lists, making the snapshot immutable. */ public RoomView { items = List.copyOf(items); npcs = List.copyOf(npcs); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Span.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Span.java index 9f01f06..177c3b6 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Span.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Span.java @@ -1,7 +1,17 @@ package thb.jeanluc.adventure.io.text; -/** A run of text carrying a single semantic {@link Style}. */ +/** + * A run of text carrying a single semantic {@link Style}. + * + * @param text the text of this run; never null + * @param style the semantic role applied to the whole run; never null + */ public record Span(String text, Style style) { + /** + * Rejects null components. + * + * @throws IllegalArgumentException if {@code text} or {@code style} is null + */ public Span { if (text == null) { throw new IllegalArgumentException("text must not be null"); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Style.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Style.java index 02d67f9..bbbefbf 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Style.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/Style.java @@ -1,4 +1,26 @@ package thb.jeanluc.adventure.io.text; /** Semantic output roles. Renderers map each role to concrete colours/attributes. */ -public enum Style { PLAIN, HEADING, ITEM, NPC, EXIT, DANGER, DIM } +public enum Style { + + /** Body text without emphasis. */ + PLAIN, + + /** Titles and section headers. */ + HEADING, + + /** Item names. */ + ITEM, + + /** NPC names. */ + NPC, + + /** Exit directions and room links. */ + EXIT, + + /** Warnings, failures and threats. */ + DANGER, + + /** Secondary text of lower importance, e.g. the HUD line or echoed input. */ + DIM +} diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/StyledText.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/StyledText.java index 0065c03..4a8652f 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/StyledText.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/io/text/StyledText.java @@ -6,18 +6,30 @@ import java.util.List; /** An ordered, immutable list of styled {@link Span}s. Built via {@link Builder}. */ public final class StyledText { + /** The spans of this text, in output order; unmodifiable. */ private final List spans; + /** + * @param spans the spans to copy into this text + */ private StyledText(List spans) { this.spans = List.copyOf(spans); } - /** @return the spans in order (unmodifiable). */ + /** + * Exposes the spans for rendering. + * + * @return the spans in order (unmodifiable) + */ public List spans() { return spans; } - /** @return all span text concatenated, without any styling. */ + /** + * Flattens this text for frontends that cannot render styles. + * + * @return all span text concatenated, without any styling + */ public String plainText() { StringBuilder sb = new StringBuilder(); for (Span s : spans) { @@ -26,32 +38,104 @@ public final class StyledText { return sb.toString(); } - /** @return a styled text of a single {@link Style#PLAIN} span. */ + /** + * Wraps an unstyled string. + * + * @param s the text + * @return a styled text of a single {@link Style#PLAIN} span + */ public static StyledText of(String s) { return new StyledText(List.of(new Span(s, Style.PLAIN))); } + /** + * Starts assembling a styled text span by span. + * + * @return a new, empty builder + */ public static Builder builder() { return new Builder(); } /** Fluent builder; one method per {@link Style} role. */ public static final class Builder { + + /** Spans collected so far, in the order the append methods were called. */ private final List spans = new ArrayList<>(); + /** + * Appends one span. + * + * @param text the text to append + * @param style the role to apply to it + * @return this builder + */ private Builder add(String text, Style style) { spans.add(new Span(text, style)); return this; } + /** + * Appends a span in the {@link Style#PLAIN} role. + * + * @param s text to append as {@link Style#PLAIN} + * @return this builder + */ public Builder plain(String s) { return add(s, Style.PLAIN); } + + /** + * Appends a span in the {@link Style#HEADING} role. + * + * @param s text to append as {@link Style#HEADING} + * @return this builder + */ public Builder heading(String s) { return add(s, Style.HEADING); } + + /** + * Appends a span in the {@link Style#ITEM} role. + * + * @param s text to append as {@link Style#ITEM} + * @return this builder + */ public Builder item(String s) { return add(s, Style.ITEM); } + + /** + * Appends a span in the {@link Style#NPC} role. + * + * @param s text to append as {@link Style#NPC} + * @return this builder + */ public Builder npc(String s) { return add(s, Style.NPC); } + + /** + * Appends a span in the {@link Style#EXIT} role. + * + * @param s text to append as {@link Style#EXIT} + * @return this builder + */ public Builder exit(String s) { return add(s, Style.EXIT); } + + /** + * Appends a span in the {@link Style#DANGER} role. + * + * @param s text to append as {@link Style#DANGER} + * @return this builder + */ public Builder danger(String s) { return add(s, Style.DANGER); } + + /** + * Appends a span in the {@link Style#DIM} role. + * + * @param s text to append as {@link Style#DIM} + * @return this builder + */ public Builder dim(String s) { return add(s, Style.DIM); } + /** + * Finishes the build; the builder may not be reused meaningfully afterwards. + * + * @return an immutable styled text of the collected spans + */ public StyledText build() { return new StyledText(spans); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/CombinationFactory.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/CombinationFactory.java index 296b4bd..4ff5d9b 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/CombinationFactory.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/CombinationFactory.java @@ -12,9 +12,19 @@ import java.util.Map; /** Builds {@link Combination}s from {@link CombinationDto}s, validating item ids. */ public final class CombinationFactory { + /** Utility class; not instantiable. */ private CombinationFactory() { } + /** + * Builds a combination and checks every referenced item id against the registry. + * + * @param dto data read from {@code combinations.yaml}; must not be null + * @param items the global item registry + * @return the freshly built combination + * @throws WorldLoadException if an item id is missing, unknown, or both + * ingredients are the same item + */ public static Combination fromDto(CombinationDto dto, Map items) { requireItem(items, dto.a(), "a"); requireItem(items, dto.b(), "b"); @@ -41,6 +51,14 @@ public final class CombinationFactory { dto.failText()); } + /** + * Asserts that {@code id} is present and known to the item registry. + * + * @param items the global item registry + * @param id the item id to check + * @param field name of the DTO field, used in the error message + * @throws WorldLoadException if the id is null or unknown + */ private static void requireItem(Map items, String id, String field) { if (id == null) { throw new WorldLoadException("Combination '" + field + "' is missing an item id"); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/EndingFactory.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/EndingFactory.java index 9ac368d..bb8f211 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/EndingFactory.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/EndingFactory.java @@ -7,9 +7,17 @@ import thb.jeanluc.adventure.model.Ending; /** Builds {@link Ending} objects from {@link EndingDto}s. */ public final class EndingFactory { + /** Utility class; not instantiable. */ private EndingFactory() { } + /** + * Builds an ending, defaulting a null {@code victory} flag to false. + * + * @param dto data read from {@code endings.yaml}; must not be null + * @return the freshly built ending + * @throws WorldLoadException if a condition sets none of its fields + */ public static Ending fromDto(EndingDto dto) { return new Ending( dto.id(), diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/NpcFactory.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/NpcFactory.java index 663267e..94466b8 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/NpcFactory.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/NpcFactory.java @@ -9,6 +9,7 @@ import thb.jeanluc.adventure.model.Npc; */ public final class NpcFactory { + /** Utility class; not instantiable. */ private NpcFactory() { } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/QuestFactory.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/QuestFactory.java index 9f3e84f..0d7b7d7 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/QuestFactory.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/QuestFactory.java @@ -13,9 +13,17 @@ import java.util.List; /** Builds {@link Quest} objects from {@link QuestDto}s (no reference resolution needed). */ public final class QuestFactory { + /** Utility class; not instantiable. */ private QuestFactory() { } + /** + * Builds a quest including all of its stages. + * + * @param dto data read from {@code quests.yaml}; must not be null + * @return the freshly built quest + * @throws WorldLoadException if a condition or effect sets none of its fields + */ public static Quest fromDto(QuestDto dto) { List stages = new ArrayList<>(); if (dto.stages() != null) { diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/RoomFactory.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/RoomFactory.java index 38d3d24..84ea7a6 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/RoomFactory.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/RoomFactory.java @@ -9,6 +9,7 @@ import thb.jeanluc.adventure.model.Room; */ public final class RoomFactory { + /** Utility class; not instantiable. */ private RoomFactory() { } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/TutorialLoader.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/TutorialLoader.java index 7ebabcb..ee89210 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/TutorialLoader.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/TutorialLoader.java @@ -22,18 +22,33 @@ public class TutorialLoader { /** Default classpath base directory. */ public static final String DEFAULT_BASE = "/world"; + /** Single Jackson mapper, configured once for YAML input. */ private final ObjectMapper yaml = new ObjectMapper(new YAMLFactory()); + + /** Classpath base directory (typically {@link #DEFAULT_BASE} or a test override). */ private final String basePath; + /** Convenience constructor using {@link #DEFAULT_BASE}. */ public TutorialLoader() { this(DEFAULT_BASE); } + /** + * Creates a loader reading from the given classpath base directory. + * + * @param basePath classpath base directory holding {@code tutorial.yaml} + */ public TutorialLoader(String basePath) { this.basePath = basePath; } - /** Reads the tutorial; absent file → {@link Tutorial#none()}. */ + /** + * Reads the tutorial; absent file → {@link Tutorial#none()}. + * + * @return the parsed tutorial, or {@link Tutorial#none()} if the file is + * missing or empty + * @throws WorldLoadException if the file exists but cannot be parsed + */ public Tutorial load() { String resource = basePath + "/tutorial.yaml"; try (InputStream in = getClass().getResourceAsStream(resource)) { diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/CombinationDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/CombinationDto.java index 8541dab..3c68358 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/CombinationDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/CombinationDto.java @@ -2,7 +2,18 @@ package thb.jeanluc.adventure.loader.dto; import java.util.List; -/** YAML representation of an item-combination recipe. */ +/** + * YAML representation of an item-combination recipe. + * + * @param a id of the first ingredient item + * @param b id of the second ingredient item + * @param requires conditions that must hold for the recipe to apply; nullable + * @param consume ids of the items removed from the inventory on success; nullable + * @param produce id of the item handed to the player on success; nullable + * @param effects effects applied on success; nullable + * @param response text printed on success + * @param failText text printed when the required conditions are not met; nullable + */ public record CombinationDto( String a, String b, diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ConditionDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ConditionDto.java index b68550f..4521377 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ConditionDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ConditionDto.java @@ -6,9 +6,21 @@ import thb.jeanluc.adventure.model.Condition; import java.util.ArrayList; import java.util.List; -/** YAML condition: exactly one of the fields is set. */ +/** + * YAML condition: exactly one of the fields is set. + * + * @param flag name of a flag that must be set; nullable + * @param notFlag name of a flag that must not be set; nullable + * @param hasItem id of an item the player must carry; nullable + */ public record ConditionDto(String flag, String notFlag, String hasItem) { + /** + * Converts this DTO into its domain condition. + * + * @return the matching {@link Condition} + * @throws WorldLoadException if none of the fields is set + */ public Condition toModel() { if (flag != null) { return new Condition(Condition.Type.FLAG, flag); @@ -22,6 +34,13 @@ public record ConditionDto(String flag, String notFlag, String hasItem) { throw new WorldLoadException("Condition must set one of flag/notFlag/hasItem"); } + /** + * Converts a list of DTOs into domain conditions. + * + * @param dtos the DTOs to convert; null is treated as empty + * @return the converted conditions, never null + * @throws WorldLoadException if any DTO sets none of its fields + */ public static List toModelList(List dtos) { List out = new ArrayList<>(); if (dtos != null) { diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/DescriptionStateDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/DescriptionStateDto.java index e08d9cb..9e80772 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/DescriptionStateDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/DescriptionStateDto.java @@ -2,6 +2,11 @@ package thb.jeanluc.adventure.loader.dto; import java.util.List; -/** YAML representation of a conditional room description. */ +/** + * YAML representation of a conditional room description. + * + * @param when conditions that must all hold for this variant to be used + * @param text description text used instead of the room's default + */ public record DescriptionStateDto(List when, String text) { } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/DialogueLineDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/DialogueLineDto.java index 2fe3464..c21fc07 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/DialogueLineDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/DialogueLineDto.java @@ -2,6 +2,11 @@ package thb.jeanluc.adventure.loader.dto; import java.util.List; -/** YAML representation of a conditional NPC line. */ +/** + * YAML representation of a conditional NPC line. + * + * @param when conditions that must all hold for this line to be spoken + * @param text text the NPC says + */ public record DialogueLineDto(List when, String text) { } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/EffectDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/EffectDto.java index 25ae0aa..a05d865 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/EffectDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/EffectDto.java @@ -6,15 +6,38 @@ import thb.jeanluc.adventure.model.Effect; import java.util.ArrayList; import java.util.List; -/** YAML effect: exactly one of the fields is set. */ +/** + * YAML effect: exactly one of the fields is set. + * + * @param setFlag name of a flag to set; nullable + * @param clearFlag name of a flag to clear; nullable + * @param giveItem id of an item to add to the inventory; nullable + * @param removeItem id of an item to take from the inventory; nullable + * @param say text to print; nullable + * @param startQuest id of a quest to start; nullable + */ public record EffectDto(String setFlag, String clearFlag, String giveItem, String removeItem, String say, String startQuest) { - /** Backward-compatible constructor without startQuest. */ + /** + * Backward-compatible constructor without startQuest. + * + * @param setFlag name of a flag to set; nullable + * @param clearFlag name of a flag to clear; nullable + * @param giveItem id of an item to add to the inventory; nullable + * @param removeItem id of an item to take from the inventory; nullable + * @param say text to print; nullable + */ public EffectDto(String setFlag, String clearFlag, String giveItem, String removeItem, String say) { this(setFlag, clearFlag, giveItem, removeItem, say, null); } + /** + * Converts this DTO into its domain effect. + * + * @return the matching {@link Effect} + * @throws WorldLoadException if none of the fields is set + */ public Effect toModel() { if (setFlag != null) { return new Effect(Effect.Type.SET_FLAG, setFlag); @@ -38,6 +61,13 @@ public record EffectDto(String setFlag, String clearFlag, String giveItem, Strin "Effect must set one of setFlag/clearFlag/giveItem/removeItem/say/startQuest"); } + /** + * Converts a list of DTOs into domain effects. + * + * @param dtos the DTOs to convert; null is treated as empty + * @return the converted effects, never null + * @throws WorldLoadException if any DTO sets none of its fields + */ public static List toModelList(List dtos) { List out = new ArrayList<>(); if (dtos != null) { diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/EndingDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/EndingDto.java index 0434956..7ee10f0 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/EndingDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/EndingDto.java @@ -2,6 +2,14 @@ package thb.jeanluc.adventure.loader.dto; import java.util.List; -/** YAML representation of a game ending. */ +/** + * YAML representation of a game ending. + * + * @param id unique slug + * @param title display title of the ending + * @param victory whether this ending counts as a win; defaults to false if null + * @param when conditions that must all hold for this ending to trigger + * @param text text printed when the ending triggers + */ public record EndingDto(String id, String title, Boolean victory, List when, String text) { } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ExitLockDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ExitLockDto.java index 9bce7fa..d5d9c6c 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ExitLockDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ExitLockDto.java @@ -2,6 +2,12 @@ package thb.jeanluc.adventure.loader.dto; import java.util.List; -/** YAML representation of a single condition-gated exit. */ +/** + * YAML representation of a single condition-gated exit. + * + * @param direction name of the gated exit direction + * @param requires conditions that must all hold for the exit to be passable + * @param blocked text printed when the exit is used while still locked + */ public record ExitLockDto(String direction, List requires, String blocked) { } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/NpcDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/NpcDto.java index 2b37c2c..e51a013 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/NpcDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/NpcDto.java @@ -20,7 +20,15 @@ public record NpcDto( List reactions, List dialogue ) { - /** Backward-compatible constructor without dialogue. */ + /** + * Backward-compatible constructor without dialogue, which defaults to null. + * + * @param id unique slug + * @param name display name + * @param description examine description + * @param greeting text the NPC says on {@code talk} + * @param reactions list of trigger/response definitions; may be null + */ public NpcDto(String id, String name, String description, String greeting, List reactions) { this(id, name, description, greeting, reactions, null); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/QuestDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/QuestDto.java index 0c16d15..c94c2af 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/QuestDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/QuestDto.java @@ -2,7 +2,15 @@ package thb.jeanluc.adventure.loader.dto; import java.util.List; -/** YAML representation of a quest. */ +/** + * YAML representation of a quest. + * + * @param id unique slug + * @param title display title + * @param autoStart whether the quest starts with the game; defaults to false if null + * @param stages ordered stages the player works through + * @param onComplete effects applied once the last stage is done; nullable + */ public record QuestDto(String id, String title, Boolean autoStart, List stages, List onComplete) { } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/QuestStageDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/QuestStageDto.java index af8e698..e7f8df4 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/QuestStageDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/QuestStageDto.java @@ -2,6 +2,12 @@ package thb.jeanluc.adventure.loader.dto; import java.util.List; -/** YAML representation of a quest stage. */ +/** + * YAML representation of a quest stage. + * + * @param objective objective text shown in the quest log + * @param completion conditions that must all hold for the stage to be done + * @param onComplete effects applied when the stage completes; nullable + */ public record QuestStageDto(String objective, List completion, List onComplete) { } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ReactionDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ReactionDto.java index 2fb817f..df70b2c 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ReactionDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/ReactionDto.java @@ -20,7 +20,14 @@ public record ReactionDto( List requires, List effects ) { - /** Backward-compatible constructor without requires/effects. */ + /** + * Backward-compatible constructor without requires/effects, which default to null. + * + * @param onReceive id of the item that triggers this reaction + * @param response text the NPC says after the exchange + * @param gives id of the item handed back; nullable + * @param consumes id of the item taken from the player; nullable + */ public ReactionDto(String onReceive, String response, String gives, String consumes) { this(onReceive, response, gives, consumes, null, null); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/RoomDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/RoomDto.java index 53a351c..19e87f2 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/RoomDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/RoomDto.java @@ -29,7 +29,16 @@ public record RoomDto( Boolean dark, String music ) { - /** Backward-compatible constructor without the optional state fields. */ + /** + * Backward-compatible constructor without the optional state fields, which default to null. + * + * @param id unique slug + * @param name display name + * @param description description shown by {@code look} + * @param exits direction-name to target-room-id map + * @param items ids of items initially in this room + * @param npcs ids of NPCs initially in this room + */ public RoomDto(String id, String name, String description, Map exits, List items, List npcs) { this(id, name, description, exits, items, npcs, null, null, null, null); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialDto.java index ab113eb..dfc4d54 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialDto.java @@ -2,6 +2,12 @@ package thb.jeanluc.adventure.loader.dto; import java.util.List; -/** YAML representation of the tutorial document. */ +/** + * YAML representation of the tutorial document. + * + * @param intro text shown before the first step + * @param steps ordered tutorial steps + * @param closingTips text shown after the last step + */ public record TutorialDto(String intro, List steps, String closingTips) { } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialStepDto.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialStepDto.java index e3f622b..9ef8110 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialStepDto.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/loader/dto/TutorialStepDto.java @@ -1,6 +1,14 @@ package thb.jeanluc.adventure.loader.dto; -/** YAML representation of a tutorial step. */ +/** + * YAML representation of a tutorial step. + * + * @param instruction text telling the player what to type + * @param expect command verb the step accepts + * @param minArgs minimum number of arguments the command must carry; 0 if null + * @param confirm text printed once the step is passed + * @param hint text printed when the player types something else + */ public record TutorialStepDto( String instruction, String expect, diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/MainMenu.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/MainMenu.java index 70f9821..024d2d7 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/MainMenu.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/MainMenu.java @@ -10,17 +10,29 @@ import java.util.List; /** Frontend-agnostic main menu, slot picker, and new-game name prompt. */ public final class MainMenu { + /** Utility class; not instantiable. */ private MainMenu() { } - /** Shows the main menu and returns the chosen action. */ + /** + * Shows the main menu and returns the chosen action. + * + * @param io frontend to render the menu on + * @return the action the player picked + */ public static MenuAction show(GameIO io) { int i = io.choose("HAUNTED MANOR", List.of("New Game", "Load Game", "Settings", "Quit")); return MenuAction.values()[i]; } - /** Prompts for a new save name; blank input yields {@code defaultName}. */ + /** + * Prompts for a new save name; blank input yields {@code defaultName}. + * + * @param io frontend to prompt on + * @param defaultName name used when the player enters nothing + * @return the trimmed save name + */ public static String promptName(GameIO io, String defaultName) { io.write("Name your save (Enter for \"" + defaultName + "\"):"); String line = io.readLine(); @@ -31,6 +43,10 @@ public final class MainMenu { * Lets the player pick a save slot to load. Returns the chosen * {@link SaveSlotInfo}, or {@code null} if there are none or the player * picks "Back". + * + * @param io frontend to render the slot list on + * @param saves source of the available slots + * @return the selected slot, or {@code null} if nothing was selected */ public static SaveSlotInfo pickSlot(GameIO io, SaveService saves) { List slots = saves.list(); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/MenuAction.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/MenuAction.java index 9af99cf..c3b771b 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/MenuAction.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/MenuAction.java @@ -2,5 +2,16 @@ package thb.jeanluc.adventure.menu; /** Top-level main-menu choices, in display order. */ public enum MenuAction { - NEW_GAME, LOAD, SETTINGS, QUIT + + /** Start a new game in a freshly named save slot. */ + NEW_GAME, + + /** Continue from an existing save slot. */ + LOAD, + + /** Open the settings screen. */ + SETTINGS, + + /** Leave the application. */ + QUIT } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/SettingsMenu.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/SettingsMenu.java index 7acf8fc..1b2a7b0 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/SettingsMenu.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/menu/SettingsMenu.java @@ -10,10 +10,18 @@ import java.util.List; /** Settings screen: cycle colour, glyph, and music level; persists + applies. */ public final class SettingsMenu { + /** Utility class; not instantiable. */ private SettingsMenu() { } - /** Loops the settings screen until "Back", persisting + applying each change. */ + /** + * Loops the settings screen until "Back", persisting + applying each change. + * + * @param io frontend to render the screen on + * @param current settings the screen starts from + * @param store destination the changed settings are written to + * @return the settings in effect when the player left the screen + */ public static Settings show(GameIO io, Settings current, SettingsStore store) { Settings s = current; while (true) { @@ -36,7 +44,12 @@ public final class SettingsMenu { } } - /** Applies settings to the IO: music level always; colour/glyph only on the console. */ + /** + * Applies settings to the IO: music level always; colour/glyph only on the console. + * + * @param s settings to apply + * @param io frontend the settings are applied to + */ public static void apply(Settings s, GameIO io) { io.setMusicLevel(s.musicLevel()); if (io instanceof ConsoleIO c) { @@ -45,6 +58,7 @@ public final class SettingsMenu { } } + /** Next colour mode in the cycle AUTO {@literal ->} ON {@literal ->} OFF. */ private static ConsoleIO.ColorMode nextColor(ConsoleIO.ColorMode m) { return switch (m) { case AUTO -> ConsoleIO.ColorMode.ON; @@ -53,6 +67,7 @@ public final class SettingsMenu { }; } + /** Next glyph mode in the cycle UNICODE {@literal ->} ASCII {@literal ->} GLYPH. */ private static ConsoleIO.GlyphMode nextGlyph(ConsoleIO.GlyphMode m) { return switch (m) { case UNICODE -> ConsoleIO.GlyphMode.ASCII; diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Combination.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Combination.java index c9c0b1c..f7bbe6b 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Combination.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Combination.java @@ -6,6 +6,15 @@ import java.util.List; * A data-driven item-combination recipe. Identified by an unordered pair of * item ids ({@code a}, {@code b}). When applied it may require conditions, * consume items, produce an item, apply effects, and print a response. + * + * @param a id of the first item of the pair + * @param b id of the second item of the pair + * @param requires conditions that must hold for the recipe to apply; empty = always + * @param consume ids of the items removed from the player's inventory on success + * @param produce id of the item handed to the player on success; nullable + * @param effects effects applied on success + * @param response text written on success + * @param failText text written when {@code requires} does not hold; nullable */ public record Combination( String a, @@ -17,18 +26,29 @@ public record Combination( String response, String failText ) { + /** Normalises the list components to immutable, non-null lists. */ public Combination { requires = requires == null ? List.of() : List.copyOf(requires); consume = consume == null ? List.of() : List.copyOf(consume); effects = effects == null ? List.of() : List.copyOf(effects); } - /** Order-independent key for an item pair (sorted, '|'-joined). */ + /** + * Order-independent key for an item pair (sorted, '|'-joined). + * + * @param x id of one item + * @param y id of the other item + * @return the canonical key identifying the pair regardless of argument order + */ public static String key(String x, String y) { return x.compareTo(y) <= 0 ? x + "|" + y : y + "|" + x; } - /** This recipe's canonical pair key. */ + /** + * This recipe's canonical pair key. + * + * @return the key under which the recipe is registered in the world + */ public String key() { return key(a, b); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Condition.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Condition.java index 8381f34..f3e8c3f 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Condition.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Condition.java @@ -1,6 +1,21 @@ package thb.jeanluc.adventure.model; -/** A single boolean test evaluated against world state and the player. */ +/** + * A single boolean test evaluated against world state and the player. + * + * @param type the kind of test to perform + * @param arg the operand the test applies to: a flag name for {@link Type#FLAG} + * and {@link Type#NOT_FLAG}, an item id for {@link Type#HAS_ITEM} + */ public record Condition(Type type, String arg) { - public enum Type { FLAG, NOT_FLAG, HAS_ITEM } + + /** The kinds of test a {@link Condition} can perform. */ + public enum Type { + /** Holds while the named flag is set in the world state. */ + FLAG, + /** Holds while the named flag is not set in the world state. */ + NOT_FLAG, + /** Holds while the player carries the named item. */ + HAS_ITEM + } } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/DescriptionState.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/DescriptionState.java index 4b2362e..d598255 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/DescriptionState.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/DescriptionState.java @@ -2,8 +2,14 @@ package thb.jeanluc.adventure.model; import java.util.List; -/** An alternate room description shown while its conditions hold. */ +/** + * An alternate room description shown while its conditions hold. + * + * @param when conditions that must all hold for this variant to be selected; empty = always + * @param text description text shown instead of the room's default description + */ public record DescriptionState(List when, String text) { + /** Normalises {@code when} to an immutable, non-null list. */ public DescriptionState { when = when == null ? List.of() : List.copyOf(when); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/DialogueLine.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/DialogueLine.java index 4b02a11..1b8b121 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/DialogueLine.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/DialogueLine.java @@ -2,8 +2,14 @@ package thb.jeanluc.adventure.model; import java.util.List; -/** A conditional line an NPC says on {@code talk}, first match wins. */ +/** + * A conditional line an NPC says on {@code talk}, first match wins. + * + * @param when conditions that must all hold for this line to be selected; empty = always + * @param text text the NPC says when this line is selected + */ public record DialogueLine(List when, String text) { + /** Normalises {@code when} to an immutable, non-null list. */ public DialogueLine { when = when == null ? List.of() : List.copyOf(when); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Effect.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Effect.java index ae36755..9ba23fd 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Effect.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Effect.java @@ -1,6 +1,29 @@ package thb.jeanluc.adventure.model; -/** A single state mutation or message. */ +/** + * A single state mutation or message. + * + * @param type the kind of mutation to apply + * @param arg the operand the mutation applies to: a flag name for + * {@link Type#SET_FLAG} and {@link Type#CLEAR_FLAG}, an item id for + * {@link Type#GIVE_ITEM} and {@link Type#REMOVE_ITEM}, the literal text + * for {@link Type#SAY}, a quest id for {@link Type#START_QUEST} + */ public record Effect(Type type, String arg) { - public enum Type { SET_FLAG, CLEAR_FLAG, GIVE_ITEM, REMOVE_ITEM, SAY, START_QUEST } + + /** The kinds of mutation an {@link Effect} can apply. */ + public enum Type { + /** Sets the named flag in the world state. */ + SET_FLAG, + /** Clears the named flag from the world state. */ + CLEAR_FLAG, + /** Adds the named item to the player's inventory. */ + GIVE_ITEM, + /** Takes the named item out of the player's inventory. */ + REMOVE_ITEM, + /** Writes the argument to the player's output. */ + SAY, + /** Activates the named quest. */ + START_QUEST + } } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Ending.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Ending.java index 83458d9..53a3f2f 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Ending.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Ending.java @@ -2,8 +2,17 @@ package thb.jeanluc.adventure.model; import java.util.List; -/** A game ending: shown (and ends the game) when its conditions hold. */ +/** + * A game ending: shown (and ends the game) when its conditions hold. + * + * @param id unique identifier of this ending + * @param title headline shown above the ending text + * @param victory whether reaching this ending counts as winning + * @param when conditions that must all hold for this ending to trigger + * @param text closing text shown to the player + */ public record Ending(String id, String title, boolean victory, List when, String text) { + /** Normalises {@code when} to an immutable, non-null list. */ public Ending { when = when == null ? List.of() : List.copyOf(when); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/ExitLock.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/ExitLock.java index 9a28d7d..522fed0 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/ExitLock.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/ExitLock.java @@ -2,8 +2,14 @@ package thb.jeanluc.adventure.model; import java.util.List; -/** A condition-gated exit with a message shown when it is blocked. */ +/** + * A condition-gated exit with a message shown when it is blocked. + * + * @param requires conditions that must all hold for the exit to be passable + * @param blocked text written when the player tries the exit while it is blocked + */ public record ExitLock(List requires, String blocked) { + /** Normalises {@code requires} to an immutable, non-null list. */ public ExitLock { requires = requires == null ? List.of() : List.copyOf(requires); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Quest.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Quest.java index ff0d58d..029814c 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Quest.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Quest.java @@ -2,9 +2,19 @@ package thb.jeanluc.adventure.model; import java.util.List; -/** A multi-stage quest. Stage completion is condition-driven; rewards are effects. */ +/** + * A multi-stage quest. Stage completion is condition-driven; rewards are effects. + * + * @param id unique identifier of this quest, referenced by + * {@link Effect.Type#START_QUEST} + * @param title display name shown in the quest log + * @param autoStart whether the quest is active from the start of the game + * @param stages the stages in the order they must be completed + * @param onComplete effects applied once the final stage is completed + */ public record Quest(String id, String title, boolean autoStart, List stages, List onComplete) { + /** Normalises the list components to immutable, non-null lists. */ public Quest { stages = stages == null ? List.of() : List.copyOf(stages); onComplete = onComplete == null ? List.of() : List.copyOf(onComplete); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/QuestStage.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/QuestStage.java index 87ed93a..85bc2c4 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/QuestStage.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/QuestStage.java @@ -2,8 +2,15 @@ package thb.jeanluc.adventure.model; import java.util.List; -/** One stage of a {@link Quest}: an objective, a completion condition, optional rewards. */ +/** + * One stage of a {@link Quest}: an objective, a completion condition, optional rewards. + * + * @param objective task description shown in the quest log while this stage is active + * @param completion conditions that must all hold for this stage to count as done + * @param onComplete effects applied when this stage is completed + */ public record QuestStage(String objective, List completion, List onComplete) { + /** Normalises the list components to immutable, non-null lists. */ public QuestStage { completion = completion == null ? List.of() : List.copyOf(completion); onComplete = onComplete == null ? List.of() : List.copyOf(onComplete); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Tutorial.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Tutorial.java index 338b2e2..aa4aec3 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Tutorial.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/Tutorial.java @@ -2,18 +2,34 @@ package thb.jeanluc.adventure.model; import java.util.List; -/** Onboarding walkthrough: an intro, ordered steps, and closing tips. */ +/** + * Onboarding walkthrough: an intro, ordered steps, and closing tips. + * + * @param intro text shown before the first step; nullable + * @param steps the steps in the order the player must complete them + * @param closingTips text shown after the last step; nullable + */ public record Tutorial(String intro, List steps, String closingTips) { + /** Normalises {@code steps} to an immutable, non-null list. */ public Tutorial { steps = steps == null ? List.of() : List.copyOf(steps); } + /** + * Whether this tutorial has anything to walk the player through. + * + * @return {@code true} if there are no steps + */ public boolean isEmpty() { return steps.isEmpty(); } - /** An absent tutorial (no steps). */ + /** + * An absent tutorial (no steps). + * + * @return a tutorial that {@link #isEmpty()} reports as empty + */ public static Tutorial none() { return new Tutorial(null, List.of(), null); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/TutorialStep.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/TutorialStep.java index e32b587..57bd191 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/TutorialStep.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/TutorialStep.java @@ -5,6 +5,12 @@ package thb.jeanluc.adventure.model; * ({@code expect} is a verb name like "look", or "go_direction"/"go_to_room"), * a minimum argument count, a success confirmation, and a hint for a valid but * wrong command. + * + * @param instruction task text shown to the player while this step is active + * @param expect name of the command that advances this step + * @param minArgs number of arguments the command must carry to count + * @param confirm text written once the step is satisfied + * @param hint text written when the player runs a valid but different command */ public record TutorialStep( String instruction, diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/World.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/World.java index c8c9054..50ed75a 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/World.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/model/World.java @@ -40,19 +40,46 @@ public class World { /** Global lookup of combination recipes by canonical pair key. */ private final Map combinations; - /** Backward-compatible constructor for worlds without quests, endings, or combinations. */ + /** + * Backward-compatible constructor for worlds without quests, endings, or combinations. + * + * @param rooms rooms by id + * @param items item prototypes by id + * @param npcs NPCs by id + * @param title display title + * @param welcomeMessage welcome message printed at startup + */ public World(Map rooms, Map items, Map npcs, String title, String welcomeMessage) { this(rooms, items, npcs, title, welcomeMessage, Map.of(), List.of(), Map.of()); } - /** Backward-compatible constructor for worlds with quests but no endings/combinations. */ + /** + * Backward-compatible constructor for worlds with quests but no endings/combinations. + * + * @param rooms rooms by id + * @param items item prototypes by id + * @param npcs NPCs by id + * @param title display title + * @param welcomeMessage welcome message printed at startup + * @param quests quests by id + */ public World(Map rooms, Map items, Map npcs, String title, String welcomeMessage, Map quests) { this(rooms, items, npcs, title, welcomeMessage, quests, List.of(), Map.of()); } - /** Backward-compatible constructor for worlds with quests + endings but no combinations. */ + /** + * Backward-compatible constructor for worlds with quests + endings but no combinations. + * + * @param rooms rooms by id + * @param items item prototypes by id + * @param npcs NPCs by id + * @param title display title + * @param welcomeMessage welcome message printed at startup + * @param quests quests by id + * @param endings endings in evaluation order + */ public World(Map rooms, Map items, Map npcs, String title, String welcomeMessage, Map quests, List endings) { this(rooms, items, npcs, title, welcomeMessage, quests, endings, Map.of()); diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveCodec.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveCodec.java index 0f27340..dfd0dec 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveCodec.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveCodec.java @@ -20,10 +20,16 @@ import java.util.Map; */ public final class SaveCodec { + /** Utility class; not instantiable. */ private SaveCodec() { } - /** Reads the mutable state of {@code session} into a {@link SaveData}. */ + /** + * Reads the mutable state of {@code session} into a {@link SaveData}. + * + * @param session the live session to snapshot + * @return a snapshot of the session's mutable state + */ public static SaveData capture(GameSession session) { World w = session.getWorld(); Player p = session.getPlayer(); @@ -67,6 +73,10 @@ public final class SaveCodec { * Overlays {@code data} onto {@code session} (whose world was just loaded * from YAML). Throws {@link SaveException} if the save references ids that * no longer exist in the current world. + * + * @param data the snapshot to restore + * @param session the session to overwrite + * @throws SaveException if the snapshot refers to a room or item the current world does not contain */ public static void apply(SaveData data, GameSession session) { World w = session.getWorld(); @@ -106,6 +116,7 @@ public final class SaveCodec { session.setTurn(data.turn()); } + /** Resolves a room id against the world, or fails with a {@link SaveException}. */ private static Room requireRoom(World w, String id) { Room r = w.getRooms().get(id); if (r == null) { @@ -115,6 +126,7 @@ public final class SaveCodec { return r; } + /** Resolves an item id against the world, or fails with a {@link SaveException}. */ private static Item requireItem(World w, String id) { Item i = w.getItems().get(id); if (i == null) { diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveData.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveData.java index 75fa852..402bb32 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveData.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveData.java @@ -7,6 +7,21 @@ import java.util.Map; * JSON-serialised snapshot of the mutable game state — a delta laid over the * world freshly loaded from YAML. Header fields ({@code slotName}, room, * {@code turn}, {@code savedAtEpochMillis}) double as the slot-list metadata. + * + * @param schemaVersion on-disk format version; must equal {@link #CURRENT_VERSION} to load + * @param worldTitle title of the world the snapshot was taken from + * @param slotName player-visible name of the save slot + * @param savedAtEpochMillis wall-clock time the snapshot was written, in epoch milliseconds + * @param turn turn counter at the time of saving + * @param currentRoomId id of the room the player stands in + * @param visitedRoomIds ids of all rooms the player has entered (fog of war) + * @param gold the player's gold amount + * @param inventoryItemIds ids of the items the player carries + * @param flags set world-state flags + * @param questStages stage index per active quest id + * @param questCompleted ids of completed quests + * @param roomItemIds item ids lying in each room, keyed by room id; rooms without items are omitted + * @param switchStates on/off state per switchable item id */ public record SaveData( int schemaVersion, diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveException.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveException.java index 190d06b..efec4e8 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveException.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveException.java @@ -3,15 +3,30 @@ package thb.jeanluc.adventure.save; /** Thrown when a save cannot be written/read; carries a player-facing message. */ public class SaveException extends RuntimeException { + /** + * Creates a failure that keeps the triggering exception as its cause. + * + * @param message player-facing description of the failure + * @param cause the underlying I/O or parse failure + */ public SaveException(String message, Throwable cause) { super(message, cause); } + /** + * Creates a failure without an underlying cause. + * + * @param message player-facing description of the failure + */ public SaveException(String message) { super(message); } - /** Player-facing message (the constructor message is already user-friendly). */ + /** + * Player-facing message (the constructor message is already user-friendly). + * + * @return the message to show to the player + */ public String getUserMessage() { return getMessage(); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveSlotInfo.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveSlotInfo.java index dd7e186..d2811f6 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveSlotInfo.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SaveSlotInfo.java @@ -1,6 +1,14 @@ package thb.jeanluc.adventure.save; -/** Lightweight metadata for one save slot, shown in the Load menu. */ +/** + * Lightweight metadata for one save slot, shown in the Load menu. + * + * @param slug filename stem of the slot on disk; the id used to load it + * @param slotName player-visible name of the slot + * @param roomId id of the room the player stands in + * @param turn turn counter at the time of saving + * @param savedAtEpochMillis wall-clock time the slot was written, in epoch milliseconds + */ public record SaveSlotInfo(String slug, String slotName, String roomId, int turn, long savedAtEpochMillis) { } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/Settings.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/Settings.java index cc30359..f4b0b97 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/Settings.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/Settings.java @@ -3,9 +3,16 @@ package thb.jeanluc.adventure.save; import thb.jeanluc.adventure.io.ConsoleIO; import thb.jeanluc.adventure.io.MusicLevel; -/** Persisted user preferences (colour + glyph fidelity + music level). */ +/** + * Persisted user preferences (colour + glyph fidelity + music level). + * + * @param colorMode ANSI colour policy of the console frontend + * @param glyphMode glyph fidelity used when rendering maps and frames + * @param musicLevel volume level of the background music + */ public record Settings(ConsoleIO.ColorMode colorMode, ConsoleIO.GlyphMode glyphMode, MusicLevel musicLevel) { + /** Compact canonical constructor: substitutes the defaults for any null component. */ public Settings { if (colorMode == null) { colorMode = ConsoleIO.ColorMode.AUTO; @@ -18,11 +25,21 @@ public record Settings(ConsoleIO.ColorMode colorMode, ConsoleIO.GlyphMode glyphM } } - /** Backward-compatible constructor for callers that don't set music. */ + /** + * Backward-compatible constructor for callers that don't set music. + * + * @param colorMode ANSI colour policy of the console frontend + * @param glyphMode glyph fidelity used when rendering maps and frames + */ public Settings(ConsoleIO.ColorMode colorMode, ConsoleIO.GlyphMode glyphMode) { this(colorMode, glyphMode, MusicLevel.MEDIUM); } + /** + * Settings used when nothing has been persisted yet. + * + * @return the default preferences + */ public static Settings defaults() { return new Settings(ConsoleIO.ColorMode.AUTO, ConsoleIO.GlyphMode.UNICODE, MusicLevel.MEDIUM); } diff --git a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SettingsStore.java b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SettingsStore.java index c912041..28a695d 100644 --- a/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SettingsStore.java +++ b/Semesterprojekt/src/main/java/thb/jeanluc/adventure/save/SettingsStore.java @@ -11,18 +11,31 @@ import java.nio.file.Path; @Slf4j public class SettingsStore { + /** Backing JSON file. */ private final Path file; + + /** Jackson mapper used for both directions. */ private final ObjectMapper mapper = new ObjectMapper(); + /** Uses the default {@code saves/settings.json} file. */ public SettingsStore() { this(Path.of("saves", "settings.json")); } + /** + * Uses a custom settings file. + * + * @param file JSON file to read from and write to + */ public SettingsStore(Path file) { this.file = file; } - /** Returns persisted settings, or {@link Settings#defaults()} on any problem. */ + /** + * Returns persisted settings, or {@link Settings#defaults()} on any problem. + * + * @return the stored settings, or the defaults if the file is missing or unreadable + */ public Settings load() { if (!Files.isRegularFile(file)) { return Settings.defaults(); @@ -35,7 +48,11 @@ public class SettingsStore { } } - /** Persists settings; logs and swallows failures (non-fatal). */ + /** + * Persists settings; logs and swallows failures (non-fatal). + * + * @param settings the preferences to write + */ public void save(Settings settings) { try { if (file.getParent() != null) {