# 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 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)

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