std.grid — Gitter und Pfadfindung
Rechteckige Gitter mit Kosten je Zelle: Nachbarschaft mit 4 oder 8 Richtungen, Bresenham und Sichtlinie, BFS-Distanzkarten, Dijkstra und A*.
→ Mathematik-Units · std.graph · std.dist
import std.grid;
Schicht 0, hängt an std.math und std.alloc. 22 Funktionen, eine Konstante (GRID_BLOCKED = 0). Alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt.
Darstellung
Ein Gitter ist ein Feld aus w · h int64-Werten — die Kosten, eine Zelle zu betreten. GRID_BLOCKED ist 0 und bedeutet unpassierbar; alles ab 1 ist begehbar und kostet entsprechend.
var c: int64 := alloc(w * h * 8);
GridFill(c, w, h, 1); // alles begehbar, Kosten 1
GridSet(c, 2, 1, w, GRID_BLOCKED); // Mauer bei (2,1)
Die Umrechnung zwischen Koordinaten und Index übernehmen GridIndex(x, y, w), GridX(idx, w) und GridY(idx, w). GridInBounds und GridWalkable prüfen Rand und Begehbarkeit.
Der wichtigste Punkt: welche Heuristik zu welcher Nachbarschaft
Fast jede Funktion nimmt ein Flag diag. Der Unterschied ist nicht nur die Zahl der Nachbarn — er entscheidet auch darüber, welche Heuristik A* überhaupt verwenden darf.
unit main;
import std.io;
import std.string;
import std.alloc;
import std.grid;
fn ZF(t: pchar, v: f64): void { Print(t); PrintF64(v); }
fn ZI(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }
fn main(): int64 {
ZI("Manhattan (0,0)->(1,1) : ", GridManhattan(0,0,1,1));
ZF("Octile (0,0)->(1,1) : ", GridOctile(0,0,1,1));
ZF("Heuristic diag=false : ", GridHeuristic(0,0,1,1,false));
ZF("Heuristic diag=true : ", GridHeuristic(0,0,1,1,true));
return 0;
}
Manhattan (0,0)->(1,1) : 2
Octile (0,0)->(1,1) : 1.414213
Heuristic diag=false : 2.000000
Heuristic diag=true : 1.414213
<WRAP alert> A* findet den kürzesten Weg nur, wenn die Heuristik den Restweg nie überschätzt. Genau hier liegt der häufigste Fehler:
- 4 Richtungen → Manhattan ist richtig.
- 8 Richtungen → Manhattan überschätzt. Der Schritt von (0,0) nach (1,1) kostet tatsächlich 1,414, Manhattan behauptet 2. A* liefert dann Wege, die kürzer aussehen als sie sind, und bricht die Suche zu früh ab. Richtig ist die Oktil-Distanz.
GridHeuristic(x1, y1, x2, y2, diag) wählt anhand von diag die passende — die Zahlen oben zeigen es. Wer eine eigene Heuristik einsetzt, muss das selbst im Blick behalten.
</WRAP>
Diagonale Schritte kosten das √2-fache eines geraden. Alles andere ergäbe Wege, die diagonal billiger sind als gerade, und ließe Pfade zickzack laufen.
Pfadfindung
unit main;
import std.io;
import std.string;
import std.alloc;
import std.grid;
fn ZI(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }
fn main(): int64 {
var w: int64 := 5; var h: int64 := 5;
var c: int64 := alloc(w * h * 8);
GridFill(c, w, h, 1);
GridSet(c, 2, 0, w, GRID_BLOCKED);
GridSet(c, 2, 1, w, GRID_BLOCKED);
GridSet(c, 2, 2, w, GRID_BLOCKED);
GridSet(c, 2, 3, w, GRID_BLOCKED);
ZI("Walkable(2,1) : ", GridWalkable(c,2,1,w,h) as int64);
ZI("Walkable(2,4) : ", GridWalkable(c,2,4,w,h) as int64);
var px: int64 := alloc(64 * 8);
var py: int64 := alloc(64 * 8);
var len: int64 := GridAStar(c, w, h, 0, 0, 4, 0, false, px, py, 64);
Print("A* Laenge="); Print(IntToStr(len)); Print(" Weg: ");
var i: int64 := 0;
while (i < len) {
Print("("); Print(IntToStr(peek64(px + i*8))); Print(",");
Print(IntToStr(peek64(py + i*8))); Print(") ");
i := i + 1;
}
PrintLn("");
ZI("LineOfSight (0,0)-(4,0): ", GridLineOfSight(c,0,0,4,0,w,h) as int64);
ZI("LineOfSight (0,4)-(4,4): ", GridLineOfSight(c,0,4,4,4,w,h) as int64);
ZI("FloodCount von (0,0) : ", GridFloodCount(c,w,h,0,0,false));
return 0;
}
Walkable(2,1) : 0
Walkable(2,4) : 1
A* Laenge=13 Weg: (0,0) (1,0) (1,1) (1,2) (1,3) (1,4) (2,4) (3,4) (4,4) (4,3) (4,2) (4,1) (4,0)
LineOfSight (0,0)-(4,0): 0
LineOfSight (0,4)-(4,4): 1
FloodCount von (0,0) : 21
Die Mauer verläuft bei x=2 über y=0…3 und lässt nur unten bei y=4 eine Lücke. A* geht genau dort herum — 13 Felder statt der 5 auf direktem Weg. LineOfSight bestätigt es: durch die Mauer nein, entlang y=4 ja. FloodCount zählt 21 erreichbare Zellen — 25 minus die 4 blockierten.
| Funktion | Zweck |
|---|---|
GridBfs(cells, w, h, sx, sy, diag, out) | Distanzkarte in Schritten, ignoriert Kosten |
GridDijkstra(…) | Distanzkarte mit Kosten |
GridAStar(…, gx, gy, diag, outX, outY, maxLen) | ein Weg zu einem Ziel, Länge als Rückgabe |
GridPathFrom(prev, goal, w, …) | Weg aus einer BFS-/Dijkstra-Vorgängerkarte |
GridPathCost(…) | Kosten eines fertigen Weges |
GridReachable · GridFloodCount | was ist von hier aus erreichbar? |
GridLine(x0, y0, x1, y1, …) | Bresenham-Linie |
GridLineOfSight(…) | dieselbe Linie, aber an der ersten Sperre abgebrochen |
GridNeighbors(cells, x, y, w, h, diag, outX, outY) | begehbare Nachbarn, Anzahl als Rückgabe |
GridAStar braucht maxLen und schreibt nicht darüber hinaus. Der Rückgabewert ist die tatsächliche Länge; passt der Weg nicht, ist das erkennbar.
Ein Ziel oder viele?GridAStarsucht einen Weg zu einem Ziel und ist dafür am schnellsten, weil die Heuristik die Suche lenkt. Wer die Entfernung zu allen Feldern braucht — Einflusskarten, Reichweiten, „wie weit komme ich in 5 Zügen„ —, nimmtGridBfsoderGridDijkstraund wertet die Karte danach beliebig oft aus.
Abgrenzung
std.grid rechnet auf Gittern. Straßennetze, Abhängigkeitsgraphen und Ablaufpläne sind keine Gitter — dafür ist std.graph da, das Dijkstra und Bellman-Ford auf allgemeinen Graphen bietet. Umgekehrt hat std.graph kein A*; die zielgerichtete Suche gibt es nur hier.
Jump-Point-Search als Aufsatz auf GridAStar gibt es nicht — er lohnt ohnehin nur bei gleichteuren Zellen.
Letzte Aktualisierung: 2026-08-16 · alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt; die Heuristik-Zulässigkeit einzeln nachgemessen, der A*-Weg Feld für Feld gegen die Gitterbelegung geprüft.
