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? GridAStar sucht 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„ —, nimmt GridBfs oder GridDijkstra und 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.