PRUNplanner doesn't model travel, so nothing in the project could answer "how far is planet X" until now. Builds a graph from /systemstars (698 systems, real jump connections + 3D coordinates) and runs Dijkstra weighted by straight-line ly (what FTL fuel/time actually scale with), reporting jump count alongside. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
75 lines
2.8 KiB
Python
75 lines
2.8 KiB
Python
"""Jump-graph routing between systems/planets, built from FIO's /systemstars (698 systems,
|
|
3D coordinates + jump connections). Not in PRUNplanner's own source (it doesn't model travel) -
|
|
this is the project's own addition. Edge weight = straight-line distance between connected
|
|
systems (ly); PrUn's own FTL fuel/time scale with this, not raw jump count, so Dijkstra on
|
|
distance gives the right "cheapest" route, with jump count reported alongside for reference."""
|
|
import heapq
|
|
import math
|
|
from . import fio
|
|
|
|
|
|
def _dist(a, b):
|
|
return math.dist((a["PositionX"], a["PositionY"], a["PositionZ"]),
|
|
(b["PositionX"], b["PositionY"], b["PositionZ"]))
|
|
|
|
|
|
def load_graph():
|
|
"""SystemId -> {name, natural_id, pos, neighbors: {SystemId: distance_ly}}"""
|
|
systems = {s["SystemId"]: s for s in fio.systems()}
|
|
g = {}
|
|
for sid, s in systems.items():
|
|
neighbors = {}
|
|
for c in s.get("Connections", []):
|
|
other = systems.get(c["ConnectingId"])
|
|
if other:
|
|
neighbors[c["ConnectingId"]] = _dist(s, other)
|
|
g[sid] = {"name": s["Name"], "natural_id": s["NaturalId"], "pos": s, "neighbors": neighbors}
|
|
return g
|
|
|
|
|
|
def resolve_system(query: str) -> str:
|
|
"""Planet natural id (e.g. ZV-759c), system natural id (ZV-759), or a name -> SystemId."""
|
|
q = query.strip().upper()
|
|
for p in fio.planets_full():
|
|
if p.get("PlanetNaturalId", "").upper() == q or (p.get("PlanetName") or "").upper() == q:
|
|
return p["SystemId"]
|
|
for s in fio.systems():
|
|
if s["NaturalId"].upper() == q or s["Name"].upper() == q:
|
|
return s["SystemId"]
|
|
raise ValueError(f"no planet or system matches {query!r}")
|
|
|
|
|
|
def shortest_path(src_system_id: str, dst_system_id: str, graph=None):
|
|
"""Dijkstra by straight-line ly distance. Returns dict: jumps, distance_ly, path (list of system natural ids)."""
|
|
g = graph or load_graph()
|
|
if src_system_id not in g or dst_system_id not in g:
|
|
raise ValueError("unknown system id")
|
|
dist = {src_system_id: 0.0}
|
|
prev = {}
|
|
pq = [(0.0, src_system_id)]
|
|
seen = set()
|
|
while pq:
|
|
d, u = heapq.heappop(pq)
|
|
if u in seen:
|
|
continue
|
|
seen.add(u)
|
|
if u == dst_system_id:
|
|
break
|
|
for v, w in g[u]["neighbors"].items():
|
|
nd = d + w
|
|
if nd < dist.get(v, float("inf")):
|
|
dist[v] = nd
|
|
prev[v] = u
|
|
heapq.heappush(pq, (nd, v))
|
|
if dst_system_id not in dist:
|
|
return None
|
|
path = [dst_system_id]
|
|
while path[-1] != src_system_id:
|
|
path.append(prev[path[-1]])
|
|
path.reverse()
|
|
return {
|
|
"jumps": len(path) - 1,
|
|
"distance_ly": dist[dst_system_id],
|
|
"path": [g[sid]["natural_id"] for sid in path],
|
|
}
|