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

195 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```java
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)
```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
```java
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)`
```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 <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 <room>'." |
| Richtung ohne Exit | (bestehend) "You can't go <dir> from here." |
| Richtung gesperrt / dunkel | (bestehend) Lock-Text / Dunkelheits-Hinweis |
| Zielname unbekannt (unter besuchten) | "You don't know any place called '<name>'." |
| Ziel == aktueller Raum | "You're already in the <Name>." |
| 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.