Files
Jander_Semester2/Semesterprojekt/docs/superpowers/specs/2026-05-31-map-design.md
Jean-Luc Makiola 025372b9b1 docs: spec for map / mini-map (sub-project 2)
Fog-of-war map from the directional exit graph. MapLayout (BFS) produces a
frontend-agnostic MapView; SwingIO draws it (Graphics2D MapPanel, replaces
exits text), ConsoleIO mirrors it as ASCII via the map command. Visited
tracking on Player. Item/NPC markers and locked-door styling deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:51:09 +02:00

152 lines
6.7 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: Karte / Mini-Map (Teilprojekt 2)
Stand: 2026-05-31. Zweites Teilprojekt aus [enhancement-ideas.md](../../enhancement-ideas.md).
Baut auf der Präsentations-Schicht (Teilprojekt 1) auf, insbesondere dem
reservierten GUI-Seitenpanel und dem semantischen Modell-+-Renderer-Muster.
## 1. Kontext & Ziel
Räume bilden bereits einen gerichteten N/S/O/W-Graphen über `Room.exits`
(`EnumMap<Direction, Room>`). Ziel: eine **Karte** in beiden Frontends, die diesen
Graphen sichtbar macht **Fog of War** (nur Entdecktes), Stil **„umrandete Räume +
Korridore"** (im Brainstorming bestätigt). Die GUI zeichnet grafisch (Graphics2D),
die Konsole spiegelt es als ASCII.
Bestätigte Entscheidungen:
- **Fog of War**: nur besuchte Räume + deren direkte Nachbarn als `?`-Stub.
- **Stil**: umrandete Räume, durch Korridore verbunden; aktueller Raum hervorgehoben.
- **Layout aus dem Graphen abgeleitet** (keine Koordinaten in YAML).
- GUI: persistentes Panel ersetzt die Exit-Zeile. Konsole: `map`-Befehl on demand,
Exit-Zeile im Raumtext bleibt.
## 2. Scope
**In Scope:**
- `MapView`-Modell (platzierte Zellen + Verbindungen, Zustände).
- `MapLayout`-Service: BFS-Layout aus dem Exit-Graphen + Fog-of-War-Filter.
- **Besuchte-Räume-Tracking** auf `Player`.
- `GameIO`: `setMap` (Push pro Zug, GUI) + `showMap` (on demand, Konsole).
- `MapCommand` (`map`, `m`).
- `Game`-Loop ruft `setMap` pro Zug (neben `setHud`).
- GUI: `MapPanel` (Graphics2D) im Seitenpanel, scrollbar, auf aktuellen Raum zentriert.
- Konsole: ASCII-Karte (`AsciiMap`), respektiert `GlyphMode`.
**Out of Scope (spätere Teilprojekte):**
- Marker für Items/NPCs auf der Karte.
- Styling für verschlossene/blockierte Exits (Quest-Teilprojekt; Karte bekommt
später einen Hook dafür).
- Zoom über das Scrollen hinaus.
## 3. Architektur (Modell + Renderer pro Frontend)
`MapLayout` erzeugt aus `World` (Exit-Graph), der Menge besuchter Räume und dem
aktuellen Raum eine frontend-agnostische `MapView`. Layout und Fog-of-War-Logik
liegen ausschließlich hier; die Renderer kennen nur das fertige `MapView`.
```mermaid
flowchart LR
W["World (Exit-Graph)"] --> L["MapLayout.compute()"]
V["Player.visitedRoomIds"] --> L
C["aktueller Raum"] --> L
L --> MV["MapView (Zellen + Verbindungen)"]
MV -- "setMap()" --> GUI["SwingIO → MapPanel (Graphics2D)"]
MV -- "showMap()" --> CON["ConsoleIO → AsciiMap (StyledText)"]
```
## 4. Komponenten & Datenmodell
Views in `io.text`, Layout-Logik in neuem Package `map`, GUI-Panel in `io`.
```java
// io.text
public enum CellState { CURRENT, VISITED, KNOWN }
public record RoomCell(String id, String name, int x, int y, CellState state) {}
public record Connection(RoomCell from, RoomCell to) {} // Stil via Zellzustand
public record MapView(java.util.List<RoomCell> cells,
java.util.List<Connection> connections) {
public MapView { cells = List.copyOf(cells); connections = List.copyOf(connections); }
}
```
- `map.MapLayout` `static MapView compute(World world, String startRoomId, Set<String> visited, Room current)`.
- `io.text.AsciiMap` `static StyledText render(MapView, GlyphMode)`.
- `io.MapPanel` `JComponent`, `void show(MapView)`; `paintComponent` zeichnet.
- `command.impl.MapCommand` baut `MapView`, ruft `io.showMap(view)`.
- `model.Player` `Set<String> visitedRoomIds` (LinkedHashSet, Reihenfolge stabil).
### GameIO-Erweiterung (additiv)
```java
/** Push pro Zug: GUI aktualisiert ihr Panel; Konsole ignoriert (kein Spam). */
default void setMap(MapView view) { /* no-op */ }
/** On demand (map-Befehl): rendert die Karte. Default = ASCII in UNICODE. */
default void showMap(MapView view) { print(AsciiMap.render(view, GlyphMode.UNICODE)); }
```
- **`ConsoleIO`** überschreibt `showMap``print(AsciiMap.render(view, this.glyphs))`
(nutzt also seinen konfigurierten Glyph-Modus). `setMap` bleibt Default (no-op) →
kein Karten-Spam pro Zug in der Konsole.
- **`SwingIO`** überschreibt `setMap``MapPanel` neu zeichnen; `showMap` → no-op
(das Panel ist ohnehin sichtbar).
- **`TestIO`** nutzt beide Defaults: `setMap` = no-op, `showMap` druckt UNICODE-ASCII
in `outputs``MapCommand`-Test kann darauf assertieren. Kein Override nötig.
So liegt der Glyph-Modus dort, wo er hingehört (am Konsolen-Renderer), ohne die
`GameIO`-Schnittstelle um eine Glyph-Methode aufzublähen.
## 5. Fog-of-War-Regeln
| Zustand | Bedeutung | Darstellung |
|---|---|---|
| `CURRENT` | besuchter Raum, in dem der Spieler steht | Goldener Rahmen |
| `VISITED` | bereits betretener Raum | heller Rahmen + Name |
| `KNOWN` | direkter Exit-Nachbar eines besuchten Raums, noch nicht betreten | gestrichelter `?`-Stub |
| (hidden) | alles andere | nicht platziert |
Beim Betreten eines `?`-Raums wird er `VISITED`; seine Nachbarn werden zu neuen
`KNOWN`-Stubs. Die Karte wächst eine Ringschicht voraus.
## 6. Layout-Algorithmus & Konfliktbehandlung
BFS vom Startraum über den **vollständigen** Exit-Graphen; jede Richtung verschiebt
die Gitterkoordinate (N→y1, S→y+1, O→x+1, W→x1). Hand-gebaute Welten sind
geometrisch konsistent; falls BFS zwei Räume auf dieselbe Zelle legt (nicht-planarer
Graph), **gewinnt der erste**, die Kollision wird geloggt, das Rendern läuft weiter
(kein Crash). Die Fog-of-War-Filterung läuft nach dem Layout; danach werden die
Koordinaten normalisiert (Verschiebung, sodass min = 0,0).
## 7. Konsolen-Verhalten
`map` / `m` druckt die ASCII-Karte on demand (keine persistente Region in der
Konsole). Die Box-Raumansicht behält ihre Inline-`Exits:`-Zeile Konsolenspieler
müssen also nicht `map` tippen, um zu navigieren; die Karte ist Überblick-Bonus. In
der GUI ersetzt das Dauerpanel die Exit-Zeile.
## 8. Fehlerbehandlung
| Fall | Verhalten |
|---|---|
| Koordinaten-Kollision (nicht-planar) | erste Zuordnung gewinnt, Warnung geloggt, Rendern fährt fort |
| Startraum hat keine Exits | Karte zeigt nur die aktuelle Zelle |
| Sehr große Karte (viele Räume) | GUI-Panel scrollbar, auf aktuelle Zelle zentriert; ASCII so groß wie nötig |
## 9. Testing
- `MapLayout` (reine Logik): Koordinaten aus bekanntem Graphen; Fog-Zustände für eine
gegebene Visited-Menge; Kollisionsbehandlung; Normalisierung.
- `MapView`/Records: Immutabilität.
- `AsciiMap`: strukturelle Assertions auf bekanntem `MapView` (Namen vorhanden,
aktueller markiert, `?` für `KNOWN`), Farbe aus.
- `MapCommand`: über `TestIO` erzeugt Karten-Ausgabe.
- `Player`: Besuchte-Räume-Tracking (Start + nach Bewegung).
- `SwingIO`/`MapPanel`: nicht unit-getestet (Graphics2D/EDT); manueller Smoke-Test.
## 10. Offene Detailfragen (in Implementierung entscheidbar)
- ASCII-Zellbreite/-höhe und Verbindungslänge (Lesbarkeit vs. Kompaktheit).
- Genaue Farben/Strichstärken im `MapPanel` (an Präsentations-Palette angelehnt).