A* Pathfinding

github.com/mechanical-lich/mlge/path

Allocation-efficient A* pathfinding with pre-allocated internal data structures and reusable pathfinder instances.

The Pather Interface

Any node in your graph must implement the Pather interface:

type Pather interface {
    PathID() int
    PathNeighborsAppend(neighbors []Pather) []Pather
    PathNeighborCost(to Pather) float64
    PathEstimatedCost(to Pather) float64
}
Method Description
PathID Unique identifier for this node (used internally for lookups)
PathNeighborsAppend Appends reachable neighbors to the provided slice and returns it
PathNeighborCost Actual cost to move from this node to a neighbor
PathEstimatedCost Heuristic estimate of cost from this node to the target (e.g., Manhattan distance)

Quick Start

Use the convenience function with the default pathfinder:

import "github.com/mechanical-lich/mlge/path"

result, distance, found := path.Path(startNode, endNode)
if found {
    for _, node := range result {
        // Walk the path
    }
}

Custom Pathfinder

For better performance in hot paths, create a dedicated AStar instance with a pre-allocated node pool:

pathfinder := path.NewAStar(2048) // Pre-allocate for ~2048 nodes

result, distance, found := pathfinder.Path(from, to)

The AStar struct reuses memory across calls, reducing GC pressure in per-frame pathfinding.

Example: Grid-Based Pathfinding

type Tile struct {
    X, Y     int
    Passable bool
    Grid     *Grid
}

func (t *Tile) PathID() int {
    return t.Y*t.Grid.Width + t.X
}

func (t *Tile) PathNeighborsAppend(neighbors []path.Pather) []path.Pather {
    for _, dir := range [][2]int{{0, -1}, {0, 1}, {-1, 0}, {1, 0}} {
        nx, ny := t.X+dir[0], t.Y+dir[1]
        if neighbor := t.Grid.Get(nx, ny); neighbor != nil && neighbor.Passable {
            neighbors = append(neighbors, neighbor)
        }
    }
    return neighbors
}

func (t *Tile) PathNeighborCost(to path.Pather) float64 {
    return 1.0
}

func (t *Tile) PathEstimatedCost(to path.Pather) float64 {
    other := to.(*Tile)
    dx := math.Abs(float64(t.X - other.X))
    dy := math.Abs(float64(t.Y - other.Y))
    return dx + dy // Manhattan distance
}

Default Instance

A shared AStar instance is available as path.Default, pre-allocated with 1024 nodes. The path.Path() convenience function uses this instance.

var Default *AStar = NewAStar(1024)

Back to top

Copyright © 2026. Distributed under the MIT License.

This site uses Just the Docs, a documentation theme for Jekyll.