9.1 KiB
Spec: Pathfinding go to <room>
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 <raum> 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 <richtung>. Ziel: go to <raum> 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 entferntto).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:
if (args.isEmpty()) { io.write("Go where? Try 'go north' or 'go to <room>'."); 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, wennid.equals(name)oderroom.getName().toLowerCase().equals(name). - Kein Treffer →
"You don't know any direction or place called '<name>'."(verrät keine unentdeckten Räume). - Ziel == aktueller Raum →
"You're already in the <Name>." - sonst →
Pathfinder.findPath; leer →"You can't find a way there right now."; sonst Auto-Walk (§6).
5. Pathfinder (game/Pathfinder.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<Room> findPath(GameContext ctx, Room target) { ... }
- BFS ab
player.getCurrentRoom(); Queue +Map<Room,Room> cameFrom. - Kante
from --dir--> topassierbar gdw.:toist inplayer.getVisitedRoomIds(),- Lock:
from.getExitLocks().get(dir)ist null oderConditions.all(lock.requires(), ctx), - Licht:
Light.isLit(ctx, to).
- Bei Erreichen von
targetPfad viacameFromrekonstruieren (ohne Startraum, inkl.target). Unerreichbar →List.of(). - Rein über die Domäne +
ctx; keine IO. Testbar wieMapLayoutTest/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
List<Room> 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());
setCurrentRoommarkiert Räume als besucht (bereits besucht → idempotent).go toist ein Zug (ein Command) — der Game-Loop erhöhtturneinmal.
GameIO.travelStep(MapView)
/** Per-step travel frame during auto-walk. Console: no-op (silent). */
default void travelStep(MapView view) {
// no animation in text mode
}
SwingIO-Override:
@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 wennn >= 3und 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 <Start> to the <Ziel>." - n >= 3:
"You make your way from the <Start>, through the <Mitte>, to the <Ziel>."
- n == 2:
(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, wieMapLayoutTest):- 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 <name>läuft zum Ziel (currentRoom aktualisiert, Ziel-lookerfolgt).- 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 nutzenTestIO; 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 eigenenuebung-Strukturen").MapLayoutmacht bereits BFS —Pathfinderist die fokussierte Wegfindungs-Variante (eigene Verantwortlichkeit, eigene Tests). - GUI-Threading:
travelStepschläft auf dem Worker-Thread (wiereadLineblockiert),setMapzeichnet 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.