Files
Jander_Semester2/Semesterprojekt/docs/superpowers/specs/2026-06-01-pathfinding-go-to-design.md
Jean-Luc Makiola d10632b800 docs: spec for go to <room> pathfinding
BFS over visited rooms (respecting locks + light), routed via GoCommand
(go to <room> vs go <direction>); 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) <noreply@anthropic.com>
2026-06-01 19:03:26 +02:00

9.1 KiB
Raw Blame History

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 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:

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, wenn id.equals(name) oder room.getName().toLowerCase().equals(name).
  • Kein Treffer → "You don't know any 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--> 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

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());
  • 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)

/** 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 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 <Start> to the <Ziel>."
    • n >= 3: "You make your way from the <Start>, through the <Mitte>, to the <Ziel>."

(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 <name> 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.