From d10632b80024b2f27f46586ea8c99f02c8b3013c Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 1 Jun 2026 19:03:26 +0200 Subject: [PATCH] docs: spec for go to pathfinding BFS over visited rooms (respecting locks + light), routed via GoCommand (go to vs go ); silent console traversal + summary naming start/middle/destination; GUI minimap animates per step via a new GameIO.travelStep hook (SwingIO override, ~300ms). Trie autocomplete deferred. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-06-01-pathfinding-go-to-design.md | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 Semesterprojekt/docs/superpowers/specs/2026-06-01-pathfinding-go-to-design.md diff --git a/Semesterprojekt/docs/superpowers/specs/2026-06-01-pathfinding-go-to-design.md b/Semesterprojekt/docs/superpowers/specs/2026-06-01-pathfinding-go-to-design.md new file mode 100644 index 0000000..149a780 --- /dev/null +++ b/Semesterprojekt/docs/superpowers/specs/2026-06-01-pathfinding-go-to-design.md @@ -0,0 +1,194 @@ +# Spec: Pathfinding `go to ` + +Stand: 2026-06-01. Zweite der verbleibenden Mechaniken (nach `use X on Y`). +Setzt den in `enhancement-ideas.md` als Algorithmus-Showcase notierten Punkt um: +`go to ` per BFS. Baut auf `GoCommand`, `MapLayout` (BFS-Gitter + Fog of +War), `Light`, `Conditions`/`ExitLock` und der `GameIO`-Abstraktion auf. + +## 1. Kontext & Ziel + +Bewegung erfolgt bisher Schritt für Schritt per `go `. Ziel: **`go to +`** läuft automatisch per **BFS** über bereits **besuchte** Räume zum Ziel, +unter Beachtung derselben Gates wie ein manueller Zug (verschlossene Exits, Licht). +In der **GUI** soll die Minikarte die Bewegung **Raum für Raum animiert** zeigen; +in der Konsole läuft die Reise still mit einer knappen Zusammenfassung. + +Bestätigte Entscheidungen (Brainstorming 2026-06-01): +- **Erreichbarkeit**: nur **besuchte** Räume sind Wegpunkte/Ziele (Fog of War bleibt + erhalten; kein Teleport in Unentdecktes). +- **Darstellung**: Konsole still + Zusammenfassung + Ziel-`look`; GUI animiert die + Minikarte (Map-Push pro Schritt). +- **Zusammenfassung**: nennt **Start**, **Ziel** und **höchstens einen** mittleren + Wegpunkt (Mittelknoten). Bei 9 Knoten: 1., 5., 9. +- **Schrittverzögerung GUI**: ~**300 ms** pro Raum. +- **Trie-Autocomplete**: **zurückgestellt** (eigenes späteres Teilprojekt). + +## 2. Scope + +**In Scope:** +- `GoCommand`-Erweiterung: Argument ist Richtung → bestehender Einzelzug; sonst → + Raum-Ziel-Pathfinding. Keine neue Verb-Registrierung (Parser entfernt `to`). +- `Pathfinder` (`game`): BFS über besuchte Räume mit Passierbarkeits-Prädikat + (Exit-Lock + Licht), liefert den Pfad oder leer. +- Auto-Walk: Schritt für Schritt `setCurrentRoom` + `GameIO.travelStep(MapView)`; + danach Zusammenfassung + `LookCommand`. +- Neue `GameIO.travelStep(MapView)`-Default-Methode (No-Op Konsole) + `SwingIO`- + Override (Map-Push + ~300 ms Sleep). +- Ziel-Auflösung nur unter besuchten Räumen (id, dann name; case-insensitive). +- Zusammenfassung mit Start/Mitte/Ziel-Logik. +- Tests: `Pathfinder` (BFS, Locks, Licht, besucht-nur, unerreichbar), `GoCommand` + (Raum-Ziel, Meldungen, Richtung weiterhin). + +**Out of Scope:** +- Trie-/Tab-Autocomplete (eigenes Teilprojekt). +- Routing durch **unbesuchte** Räume. +- Pfad-Neuplanung mitten im Lauf (Zustand ändert sich während eines Zuges nicht). +- Gewichtete Kanten / Kosten (alle Kanten gleich; BFS = kürzeste Raumzahl). +- Mehr-Wort-Item-/Richtungs-Sonderfälle über die beschriebene Auflösung hinaus. + +## 3. Routing in `GoCommand` + +Der Parser entfernt `to`/`with`/… als Filler, daher liefern `go to library` und +`go library` beide `args=[library]`. `execute` verzweigt: + +```java +if (args.isEmpty()) { io.write("Go where? Try 'go north' or 'go to '."); return; } +String first = args.getFirst(); +try { + Direction dir = Direction.fromString(first); + moveDirection(ctx, dir); // bestehender Einzelzug (unverändert ausgelagert) + return; +} catch (IllegalArgumentException notADirection) { + goToRoom(ctx, args); // neuer Raum-Ziel-Pfad +} +``` + +`moveDirection` = der bisherige Rumpf (Exit prüfen, Lock, Licht, setCurrentRoom, +look). `move`/`walk`-Aliase erben beides. + +## 4. Ziel-Auflösung (`goToRoom`) + +- Zielname = `String.join(" ", args).toLowerCase()` (`go to old kitchen` → "old kitchen"). +- Suche **nur unter besuchten Räumen** (`player.getVisitedRoomIds()` → `world.getRooms().get(id)`): + Treffer, wenn `id.equals(name)` **oder** `room.getName().toLowerCase().equals(name)`. +- Kein Treffer → `"You don't know any place called ''."` (verrät keine + unentdeckten Räume). +- Ziel == aktueller Raum → `"You're already in the ."` +- sonst → `Pathfinder.findPath`; leer → `"You can't find a way there right now."`; + sonst Auto-Walk (§6). + +## 5. `Pathfinder` (game/Pathfinder.java) + +```java +/** Shortest path (BFS) over visited rooms to target, honouring locks + light. + * Returns the steps AFTER the current room, ending at target; empty if none. */ +public static List findPath(GameContext ctx, Room target) { ... } +``` + +- BFS ab `player.getCurrentRoom()`; Queue + `Map cameFrom`. +- Kante `from --dir--> to` passierbar gdw.: + 1. `to` ist in `player.getVisitedRoomIds()`, + 2. Lock: `from.getExitLocks().get(dir)` ist null **oder** `Conditions.all(lock.requires(), ctx)`, + 3. Licht: `Light.isLit(ctx, to)`. +- Bei Erreichen von `target` Pfad via `cameFrom` rekonstruieren (ohne Startraum, + inkl. `target`). Unerreichbar → `List.of()`. +- Rein über die Domäne + `ctx`; keine IO. Testbar wie `MapLayoutTest`/`QuestEngineTest`. + +Hinweis: Da nur besuchte Räume Kanten liefern und Start immer „besucht" ist, ist +die Suche auf die bekannte Karte beschränkt. `target == current` behandelt +`goToRoom` vorab (Pathfinder müsste sonst leeren Pfad liefern). + +## 6. Auto-Walk + animierte Minikarte + +```java +List path = Pathfinder.findPath(ctx, target); +if (path.isEmpty()) { io.write("You can't find a way there right now."); return; } +for (Room step : path) { + ctx.getPlayer().setCurrentRoom(step); + ctx.getIo().travelStep(MapLayout.compute( + ctx.getWorld(), ctx.getPlayer().getVisitedRoomIds(), step)); +} +ctx.getIo().write(summary(routeIncludingStart)); // §7 +new LookCommand().execute(ctx, List.of()); +``` + +- `setCurrentRoom` markiert Räume als besucht (bereits besucht → idempotent). +- `go to` ist **ein** Zug (ein Command) — der Game-Loop erhöht `turn` einmal. + +### `GameIO.travelStep(MapView)` + +```java +/** Per-step travel frame during auto-walk. Console: no-op (silent). */ +default void travelStep(MapView view) { + // no animation in text mode +} +``` + +`SwingIO`-Override: +```java +@Override +public void travelStep(MapView view) { + setMap(view); // Map-Panel neu zeichnen (invokeLater) + try { Thread.sleep(TRAVEL_STEP_MS); } // ~300 ms, läuft auf dem Worker-Thread + catch (InterruptedException e) { Thread.currentThread().interrupt(); } +} +``` +`TRAVEL_STEP_MS = 300`. Konsole bleibt still und ohne Pause (Default No-Op). +Der reguläre `publishHud` am Zugende setzt den finalen Map-/HUD-Stand. + +## 7. Zusammenfassungs-Zeile (§ Wegpunkte) + +Über die volle Route `route = [current] + path` (n = `route.size()`, Index 0..n-1): +- **Start** = `route[0]`, **Ziel** = `route[n-1]` immer genannt. +- **Mitte** = `route[n/2]` nur wenn `n >= 3` und der Index weder 0 noch n-1 ist + (also höchstens **ein** Zwischenpunkt). Bei n=9 → Index 4 (der 5. Knoten). +- Rendering: + - n == 2: `"You make your way from the to the ."` + - n >= 3: `"You make your way from the , through the , to the ."` + +(Artikel „the" bewusst einfach gehalten; reine Flavor-Zeile, leicht anpassbar.) + +## 8. Fehler-/Randfälle (Meldungen) + +| Fall | Meldung | +|---|---| +| `go` ohne Argument | "Go where? Try 'go north' or 'go to '." | +| Richtung ohne Exit | (bestehend) "You can't go from here." | +| Richtung gesperrt / dunkel | (bestehend) Lock-Text / Dunkelheits-Hinweis | +| Zielname unbekannt (unter besuchten) | "You don't know any place called ''." | +| Ziel == aktueller Raum | "You're already in the ." | +| Ziel besucht, aber kein passierbarer Pfad | "You can't find a way there right now." | + +## 9. Tests + +- **`PathfinderTest`** (Welt inline, wie `MapLayoutTest`): + - kürzester Pfad über besuchte Räume (Länge + Knoten korrekt). + - unbesuchter Zwischenraum wird **nicht** als Wegpunkt genutzt (kein Pfad bzw. + Umweg nur über Besuchtes). + - kein Pfad durch einen Exit mit nicht erfüllter `ExitLock`-Bedingung; Pfad + erscheint, wenn die Flag gesetzt ist. + - kein Pfad in einen dunklen Raum ohne Licht; mit getragenem Licht erreichbar. + - unerreichbares Ziel → leere Liste. +- **`GoCommand`-Tests** (Raum-Ziel): + - `go to ` läuft zum Ziel (currentRoom aktualisiert, Ziel-`look` erfolgt). + - Auflösung per id **und** per name (case-insensitive). + - unbekannter Name / Ziel==aktuell / kein Pfad → jeweilige Meldung. + - `go north` (Richtung) funktioniert weiterhin (Regressionsschutz). +- **`travelStep`**: Default ist No-Op (Tests nutzen `TestIO`; keine Sleeps, keine + GUI). GUI-Animation wird manuell verifiziert (Konvention: keine GUI-Unit-Tests). + +## 10. Architektur-Werte / Risiken + +- **Gemeinsamer Loop bleibt**: nur eine neue `GameIO`-Default-Methode; Pacing/ + Animation lebt im Renderer (`SwingIO`), Konsole No-Op. Keine Divergenz der Logik. +- **Konsistenz mit manuellem Zug**: Pathfinder nutzt dieselben Gates (Lock, Licht) + wie `GoCommand` — kein Auto-Walk durch etwas, das man manuell nicht passieren darf. +- **Fog of War**: nur besuchte Räume → kein Informationsleck über Unentdecktes. +- **Algorithmus-Showcase**: klassisches BFS (Queue + `cameFrom`) mit + Standard-Collections (entspricht der Projekt-Entscheidung „keine eigenen + `uebung`-Strukturen"). `MapLayout` macht bereits BFS — `Pathfinder` ist die + fokussierte Wegfindungs-Variante (eigene Verantwortlichkeit, eigene Tests). +- **GUI-Threading**: `travelStep` schläft auf dem Worker-Thread (wie `readLine` + blockiert), `setMap` zeichnet via EDT — kein Deadlock; EDT bleibt responsiv. +- Risiko: lange Pfade × 300 ms könnten sich zäh anfühlen; bewusst akzeptiert (kurze + Karte). Später ggf. Settings-gesteuert.