@shipload/sdk 1.0.0-next.36 → 1.0.0-next.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,183 @@
1
+ import {distanceBetweenPoints, findNearbyPlanets} from './travel'
2
+ import {hasSystem} from '../utils/system'
3
+ import {PRECISION} from '../types'
4
+ import {Checksum256, type Checksum256Type} from '@wharfkit/antelope'
5
+
6
+ export interface Coord {
7
+ x: number
8
+ y: number
9
+ }
10
+
11
+ export interface Neighbor {
12
+ coord: Coord
13
+ dist: number
14
+ }
15
+
16
+ export interface SystemGraph {
17
+ hasSystem(c: Coord): boolean
18
+ nearby(c: Coord, reachTiles: number): Neighbor[]
19
+ }
20
+
21
+ export interface RoutePlan {
22
+ ok: true
23
+ waypoints: Coord[]
24
+ legs: number
25
+ totalDistance: number
26
+ }
27
+
28
+ export type RouteFailureReason = 'empty-destination' | 'no-path' | 'max-legs'
29
+
30
+ export interface RouteFailure {
31
+ ok: false
32
+ reason: RouteFailureReason
33
+ furthest?: Coord
34
+ legsNeeded?: number
35
+ partialWaypoints?: Coord[]
36
+ }
37
+
38
+ export type RouteResult = RoutePlan | RouteFailure
39
+
40
+ export interface PlanRouteParams {
41
+ origin: Coord
42
+ dest: Coord
43
+ perLegReach: number
44
+ graph: SystemGraph
45
+ corridorSlack?: number
46
+ nodeBudget?: number
47
+ maxLegs?: number
48
+ }
49
+
50
+ const key = (c: Coord): string => `${c.x},${c.y}`
51
+ const sameCoord = (a: Coord, b: Coord): boolean => a.x === b.x && a.y === b.y
52
+ const dist = (a: Coord, b: Coord): number => Math.hypot(a.x - b.x, a.y - b.y)
53
+
54
+ export const MAX_LEGS = 12
55
+
56
+ export function planRoute(params: PlanRouteParams): RouteResult {
57
+ const {origin, dest, perLegReach, graph} = params
58
+ const corridorSlack = params.corridorSlack ?? perLegReach
59
+ const nodeBudget = params.nodeBudget ?? 5000
60
+ const maxLegs = params.maxLegs ?? MAX_LEGS
61
+
62
+ if (!graph.hasSystem(dest)) {
63
+ return {ok: false, reason: 'empty-destination'}
64
+ }
65
+
66
+ const straightLine = dist(origin, dest)
67
+ const heuristic = (c: Coord): number => Math.ceil(dist(c, dest) / perLegReach)
68
+
69
+ const gScore = new Map<string, number>([[key(origin), 0]])
70
+ const cameFrom = new Map<string, Coord>()
71
+ const frontier: {coord: Coord; g: number; f: number; remaining: number}[] = [
72
+ {coord: origin, g: 0, f: heuristic(origin), remaining: straightLine},
73
+ ]
74
+
75
+ let furthest = origin
76
+ let furthestRemaining = dist(origin, dest)
77
+ let expansions = 0
78
+ let cappedByMaxLegs = false
79
+
80
+ while (frontier.length > 0) {
81
+ let bestIdx = 0
82
+ for (let i = 1; i < frontier.length; i++) {
83
+ const a = frontier[i]
84
+ const b = frontier[bestIdx]
85
+ if (a.f < b.f || (a.f === b.f && a.remaining < b.remaining)) {
86
+ bestIdx = i
87
+ }
88
+ }
89
+ const current = frontier.splice(bestIdx, 1)[0]
90
+
91
+ if (sameCoord(current.coord, dest)) {
92
+ return reconstruct(cameFrom, origin, dest)
93
+ }
94
+
95
+ if (current.remaining < furthestRemaining) {
96
+ furthestRemaining = current.remaining
97
+ furthest = current.coord
98
+ }
99
+
100
+ if (++expansions > nodeBudget) break
101
+
102
+ for (const n of graph.nearby(current.coord, perLegReach)) {
103
+ const inCorridor =
104
+ dist(origin, n.coord) + dist(n.coord, dest) <= straightLine + corridorSlack
105
+ if (!inCorridor) continue
106
+
107
+ const tentativeG = current.g + 1
108
+ if (tentativeG > maxLegs) {
109
+ cappedByMaxLegs = true
110
+ continue
111
+ }
112
+ const nk = key(n.coord)
113
+ if (tentativeG < (gScore.get(nk) ?? Infinity)) {
114
+ gScore.set(nk, tentativeG)
115
+ cameFrom.set(nk, current.coord)
116
+ const remaining = dist(n.coord, dest)
117
+ frontier.push({
118
+ coord: n.coord,
119
+ g: tentativeG,
120
+ f: tentativeG + Math.ceil(remaining / perLegReach),
121
+ remaining,
122
+ })
123
+ }
124
+ }
125
+ }
126
+
127
+ if (cappedByMaxLegs) {
128
+ return {
129
+ ok: false,
130
+ reason: 'max-legs',
131
+ furthest,
132
+ partialWaypoints: reconstructWaypoints(cameFrom, origin, furthest),
133
+ }
134
+ }
135
+ return {
136
+ ok: false,
137
+ reason: 'no-path',
138
+ furthest,
139
+ partialWaypoints: reconstructWaypoints(cameFrom, origin, furthest),
140
+ }
141
+ }
142
+
143
+ function reconstructWaypoints(cameFrom: Map<string, Coord>, origin: Coord, target: Coord): Coord[] {
144
+ if (sameCoord(target, origin)) return []
145
+ const path: Coord[] = [target]
146
+ let cur = target
147
+ while (!sameCoord(cur, origin)) {
148
+ const prev = cameFrom.get(key(cur))
149
+ if (!prev) break
150
+ path.unshift(prev)
151
+ cur = prev
152
+ }
153
+ return path.slice(1)
154
+ }
155
+
156
+ function reconstruct(cameFrom: Map<string, Coord>, origin: Coord, dest: Coord): RoutePlan {
157
+ const path: Coord[] = [dest]
158
+ let cur = dest
159
+ let totalDistance = 0
160
+ while (!sameCoord(cur, origin)) {
161
+ const prev = cameFrom.get(key(cur))
162
+ if (!prev) break
163
+ totalDistance += dist(prev, cur)
164
+ path.unshift(prev)
165
+ cur = prev
166
+ }
167
+ const waypoints = path.slice(1)
168
+ return {ok: true, waypoints, legs: waypoints.length, totalDistance}
169
+ }
170
+
171
+ export function sdkSystemGraph(seed: Checksum256Type): SystemGraph {
172
+ const s = Checksum256.from(seed)
173
+ return {
174
+ hasSystem: (c) => hasSystem(s, {x: c.x, y: c.y}),
175
+ nearby: (c, reachTiles) =>
176
+ findNearbyPlanets(s, {x: c.x, y: c.y}, reachTiles * PRECISION)
177
+ .map((d) => ({
178
+ coord: {x: Number(d.destination.x), y: Number(d.destination.y)},
179
+ dist: Number(d.distance) / PRECISION,
180
+ }))
181
+ .filter((n) => !(n.coord.x === c.x && n.coord.y === c.y)),
182
+ }
183
+ }