Commit Graph

151 Commits

Author SHA1 Message Date
d7f91c2e47 docs(readme): restructure around the two ways to run the game
The README buried the "just run it" path below the Maven section and left out
several commands the game actually supports.

- Split the quick start into Weg A (prebuilt jars, Java only, works offline) and
  Weg B (Maven wrapper: test, package, exec:java@run / @gui)
- State the Java 21 prerequisite up front, with how to check it
- Note that the console version runs headless while the GUI needs a desktop, and
  where saves/ is written
- Document the missing commands: 'go to <raum>', 'save'/'speichern', and the
  skippable tutorial
- Add the scope up front (14 rooms, 19 items, 3 NPCs, 259 tests)

All commands and links in the file were executed/resolved before committing.
2026-07-14 12:55:19 +02:00
c205d1f49b docs: correct stale documentation and drop planning artifacts
The docs had drifted from the code and described things that never existed.

- architecture.md documented a QuitCommand class (quit/exit are aliases on
  MenuCommand) and a GameIO interface of read()/write(String); the real one is
  readLine() plus print(StyledText) with presentation hooks. Both corrected,
  along with the package diagram and the game-loop sketch.
- conventions.md listed a package tree missing save, menu, io.text and
  command.impl. Updated, including why MapLayout lives in game.
- data-structures.md claimed LinkedList was deliberately avoided (Pathfinder
  uses one for O(1) addFirst during path reconstruction), named the wrong queue
  for the Swing bridge, and listed an input history that was never built.
- Removed enhancement-ideas.md and implementation-status.md: a backlog and a
  phase tracker, neither of which documents the delivered project.

Also stop tracking the built jars. They still ship in the submission, but a
22 MB binary that changes on every build does not belong in git history.
2026-07-14 12:40:09 +02:00
526e3d4ee7 docs(javadoc): document every class, method and instance variable
The assignment requires Javadoc on classes, methods and instance variables.
Coverage was at roughly 45%: enum constants were undocumented across the board
(0 of 37), record components — which are a record's instance variables — sat at
24%, and instance fields at 52%. GameSession was @Getter-exposed with all six
fields undocumented, the exact case docs/conventions.md calls out by name.

Backfilled across model, io, game, loader, command, save and menu:
all enum constants, all record components (@param blocks), all instance fields,
and the public/protected methods and constructors that lacked tags.

'javadoc -Xdoclint:missing' now reports zero missing-doc warnings for the public
API. The 30 remaining warnings are all "use of default constructor" on
Lombok-generated constructors, which would require adding code to silence.
2026-07-14 12:39:25 +02:00
1b39eac90e refactor: move MapLayout into the game package
The 'map' package held a single class, which breaks the project's own rule in
docs/conventions.md: a subpackage only pays for itself once a class family
reaches three classes and forms its own abstraction.

MapLayout is BFS grid-layout logic and belongs next to Pathfinder in 'game'.
2026-07-14 12:39:15 +02:00
4cf1b42254 fix(io,save): music thread leak, save-slot collision, unresponsive GUI
- OggMusicBackend shared one 'stopping' flag across playbacks and nulled the
  thread handle without checking whether the join had actually succeeded. A
  thread that outlived its join budget saw the flag flip back to false and
  resumed playing, so two tracks played at once and the orphan never died. Each
  playback now owns its stop token, and fades abort when it is set.

- SaveService.slug mapped "Bedroom Run" and "Bedroom_Run" to the same filename,
  so the second save silently overwrote the first. The slug is now injective: a
  short hash is appended whenever sanitising was lossy. Clean names keep clean
  filenames, so existing saves still load.

- The Swing worker is the only consumer of the input queue, so when it died on a
  loader error the window stayed open and ignored every command forever. It now
  reports the error and closes. Typing while the menu overlay is up no longer
  queues commands that get replayed once the menu closes.
2026-07-14 12:38:56 +02:00
73fc6eab68 feat(command): accept German command and direction aliases
The assignment's examples are German ("Gehe nach Norden", "Nimm Brief",
"Lies Brief", "Benutze Schaufel") but every verb was English-only, so none of
them parsed.

- Register German aliases alongside the English ones (gehe, nimm, lies, benutze,
  lege, gib, rede, schau, karte, inventar, hilfe, beenden)
- Direction.fromString accepts norden/sueden/süden/osten/westen and the n/s/e/w
  shorthands; YAML exits keep the canonical English names
- Treat "nach", "den", "die", "das" as filler words so "Gehe nach Norden" parses
2026-07-14 12:38:45 +02:00
8f6ee80f30 fix(quests): tick the escort engine before the quest engine; guard the valve
Two independent world-logic bugs:

- Game.publishHud ticked QuestEngine before EscortEngine, so the twins_reunited
  flag set by the escort was only observed a full turn later. On the turn the
  twins were reunited, 'quests' still listed the objective as active.

- The valve_wheel/valve recipe had neither 'consume' nor 'requires', so it could
  be repeated indefinitely, minting a fresh ceramic fuse every time. It now
  requires notFlag cellar_drained and explains itself once the cellar is dry.
2026-07-14 12:38:35 +02:00
cc2221121d fix(game): apply the darkness rule to every command, not just three
Only look, examine, take and go checked whether the room was lit. In a pitch
black room the player could still read a letter, talk to and bribe an NPC, drop
items, and even 'use valve_wheel on valve' to drain the cellar and complete a
quest objective — all in total darkness.

Light.canSeeRoom() now gates every command that resolves room contents. The
inventory stays reachable in the dark on purpose: otherwise switching the lamp
off inside a dark room would be an unrecoverable soft-lock.

Room-item lookups in UseCommand and Combinations are gated too, and the
"too dark" message does not reveal whether the item is actually there.
2026-07-14 12:38:26 +02:00
285e68055e fix(items): make scenery items fixed so they cannot be taken
Nothing stopped the player from picking up scenery. The front door is an
ordinary switchable item in the foyer whose on-transition sets the 'left_manor'
flag that every ending keys off, so it could be carried into the cellar and used
there to end the game two floors underground. The chapel shrine could likewise
be carried out and the locket/photograph ritual performed anywhere.

Item gains a 'fixed' flag; TakeCommand refuses fixed items and leaves them in
the room. Fixed items stay usable in place, so all recipes still work.

Marked fixed: front_door, shrine, generator, valve, portrait — their
descriptions already claimed they were bolted down.
2026-07-14 12:38:16 +02:00
65a8935e32 fix(loader): resolve every id reference at load time
WorldValidator only checked id syntax, startRoom and NPC greetings. Quests,
endings and combinations were never even passed to it, so a broken world could
load "successfully" and then fail during play:

- A room without a 'name' loaded cleanly and threw an NPE in ConsoleIO.frameTop
  the moment the player walked in.
- A typo'd item id in a quest condition silently evaluated to false forever,
  leaving the stage uncompletable with no diagnostic.

The validator now receives quests, endings and combinations, and resolves every
item and quest id used in conditions, effects and recipes. Rooms and items must
carry a non-blank name and description. All problems are collected and reported
together instead of one per run.
2026-07-14 12:38:00 +02:00
e5c87015fa build: target Java 21 (LTS) instead of Java 25
maven.compiler.source/target were set to 25, but the newest language feature
actually used is List.getFirst() (SequencedCollection, Java 21). On a JDK 17 or
21 the project would not compile at all.

- Use maven.compiler.release=21 instead of the source/target pair
- README: update prerequisites, tech stack and test count
- .gitignore: ignore the Nextcloud sync file and release/*.jar
2026-07-14 12:37:45 +02:00
8e63f21c7d build(io): split release into Console + GUI jars and slim the console jar
Replace the single HauntedManor.jar with two self-contained jars, one per
entry point (HauntedManor-Console.jar -> App, HauntedManor-GUI.jar -> AppGui),
each with its own Main-Class so both run via plain `java -jar`.

The console jar omits the OGG decode libraries and the ~11 MB of music tracks
(GameIO.setMusic is a no-op in text mode), dropping it from 17 MB to ~5 MB.
README documents running each jar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 10:31:36 +02:00
41c79c3b96 feat(world): add the Lost Twins optional escort side-quest
Two separated brothers, Marlon (conservatory) and Malte (boiler room), can be
reunited as optional content. Carry each twin's marble to the other as proof
he's alive and that twin follows you; lead both to the foyer and they escape
together, granting a Brass Compass keepsake and unlocking a new top-priority
"No One Left Behind" ending. Purely optional — no main win condition depends
on it.

The new EscortEngine drives following and the foyer reunion from flags alone,
so save/load needs no format change (it reconciles follower placement on the
first tick after loading). Adds Room.removeNpc, one tick call in the game loop,
and a follower note appended by LookCommand. descriptionStates swap the twins'
home-room prose once they leave.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 10:31:25 +02:00
f8f7eb90f0 build: add Maven wrapper and prebuilt release jar; ignore docs/superpowers
- Add ./mvnw wrapper (script-only) so the project builds/runs without a
  system Maven install.
- Add maven-shade-plugin producing a self-contained release/HauntedManor.jar
  (merges audio SPI services; no dependency-reduced-pom).
- Document running via ./mvnw and via the jar directly in the README.
- Ignore docs/superpowers/ (planning specs kept locally, untracked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:51:04 +02:00
675bf4c2ea Merge feature/content-expansion into develop
Haunted Manor content expansion (14 rooms, one world + solvability test),
bundled background music and GUI font, wider sidebar, and the main-menu
Quit fix.
2026-06-02 09:40:00 +02:00
d0966c3ce4 fix(menu): main-menu Quit now closes the GUI instead of revealing the game
App.run ended its menu loop on Quit and just returned. The console exits then,
but in the GUI run() is on a daemon worker while the Swing JFrame stays
displayable on the EDT, keeping the JVM alive — and the dismissed menu overlay
just revealed the running game underneath ("back into the game"). EXIT_ON_CLOSE
only fires for the native window X, not a programmatic quit.

Add a GameIO.shutdown() teardown hook (default no-op) and call it after the menu
loop ends. SwingIO overrides it to stop music, dispose the window, and exit the
JVM, mirroring EXIT_ON_CLOSE; console keeps the no-op. Extract an injectable
App.run overload and add AppQuitTest asserting the Quit path invokes shutdown().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:39:05 +02:00
1cbf606bd4 feat(io): widen GUI sidebar and drop its max-width cap
Give the map/quests sidebar more room: raise the width fraction (0.28 -> 0.34)
and minimum (180 -> 220), and remove the SIDE_MAX upper clamp so the panel
keeps scaling with the window instead of stopping at 460px.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:32:45 +02:00
7ea11058a1 feat(io): bundle Cascadia Mono Nerd Font as the GUI font
Install the downloaded Cascadia Mono Nerd Font as src/main/resources/fonts/
game.ttf — the exact name SwingIO.loadFont() looks up on the classpath
(/fonts/game.ttf), so the GUI now uses a real monospaced Nerd Font (icon
glyphs included) instead of falling back to the logical monospaced font.
Include the font's SIL Open Font License as required for redistribution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:27:25 +02:00
aa802582ec feat(io): bundle background music as classpath resources
Switch OggMusicBackend from streaming an external, git-ignored music/ folder
(relative to the run directory) to streaming bundled classpath resources under
/music/, so the four manor tracks ship inside the JAR and play out of the box —
consistent with how fonts are bundled. The play loop reopens the resource each
iteration via a BufferedInputStream (the vorbisspi SPI needs mark/reset to read
the OGG header). Missing tracks still fall back to silence.

Add the four converted OGG Vorbis tracks (manor-theme/cellar-drone/musicbox/
lament) under src/main/resources/music/, un-ignore that path, and re-anchor the
gitignore so only the top-level MP3 source folder stays ignored. Update
docs/music.md for the bundled approach and conversion recipes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:21:11 +02:00
d8e1a662bc feat(world): Haunted Manor content expansion (14 rooms, one world)
Replace the 5-room demo with a condensed 14-room manor on one spine
(restore power -> escape) that stacks all five mechanics, plus an optional
spirit thread feeding 3 prioritized endings (no hard-fail). Pure YAML on the
existing engines: 14 rooms across 3 levels, ~16 items, 5 combinations, the
old_man hint NPC, restore_power + lay_to_rest quests, and 3 endings.

Consolidate to one world: remove the toy "Test Manor" test fixture so the
loader tests exercise the real shipped world. Adapt WorldLoaderTest and
TutorialLoaderTest to form-invariants of the real world, and add
WorldSolvableTest, which walks the critical path (light/lock gating +
Combinations.tryUse + flags) to guarantee power_on, ghost_banished, and a
victory ending are reachable — an unsolvable world now fails the build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:06:28 +02:00
88cad77c83 Merge GUI background music into develop
Per-room OGG Vorbis background music in the Swing GUI: a testable MusicController
(idempotent track switch, null->silence, OFF disables) over an isolated
OggMusicBackend (vorbisspi/jorbis, streamed PCM, ~400ms gain fades, graceful on
missing files). Driven from GameIO.setMusic per turn (console no-op) and a new
persisted Music level (Off/Low/Med/High, default Medium) in Settings. Tracks come
from an external gitignored music/ folder via an optional rooms.yaml `music:`
field. Console stays silent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 23:29:11 +02:00
702f7ac0d4 docs: clarify music transition is a sequential fade, not cross-fade 2026-06-01 23:29:11 +02:00
830a95c9f0 docs: mark GUI background music implemented 2026-06-01 23:25:11 +02:00
f1aedebfcb feat(io): OggMusicBackend + SwingIO wiring; music/ folder + docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 23:20:14 +02:00
7380dcae41 refactor(settings): guard all Settings fields vs null; tidy import + test
Guard colorMode and glyphMode against null in the Settings compact
constructor (alongside the existing musicLevel guard), so a hand-edited
settings.json with a missing field no longer NPEs in SettingsMenu.
Remove the unused MusicLevel import from SettingsMenu. Assert that the
defaulted musicLevel survives a round-trip in SettingsStoreTest.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 23:17:26 +02:00
c6ed199f7e feat(settings): persisted Music level; SettingsMenu applies via GameIO
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 23:08:27 +02:00
bf36730524 feat(io): GameIO music seams; push room track per turn, silence at menu
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 23:02:44 +02:00
fa7851b6e0 feat(loader): per-room music field + demo rooms.yaml entries
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 22:58:13 +02:00
66e43830be fix(io): idempotent MusicController.level; assert gain forwarding + shutdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 22:55:27 +02:00
977872e77a feat(io): MusicController + MusicBackend (testable music logic)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 22:50:58 +02:00
3dd27a1b63 test(io): assert MEDIUM.isOn() too 2026-06-01 22:49:20 +02:00
e7db45f98d feat(io): MusicLevel enum (Off/Low/Med/High)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 22:47:40 +02:00
705e515a8e Merge: suppress endings during the tutorial 2026-06-01 22:09:06 +02:00
00d3f97c9b fix(game): suppress endings while the tutorial is active
A command during the walkthrough (e.g. use front_door -> fled) no longer ends
the game mid-tutorial; a pending ending fires once the tutorial finishes or is
skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 22:09:06 +02:00
ebeda36b4a Merge interactive start-of-game tutorial into develop
A skippable interactive walkthrough shown on New Game: a TutorialGuide layered
onto the real game loop makes the player perform the core commands
(look→examine→take→inventory→use→go→go to) in the starting room, with verb-/
alias-aware completion (movement steps require an actual room change), gentle
hints on wrong-but-valid input, and closing tips for the rest. Data-driven
(tutorial.yaml + standalone TutorialLoader); 'skip' ends it; loaded games never
show it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 22:03:59 +02:00
1ff475c085 docs: note interactive start-of-game tutorial 2026-06-01 21:59:44 +02:00
236ed0a180 refactor: consistent isActive() guard on tutorial onCommand; tidy App imports 2026-06-01 21:59:07 +02:00
c315d3f4b7 feat: wire TutorialGuide into the game loop; show on New Game only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 21:53:27 +02:00
e27a12e4fe fix(game): null-safe moved() + idempotent begin(); cover multi-step + go_to_room hint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 21:49:47 +02:00
af319508f6 feat(game): TutorialGuide interactive walkthrough engine
Alias-aware, stateful engine with verb/minArgs matching, go_direction/go_to_room
room-change detection, skip, and closing-tips on completion. 7 tests, all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 21:42:30 +02:00
dbfdf86810 test(tutorial): cover malformed tutorial.yaml -> WorldLoadException 2026-06-01 21:40:06 +02:00
91f79b1537 feat(tutorial): Tutorial model + TutorialLoader + tutorial.yaml content
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 21:25:48 +02:00
0250eadd50 Merge go to <room> pathfinding into develop
BFS auto-walk over visited rooms (respecting locked exits + darkness), routed
through GoCommand (go to <room> vs go <direction>). Console: silent traversal +
a start/middle/destination summary line. GUI: the minimap animates the player
moving room-by-room via a new GameIO.travelStep hook (SwingIO override, ~300ms;
console no-op). New stateless Pathfinder BFS in the game package. Trie
autocomplete deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 20:27:30 +02:00
0af172ff64 docs: mark go to <room> pathfinding implemented 2026-06-01 20:24:26 +02:00
d20f3fd7d6 fix(command): combined unknown-target message; cover both go-to summary forms
- UX: unknown-target message now reads "don't know any direction or place
  called '...'" so direction typos (e.g. 'go nroth') make sense
- clarity: resolveVisited uses equalsIgnoreCase for id/name matching;
  add null-world guard comment pointing to CommandTestSupport
- Javadoc: note that 'to' filler word is stripped, making go-to forms equivalent
- tests: assert 'through the Hallway' in 3-node route; update unknown message
  assertion; add adjacentWalkUsesShortSummary covering the n==2 short-form path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 20:23:41 +02:00
97e2809719 feat(command): go to <room> auto-walk (BFS path, animated map, summary)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 20:13:25 +02:00
f1b9695c5e feat(game): Pathfinder BFS over visited rooms (locks + light aware)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 20:08:11 +02:00
62b7663e34 feat(io): travelStep hook for animated auto-walk (console no-op, Swing override)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 20:04:17 +02:00
86e138223a Merge use X on Y item combinations into develop
Data-driven combination recipes (combinations.yaml: requires/consume/produce/
effects/response/failText), operands resolved from inventory or current room,
order-independent canonical pair key. UseCommand routes 2+ args to a new
Combinations engine; no parser change (on/with/to already fillers). Loader
validates ids + rejects self-combination/duplicate pairs at load. Demo:
matches + torch -> lit_torch (a working light source).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 17:48:20 +02:00
e0e8612982 docs: mark use X on Y item combination implemented 2026-06-01 17:45:16 +02:00