moonpath

A MoonBit graph algorithms and tile-grid pathfinding library for reusable open-source ecosystem building.

graph
pathfinding
astar
dijkstra
bellman-ford
grid
benchmark
moonbit
moon add wzx2007/moonpath@0.31.0
Download zip
Author
Version
0.31.0
License
Apache-2.0
Last updated
last month
Downloads
8
README

#MoonPath

MoonPath is a MoonBit graph algorithms and pathfinding library prepared for the MoonBit open-source ecosystem contribution contest.

///|
test "shortest path example" {
let graph = @moonpath.Graph::new()
graph.add_edge("A", "B", 4)
graph.add_edge("A", "C", 1)
graph.add_edge("C", "B", 2)
graph.add_edge("B", "D", 1)
guard graph.bidirectional_dijkstra("A", "D") is Some(path) else {
fail("expected a path")
}
assert_eq(path.cost, 4)
assert_true(path.nodes == ["A", "C", "B", "D"])
assert_true(graph.path_cost(path.nodes) == Some(4))
}

///|
test "weighted graph summary example" {
let graph = @moonpath.Graph::new()
graph.add_edge("A", "B", 5)
graph.add_edge("B", "C", 2)
assert_eq(graph.total_edge_cost(), 7)
assert_true(graph.min_edge_cost() == Some(2))
assert_true(graph.max_edge_cost() == Some(5))
}

///|
test "directed graph structure example" {
let graph = @moonpath.Graph::new()
graph.add_edge("parse", "compile", 1)
graph.add_edge("compile", "package", 1)
graph.add_node("docs")
assert_true(graph.sources().length() == 2)
assert_true(graph.sinks().length() == 2)
assert_true(graph.isolated_nodes() == ["docs"])
}

///|
test "grid resize example" {
let grid = @moonpath.Grid::new(4, 4)
grid.block(@moonpath.Point::new(1, 1))
grid.set_cost(@moonpath.Point::new(2, 2), 7)
let smaller = grid.resized(3, 3)
assert_true(smaller.is_blocked(@moonpath.Point::new(1, 1)))
assert_true(smaller.terrain_cost(@moonpath.Point::new(2, 2)) == Some(7))
}

///|
test "obstacle inflation example" {
let grid = @moonpath.Grid::new(5, 5)
grid.block(@moonpath.Point::new(2, 2))
let inflated = grid.inflated_blocks(1)
assert_eq(inflated.blocked_points().length(), 9)
assert_true(inflated.is_blocked(@moonpath.Point::new(1, 1)))
assert_true(!inflated.is_blocked(@moonpath.Point::new(0, 0)))
}

///|
test "grid region example" {
let grid = @moonpath.Grid::new(2, 2)
grid.block(@moonpath.Point::new(1, 0))
grid.block(@moonpath.Point::new(0, 1))
assert_eq(grid.reachable_points4(@moonpath.Point::new(0, 0)).length(), 1)
assert_eq(grid.reachable_points8(@moonpath.Point::new(0, 0)).length(), 2)
assert_eq(grid.component_count4(), 2)
assert_eq(grid.component_count8(), 1)
assert_true(!grid.is_fully_connected4())
assert_true(grid.is_fully_connected8())
}

///|
test "dynamic graph update example" {
let graph = @moonpath.Graph::new()
graph.add_edge("A", "B", 1)
graph.add_edge("A", "C", 2)
assert_eq(graph.remove_edge("A", "B"), 1)
assert_true(!graph.contains_edge("A", "B"))
assert_true(graph.remove_node("C"))
}

///|
test "safe graph build example" {
let arcs = [
@moonpath.Arc::{ from: "A", to: "B", cost: 2 },
@moonpath.Arc::{ from: "B", to: "C", cost: 3 },
]
assert_true(@moonpath.Graph::try_from_arcs(arcs) is Some(_))
}

///|
test "grid path validation example" {
let grid = @moonpath.Grid::new(3, 3)
guard grid.dijkstra4(@moonpath.Point::new(0, 0), @moonpath.Point::new(2, 2))
is Some(path) else {
fail("expected a path")
}
assert_eq(path.edge_count(), path.nodes.length() - 1)
assert_true(grid.path_valid4(path.nodes))
assert_true(grid.path_cost4(path.nodes) == Some(path.cost))
}

///|
test "path smoothing example" {
let grid = @moonpath.Grid::new(5, 5)
let start = @moonpath.Point::new(0, 0)
let goal = @moonpath.Point::new(4, 4)
let jagged = [
start,
@moonpath.Point::new(1, 0),
@moonpath.Point::new(4, 3),
goal,
]
assert_true(grid.line_of_sight(start, goal))
assert_true(grid.smooth_path(jagged) == [start, goal])
}

///|
test "edge list workflow example" {
let graph = @moonpath.Graph::new()
graph.add_edge("A", "B", 2)
graph.add_edge("B", "C", 3)
let rebuilt = @moonpath.Graph::from_arcs(graph.arcs())
assert_true(rebuilt.path_cost(["A", "B", "C"]) == Some(5))
let subgraph = graph.induced_subgraph(node => node != "C")
assert_true(!subgraph.contains_node("C"))
}

///|
test "bfs tree example" {
let graph = @moonpath.Graph::new()
graph.add_edge("A", "B", 1)
graph.add_edge("A", "C", 2)
graph.add_edge("B", "D", 3)
let tree = graph.bfs_tree("A")
assert_eq(tree.length(), 3)
}

///|
test "reachable subgraph example" {
let graph = @moonpath.Graph::new()
graph.add_edge("A", "B", 1)
graph.add_edge("B", "C", 2)
graph.add_edge("X", "Y", 3)
let subgraph = graph.reachable_subgraph("A")
assert_eq(subgraph.node_count(), 3)
assert_true(!subgraph.contains_node("X"))
}

///|
test "all pairs summary example" {
let graph = @moonpath.Graph::new()
graph.add_edge("A", "B", 2)
graph.add_edge("B", "C", 3)
let all = graph.all_pairs_distances()
guard all.get("A") is Some(from_a) else { fail("missing A") }
assert_eq(from_a.get_or_default("C", -1), 5)
assert_true(graph.has_path("A", "C"))
assert_true(graph.diameter() == Some(5))
}

///|
test "shortest path tree example" {
let graph = @moonpath.Graph::new()
graph.add_edge("A", "B", 5)
graph.add_edge("A", "C", 2)
graph.add_edge("C", "B", 1)
let tree = graph.shortest_path_tree("A")
assert_eq(tree.length(), 2)
}

///|
test "dependency direction example" {
let graph = @moonpath.Graph::new()
graph.add_edge("parse", "compile", 1)
graph.add_edge("compile", "package", 1)
assert_true(graph.descendants("parse") == ["compile", "package"])
assert_true(graph.ancestors("package").length() == 2)
}

///|
test "distance and dag layer example" {
let graph = @moonpath.Graph::new()
graph.add_edge("parse", "lint", 1)
graph.add_edge("parse", "compile", 2)
graph.add_edge("compile", "package", 3)
let distances = graph.distances_from("parse")
assert_eq(distances.get_or_default("package", -1), 5)
assert_true(graph.topological_layers() is Some(_))
}

///|
test "dag longest path example" {
let graph = @moonpath.Graph::new()
graph.add_edge("start", "A", 2)
graph.add_edge("start", "B", 1)
graph.add_edge("A", "done", 3)
graph.add_edge("B", "done", 8)
guard graph.dag_longest_path("start", "done") is Some(path) else {
fail("expected a critical path")
}
assert_eq(path.cost, 9)
assert_true(path.nodes == ["start", "B", "done"])
}

///|
test "dag path enumeration example" {
let graph = @moonpath.Graph::new()
graph.add_edge("start", "lint", 1)
graph.add_edge("start", "compile", 2)
graph.add_edge("lint", "package", 3)
graph.add_edge("compile", "package", 4)
assert_eq(graph.dag_paths("start", "package").length(), 2)
}

///|
test "bulk grid example" {
let grid = @moonpath.Grid::new(5, 4)
grid.block_rect(@moonpath.Point::new(1, 1), 3, 2)
grid.unblock_rect(@moonpath.Point::new(2, 1), 1, 2)
grid.set_cost_rect(@moonpath.Point::new(0, 0), 2, 2, 4)
assert_true(grid.clear_cost(@moonpath.Point::new(1, 1)))
assert_eq(grid.clear_cost_rect(@moonpath.Point::new(0, 0), 2, 1), 2)
let rebuilt = @moonpath.Grid::from_parts(
5,
4,
grid.blocked_points(),
grid.terrain_cells(),
)
assert_true(rebuilt.terrain_cost(@moonpath.Point::new(1, 1)) == Some(4))
}

///|
test "grid terrain example" {
let grid = @moonpath.Grid::new(3, 3)
grid.set_cost(@moonpath.Point::new(1, 0), 5)
guard grid.dijkstra8(@moonpath.Point::new(0, 0), @moonpath.Point::new(2, 2))
is Some(path) else {
fail("expected a path")
}
assert_eq(path.cost, 28)
}

///|
test "serialization example" {
let graph = @moonpath.Graph::new()
graph.add_node("isolated")
graph.add_edge("A", "B", 2)
assert_true(
@moonpath.Graph::from_json_string(graph.to_json_string()) is Some(_),
)
assert_true(@moonpath.Graph::from_text(graph.to_text()) is Some(_))
}

///|
test "bellman ford example" {
let arcs = [
@moonpath.Arc::{ from: "S", to: "A", cost: 4 },
@moonpath.Arc::{ from: "A", to: "T", cost: -2 },
]
guard @moonpath.bellman_ford_path(["S", "A", "T"], arcs, "S", "T")
is Some(path) else {
fail("expected a path")
}
assert_eq(path.cost, 2)
}

///|
test "no corner cutting example" {
let grid = @moonpath.Grid::new(2, 2)
grid.block(@moonpath.Point::new(1, 0))
grid.block(@moonpath.Point::new(0, 1))
assert_true(
grid.astar8_no_corner_cutting(
@moonpath.Point::new(0, 0),
@moonpath.Point::new(1, 1),
) ==
None,
)
}

See README.md and docs/ for installation, usage, contest materials, and the submission checklist.

#
Arc

pub(all) struct Arc[N] {
from : N
to : N
cost : Int
} derive(Eq, ToJson,
Debug
,
FromJson
)

A full directed edge record with source, target, and cost.

#
CellCost

pub(all) struct CellCost {
point : Point
cost : Int
} derive(Eq, ToJson,
Debug
,
FromJson
)

A terrain override for one grid cell.

#
Edge

pub(all) struct Edge[N] {
to : N
cost : Int
} derive(Eq, ToJson,
Debug
,
FromJson
)

A weighted directed edge.

#
Graph

pub(all) struct Graph[N] {
adjacency :
HashMap
[N, Array[Edge[N]]]
}

A mutable directed weighted graph backed by hash maps.

#
Graph::add_edge

fn[N : Hash + Eq] Graph::add_edge(self : Graph[N], from : N, to : N, cost : Int) -> Unit

#
Graph::add_node

fn[N : Hash + Eq] Graph::add_node(self : Graph[N], node : N) -> Unit

#
Graph::add_undirected_edge

fn[N : Hash + Eq] Graph::add_undirected_edge(self : Graph[N], a : N, b : N, cost : Int) -> Unit

#
Graph::all_pairs_distances

#
Graph::ancestors

fn[N : Hash + Eq] Graph::ancestors(self : Graph[N], node : N) -> Array[N]

#
Graph::arcs

fn[N] Graph::arcs(self : Graph[N]) -> Array[Arc[N]]

#
Graph::astar

fn[N : Hash + Eq] Graph::astar(self : Graph[N], start : N, goal : N, heuristic : (N, N) -> Int) -> Path[N]?

#
Graph::bfs

fn[N : Hash + Eq] Graph::bfs(self : Graph[N], start : N, goal : N) -> Path[N]?

#
Graph::bfs_tree

fn[N : Hash + Eq] Graph::bfs_tree(self : Graph[N], start : N) -> Array[Arc[N]]

#
Graph::bidirectional_astar

fn[N : Hash + Eq] Graph::bidirectional_astar(self : Graph[N], start : N, goal : N, heuristic : (N, N) -> Int) -> Path[N]?

#
Graph::bidirectional_dijkstra

fn[N : Hash + Eq] Graph::bidirectional_dijkstra(self : Graph[N], start : N, goal : N) -> Path[N]?

#
Graph::clear_edges_from

fn[N : Hash + Eq] Graph::clear_edges_from(self : Graph[N], node : N) -> Int

#
Graph::connected_components

fn[N : Hash + Eq] Graph::connected_components(self : Graph[N]) -> Array[Array[N]]

#
Graph::contains_edge

fn[N : Hash + Eq] Graph::contains_edge(self : Graph[N], from : N, to : N) -> Bool

#
Graph::contains_node

fn[N : Hash + Eq] Graph::contains_node(self : Graph[N], node : N) -> Bool

#
Graph::dag_longest_path

fn[N : Hash + Eq] Graph::dag_longest_path(self : Graph[N], start : N, goal : N) -> Path[N]?

#
Graph::dag_paths

fn[N : Hash + Eq] Graph::dag_paths(self : Graph[N], start : N, goal : N) -> Array[Path[N]]

#
Graph::descendants

fn[N : Hash + Eq] Graph::descendants(self : Graph[N], node : N) -> Array[N]

#
Graph::dfs_postorder

fn[N : Hash + Eq] Graph::dfs_postorder(self : Graph[N], start : N) -> Array[N]

#
Graph::dfs_preorder

fn[N : Hash + Eq] Graph::dfs_preorder(self : Graph[N], start : N) -> Array[N]

#
Graph::diameter

fn[N : Hash + Eq] Graph::diameter(self : Graph[N]) -> Int?

#
Graph::dijkstra

fn[N : Hash + Eq] Graph::dijkstra(self : Graph[N], start : N, goal : N) -> Path[N]?

#
Graph::distances_from

fn[N : Hash + Eq] Graph::distances_from(self : Graph[N], start : N) ->
HashMap
[N, Int]

#
Graph::eccentricity

fn[N : Hash + Eq] Graph::eccentricity(self : Graph[N], start : N) -> Int?

#
Graph::edge_count

fn[N] Graph::edge_count(self : Graph[N]) -> Int

#
Graph::from_arcs

fn[N : Hash + Eq] Graph::from_arcs(arcs : Array[Arc[N]]) -> Graph[N]

#
Graph::from_json_string

fn Graph::from_json_string(text : String) -> Graph[String]?

#
Graph::from_text

fn Graph::from_text(text : String) -> Graph[String]?

#
Graph::has_path

fn[N : Hash + Eq] Graph::has_path(self : Graph[N], start : N, goal : N) -> Bool

#
Graph::in_degree

fn[N : Eq] Graph::in_degree(self : Graph[N], node : N) -> Int

#
Graph::induced_subgraph

fn[N : Hash + Eq] Graph::induced_subgraph(self : Graph[N], keep : (N) -> Bool) -> Graph[N]

#
Graph::is_acyclic

fn[N : Hash + Eq] Graph::is_acyclic(self : Graph[N]) -> Bool

#
Graph::is_strongly_connected

fn[N : Hash + Eq] Graph::is_strongly_connected(self : Graph[N]) -> Bool

#
Graph::is_weakly_connected

fn[N : Hash + Eq] Graph::is_weakly_connected(self : Graph[N]) -> Bool

#
Graph::isolated_nodes

fn[N : Hash + Eq] Graph::isolated_nodes(self : Graph[N]) -> Array[N]

#
Graph::max_edge_cost

fn[N] Graph::max_edge_cost(self : Graph[N]) -> Int?

#
Graph::min_edge_cost

fn[N] Graph::min_edge_cost(self : Graph[N]) -> Int?

#
Graph::neighbors

fn[N : Hash + Eq] Graph::neighbors(self : Graph[N], node : N) -> Array[Edge[N]]

#
Graph::new

fn[N : Hash + Eq] Graph::new() -> Graph[N]

#
Graph::node_count

fn[N] Graph::node_count(self : Graph[N]) -> Int

#
Graph::nodes

fn[N] Graph::nodes(self : Graph[N]) -> Array[N]

#
Graph::out_degree

fn[N : Hash + Eq] Graph::out_degree(self : Graph[N], node : N) -> Int

#
Graph::path_cost

fn[N : Hash + Eq] Graph::path_cost(self : Graph[N], nodes : Array[N]) -> Int?

#
Graph::reachable

fn[N : Hash + Eq] Graph::reachable(self : Graph[N], start : N) -> Array[N]

#
Graph::reachable_subgraph

fn[N : Hash + Eq] Graph::reachable_subgraph(self : Graph[N], start : N) -> Graph[N]

#
Graph::remove_edge

fn[N : Hash + Eq] Graph::remove_edge(self : Graph[N], from : N, to : N) -> Int

#
Graph::remove_node

fn[N : Hash + Eq] Graph::remove_node(self : Graph[N], node : N) -> Bool

#
Graph::shortest_distance

fn[N : Hash + Eq] Graph::shortest_distance(self : Graph[N], start : N, goal : N) -> Int?

#
Graph::shortest_path_tree

fn[N : Hash + Eq] Graph::shortest_path_tree(self : Graph[N], start : N) -> Array[Arc[N]]

#
Graph::sinks

fn[N : Hash + Eq] Graph::sinks(self : Graph[N]) -> Array[N]

#
Graph::sources

fn[N : Hash + Eq] Graph::sources(self : Graph[N]) -> Array[N]

#
Graph::strongly_connected_components

fn[N : Hash + Eq] Graph::strongly_connected_components(self : Graph[N]) -> Array[Array[N]]

#
Graph::to_json

fn Graph::to_json(self : Graph[String]) -> Json

#
Graph::to_json_string

fn Graph::to_json_string(self : Graph[String]) -> String

#
Graph::to_text

fn Graph::to_text(self : Graph[String]) -> String

#
Graph::topological_layers

fn[N : Hash + Eq] Graph::topological_layers(self : Graph[N]) -> Array[Array[N]]?

#
Graph::topological_sort

fn[N : Hash + Eq] Graph::topological_sort(self : Graph[N]) -> Array[N]?

#
Graph::total_edge_cost

fn[N] Graph::total_edge_cost(self : Graph[N]) -> Int

#
Graph::transpose

fn[N : Hash + Eq] Graph::transpose(self : Graph[N]) -> Graph[N]

#
Graph::try_from_arcs

fn[N : Hash + Eq] Graph::try_from_arcs(arcs : Array[Arc[N]]) -> Graph[N]?

#
Graph::weakly_connected_components

fn[N : Hash + Eq] Graph::weakly_connected_components(self : Graph[N]) -> Array[Array[N]]

#
Grid

pub(all) struct Grid {
width : Int
height : Int
blocked :
HashSet
[Point]
terrain :
HashMap
[Point, Int]
}

A rectangular grid with blocked cells.

#
Grid::astar4

fn Grid::astar4(self : Grid, start : Point, goal : Point) -> Path[Point]?

#
Grid::astar8

fn Grid::astar8(self : Grid, start : Point, goal : Point) -> Path[Point]?

#
Grid::astar8_no_corner_cutting

fn Grid::astar8_no_corner_cutting(self : Grid, start : Point, goal : Point) -> Path[Point]?

#
Grid::bidirectional_astar4

fn Grid::bidirectional_astar4(self : Grid, start : Point, goal : Point) -> Path[Point]?

#
Grid::bidirectional_astar8

fn Grid::bidirectional_astar8(self : Grid, start : Point, goal : Point) -> Path[Point]?

#
Grid::block

fn Grid::block(self : Grid, point : Point) -> Unit

#
Grid::block_rect

fn Grid::block_rect(self : Grid, top_left : Point, width : Int, height : Int) -> Unit

#
Grid::blocked_points

fn Grid::blocked_points(self : Grid) -> Array[Point]

#
Grid::clear_cost

fn Grid::clear_cost(self : Grid, point : Point) -> Bool

#
Grid::clear_cost_rect

fn Grid::clear_cost_rect(self : Grid, top_left : Point, width : Int, height : Int) -> Int

#
Grid::component_count4

fn Grid::component_count4(self : Grid) -> Int

#
Grid::component_count8

fn Grid::component_count8(self : Grid) -> Int

#
Grid::contains

fn Grid::contains(self : Grid, point : Point) -> Bool

#
Grid::dijkstra4

fn Grid::dijkstra4(self : Grid, start : Point, goal : Point) -> Path[Point]?

#
Grid::dijkstra8

fn Grid::dijkstra8(self : Grid, start : Point, goal : Point) -> Path[Point]?

#
Grid::from_blocked

fn Grid::from_blocked(width : Int, height : Int, blocked : Array[Point]) -> Grid

#
Grid::from_json_string

fn Grid::from_json_string(text : String) -> Grid?

#
Grid::from_parts

fn Grid::from_parts(width : Int, height : Int, blocked : Array[Point], terrain : Array[CellCost]) -> Grid

#
Grid::from_text

fn Grid::from_text(text : String) -> Grid?

#
Grid::inflated_blocks

fn Grid::inflated_blocks(self : Grid, radius : Int) -> Grid

#
Grid::is_blocked

fn Grid::is_blocked(self : Grid, point : Point) -> Bool

#
Grid::is_fully_connected4

fn Grid::is_fully_connected4(self : Grid) -> Bool

#
Grid::is_fully_connected8

fn Grid::is_fully_connected8(self : Grid) -> Bool

#
Grid::is_open

fn Grid::is_open(self : Grid, point : Point) -> Bool

#
Grid::line_of_sight

fn Grid::line_of_sight(self : Grid, from : Point, to : Point) -> Bool

#
Grid::neighbors4

fn Grid::neighbors4(self : Grid, point : Point) -> Array[Edge[Point]]

#
Grid::neighbors8

fn Grid::neighbors8(self : Grid, point : Point) -> Array[Edge[Point]]

#
Grid::neighbors8_no_corner_cutting

fn Grid::neighbors8_no_corner_cutting(self : Grid, point : Point) -> Array[Edge[Point]]

#
Grid::new

fn Grid::new(width : Int, height : Int) -> Grid

#
Grid::open_points

fn Grid::open_points(self : Grid) -> Array[Point]

#
Grid::open_regions4

fn Grid::open_regions4(self : Grid) -> Array[Array[Point]]

#
Grid::open_regions8

fn Grid::open_regions8(self : Grid) -> Array[Array[Point]]

#
Grid::path_cost4

fn Grid::path_cost4(self : Grid, nodes : Array[Point]) -> Int?

#
Grid::path_cost8

fn Grid::path_cost8(self : Grid, nodes : Array[Point]) -> Int?

#
Grid::path_valid4

fn Grid::path_valid4(self : Grid, nodes : Array[Point]) -> Bool

#
Grid::path_valid8

fn Grid::path_valid8(self : Grid, nodes : Array[Point]) -> Bool

#
Grid::points

fn Grid::points(self : Grid) -> Array[Point]

#
Grid::reachable_points4

fn Grid::reachable_points4(self : Grid, start : Point) -> Array[Point]

#
Grid::reachable_points8

fn Grid::reachable_points8(self : Grid, start : Point) -> Array[Point]

#
Grid::resized

fn Grid::resized(self : Grid, width : Int, height : Int) -> Grid

#
Grid::set_cost

fn Grid::set_cost(self : Grid, point : Point, cost : Int) -> Unit

#
Grid::set_cost_rect

fn Grid::set_cost_rect(self : Grid, top_left : Point, width : Int, height : Int, cost : Int) -> Unit

#
Grid::smooth_path

fn Grid::smooth_path(self : Grid, nodes : Array[Point]) -> Array[Point]

#
Grid::terrain_cells

fn Grid::terrain_cells(self : Grid) -> Array[CellCost]

#
Grid::terrain_cost

fn Grid::terrain_cost(self : Grid, point : Point) -> Int?

#
Grid::to_graph4

fn Grid::to_graph4(self : Grid) -> Graph[Point]

#
Grid::to_graph8

fn Grid::to_graph8(self : Grid) -> Graph[Point]

#
Grid::to_json

fn Grid::to_json(self : Grid) -> Json

#
Grid::to_json_string

fn Grid::to_json_string(self : Grid) -> String

#
Grid::to_text

fn Grid::to_text(self : Grid) -> String

#
Grid::unblock

fn Grid::unblock(self : Grid, point : Point) -> Unit

#
Grid::unblock_rect

fn Grid::unblock_rect(self : Grid, top_left : Point, width : Int, height : Int) -> Unit

#
Path

pub(all) struct Path[N] {
cost : Int
nodes : Array[N]
visited : Int
} derive(Eq, ToJson,
Debug
,
FromJson
)

A successful shortest-path query.

#
Path::edge_count

fn[N] Path::edge_count(self : Path[N]) -> Int

#
Path::node_count

fn[N] Path::node_count(self : Path[N]) -> Int

#
Point

pub(all) struct Point {
x : Int
y : Int
} derive(Eq, Hash, ToJson,
Debug
,
FromJson
)

A grid point for tile-based pathfinding.

#
Point::new

fn Point::new(x : Int, y : Int) -> Point

#
Point::offset

fn Point::offset(self : Point, dx : Int, dy : Int) -> Point

#
bellman_ford_distances

fn[N : Hash + Eq] bellman_ford_distances(nodes : Array[N], arcs : Array[Arc[N]], start : N) ->
HashMap
[N, Int]?

#
bellman_ford_path

fn[N : Hash + Eq] bellman_ford_path(nodes : Array[N], arcs : Array[Arc[N]], start : N, goal : N) -> Path[N]?

#
manhattan

fn manhattan(a : Point, b : Point) -> Int

#
octile

fn octile(a : Point, b : Point) -> Int

#
shortest_path

fn[N : Hash + Eq] shortest_path(start : N, goal : N, neighbors : (N) -> Array[Edge[N]], heuristic : (N, N) -> Int) -> Path[N]?