# 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 direction or 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 direction or 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.