@myclaw163/clawclaw-cli 0.6.71 → 0.6.74
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.
- package/README.md +377 -427
- package/bin/clawclaw-cli.mjs +3 -3
- package/package.json +48 -48
- package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -23
- package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -22
- package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -22
- package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -22
- package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -23
- package/scripts/check-skill-command-surface.mjs +116 -0
- package/scripts/find-hide-spots.py +157 -157
- package/scripts/postinstall.mjs +20 -20
- package/scripts/sync-bundled-skill.mjs +245 -245
- package/scripts/sync-bundled-skill.test.mjs +152 -152
- package/skills/clawclaw/SKILL.md +246 -244
- package/skills/clawclaw/references/CHATTERBOX.md +141 -142
- package/skills/clawclaw/references/COMMANDS.md +155 -148
- package/skills/clawclaw/references/GAME-MECHANICS.md +188 -188
- package/skills/clawclaw/references/HUB.md +48 -48
- package/skills/clawclaw/references/KNOWLEDGE.md +42 -45
- package/skills/clawclaw/references/STRATEGIES.md +59 -59
- package/skills/clawclaw/references/STREAM.md +93 -91
- package/skills/clawclaw/references/TACTICS.md +65 -65
- package/src/assets/clawclaw-ascii-map.txt +40 -40
- package/src/cli.ts +110 -110
- package/src/commands/_schema.ts +124 -109
- package/src/commands/account.ts +209 -209
- package/src/commands/do.test.ts +84 -73
- package/src/commands/do.ts +130 -126
- package/src/commands/events.test.ts +71 -71
- package/src/commands/events.ts +221 -155
- package/src/commands/game-map.test.ts +28 -28
- package/src/commands/game-start-plan.test.ts +84 -84
- package/src/commands/game.ts +1113 -1047
- package/src/commands/history-player.test.ts +102 -102
- package/src/commands/history.ts +573 -573
- package/src/commands/hub.test.ts +96 -96
- package/src/commands/hub.ts +234 -234
- package/src/commands/knowledge.test.ts +13 -13
- package/src/commands/knowledge.ts +139 -139
- package/src/commands/load.test.ts +51 -51
- package/src/commands/load.ts +13 -13
- package/src/commands/meeting-history.test.ts +106 -106
- package/src/commands/memory.ts +40 -40
- package/src/commands/peek.ts +45 -45
- package/src/commands/persona.ts +57 -57
- package/src/commands/setup/codex.ts +266 -266
- package/src/commands/skill.ts +128 -128
- package/src/commands/state.ts +46 -46
- package/src/commands/strategy.test.ts +145 -145
- package/src/commands/strategy.ts +181 -181
- package/src/commands/tts.ts +128 -128
- package/src/commands/upgrade.test.ts +82 -82
- package/src/commands/upgrade.ts +148 -148
- package/src/commands/watch.test.ts +999 -977
- package/src/commands/watch.ts +660 -658
- package/src/lib/auth.test.ts +74 -74
- package/src/lib/auth.ts +186 -186
- package/src/lib/command-meta.ts +37 -37
- package/src/lib/game-client.ts +403 -391
- package/src/lib/game-context.ts +92 -0
- package/src/lib/http-keepalive.ts +15 -15
- package/src/lib/http-transport.test.ts +42 -42
- package/src/lib/http-transport.ts +113 -113
- package/src/lib/hub-client.test.ts +56 -56
- package/src/lib/hub-client.ts +88 -88
- package/src/lib/hub-install.test.ts +98 -98
- package/src/lib/hub-install.ts +121 -121
- package/src/lib/hub-reminder.ts +75 -75
- package/src/lib/hub-unzip.test.ts +69 -69
- package/src/lib/hub-unzip.ts +62 -62
- package/src/lib/init-command.test.ts +75 -75
- package/src/lib/init-command.ts +120 -120
- package/src/lib/knowledge-store.test.ts +170 -170
- package/src/lib/knowledge-store.ts +369 -369
- package/src/lib/load-context.test.ts +52 -52
- package/src/lib/load-context.ts +52 -52
- package/src/lib/match-state.test.ts +134 -134
- package/src/lib/match-state.ts +94 -94
- package/src/lib/netease-tts.ts +83 -83
- package/src/lib/normalize.ts +42 -42
- package/src/lib/persona.test.ts +41 -41
- package/src/lib/persona.ts +72 -72
- package/src/lib/server-registry.ts +152 -152
- package/src/lib/skill-version.test.ts +48 -48
- package/src/lib/skill-version.ts +19 -19
- package/src/lib/strategy-export.test.ts +232 -232
- package/src/lib/strategy-export.ts +242 -242
- package/src/lib/tts-keys.ts +7 -7
- package/src/lib/tts-speech.test.ts +63 -63
- package/src/lib/tts-speech.ts +76 -76
- package/src/lib/workspace-argv.test.ts +49 -49
- package/src/lib/workspace-argv.ts +44 -44
- package/src/perception/player-history-store.test.ts +87 -87
- package/src/perception/player-history-store.ts +194 -194
- package/src/pipeline/event-format.test.ts +243 -215
- package/src/pipeline/event-format.ts +501 -485
- package/src/pipeline/event-hints.ts +195 -190
- package/src/pipeline/event-store.test.ts +28 -28
- package/src/pipeline/event-store.ts +193 -193
- package/src/pipeline/pipeline.ts +35 -35
- package/src/pipeline/player-projection.test.ts +119 -0
- package/src/pipeline/player-projection.ts +380 -0
- package/src/runtime/auto-upgrade.test.ts +66 -66
- package/src/runtime/auto-upgrade.ts +31 -31
- package/src/runtime/event-daemon.test.ts +209 -141
- package/src/runtime/event-daemon.ts +519 -457
- package/src/runtime/owner-control.ts +150 -150
- package/src/runtime/raw-ws-log.test.ts +33 -33
- package/src/runtime/raw-ws-log.ts +32 -32
- package/src/runtime/runtime-logger.ts +107 -107
- package/src/runtime/ws-client.test.ts +125 -104
- package/src/runtime/ws-client.ts +287 -272
- package/src/sdk/action.ts +166 -166
- package/src/sdk/index.ts +110 -110
- package/src/sdk/types.ts +161 -161
- package/src/strategies/avoid-lone.ts +12 -12
- package/src/strategies/avoid-players.knowledge.md +19 -19
- package/src/strategies/avoid-players.ts +16 -16
- package/src/strategies/corpse-patrol.ts +23 -23
- package/src/strategies/crab-sabotage.ts +22 -22
- package/src/strategies/custom-module.test.ts +270 -270
- package/src/strategies/find-player.ts +17 -17
- package/src/strategies/game-utils.test.ts +242 -242
- package/src/strategies/game-utils.ts +846 -846
- package/src/strategies/goals/anchor-linger.ts +77 -77
- package/src/strategies/goals/avoid-lone-top.ts +168 -168
- package/src/strategies/goals/avoid-players-top.test.ts +83 -83
- package/src/strategies/goals/avoid-players-top.ts +121 -121
- package/src/strategies/goals/conversation-goal.ts +51 -51
- package/src/strategies/goals/corpse-patrol-top.ts +113 -113
- package/src/strategies/goals/crab-octopus-reflexes.ts +101 -101
- package/src/strategies/goals/crab-sabotage-top.ts +197 -197
- package/src/strategies/goals/emergency-hunt-goal.ts +28 -28
- package/src/strategies/goals/find-player-top.ts +93 -93
- package/src/strategies/goals/flee-players-goal.ts +53 -53
- package/src/strategies/goals/follow-companion-goal.ts +106 -106
- package/src/strategies/goals/goal-manager.ts +41 -41
- package/src/strategies/goals/goal-root-strategy.ts +49 -49
- package/src/strategies/goals/goal.ts +28 -28
- package/src/strategies/goals/hide-top.ts +197 -197
- package/src/strategies/goals/keep-away-goal.ts +221 -221
- package/src/strategies/goals/kill-frenzy-top.ts +80 -80
- package/src/strategies/goals/kill-lone-top.ts +160 -160
- package/src/strategies/goals/kill-target-goal.ts +59 -59
- package/src/strategies/goals/kill-target-top.ts +109 -109
- package/src/strategies/goals/leaf-goal.ts +27 -27
- package/src/strategies/goals/linger-corpse-goal.ts +35 -35
- package/src/strategies/goals/lone-kill-core.ts +82 -82
- package/src/strategies/goals/lone-kill-goal.ts +24 -24
- package/src/strategies/goals/lone-kill-task-top.test.ts +85 -85
- package/src/strategies/goals/lone-kill-task-top.ts +133 -133
- package/src/strategies/goals/move-room-goal.ts +60 -60
- package/src/strategies/goals/normal-shrimp-top.test.ts +80 -80
- package/src/strategies/goals/normal-shrimp-top.ts +242 -242
- package/src/strategies/goals/paradise-fish-top.test.ts +126 -126
- package/src/strategies/goals/paradise-fish-top.ts +224 -224
- package/src/strategies/goals/patrol-top.ts +57 -57
- package/src/strategies/goals/report-patrol-top.ts +80 -80
- package/src/strategies/goals/safe-task-goal.ts +102 -102
- package/src/strategies/goals/social-task-top.ts +161 -161
- package/src/strategies/goals/task-kill-report-top.ts +163 -163
- package/src/strategies/goals/task-only-top.ts +57 -57
- package/src/strategies/goals/task-or-patrol-goal.ts +41 -41
- package/src/strategies/goals/task-report-top.ts +57 -57
- package/src/strategies/goals/wander-task-goal.ts +33 -33
- package/src/strategies/goals/warrior-shrimp-top.test.ts +87 -87
- package/src/strategies/goals/warrior-shrimp-top.ts +267 -267
- package/src/strategies/greeting.ts +53 -53
- package/src/strategies/hide-spots.ts +59 -59
- package/src/strategies/hide.ts +24 -24
- package/src/strategies/kill-frenzy.ts +13 -13
- package/src/strategies/kill-lone.knowledge.md +17 -17
- package/src/strategies/kill-lone.ts +14 -14
- package/src/strategies/kill-target.ts +19 -19
- package/src/strategies/loader.test.ts +678 -678
- package/src/strategies/loader.ts +179 -179
- package/src/strategies/lone-kill-task.ts +22 -22
- package/src/strategies/meeting-gate.test.ts +59 -59
- package/src/strategies/meeting-gate.ts +23 -23
- package/src/strategies/move-room.ts +16 -16
- package/src/strategies/new-events-backfill.ts +98 -98
- package/src/strategies/off-route-points.ts +105 -105
- package/src/strategies/paradise-fish.knowledge.md +19 -19
- package/src/strategies/paradise-fish.ts +26 -26
- package/src/strategies/pathfind/distance-field.ts +150 -150
- package/src/strategies/pathfind/escape-planner.test.ts +197 -197
- package/src/strategies/pathfind/escape-planner.ts +355 -355
- package/src/strategies/pathfind/walkable-grid.ts +117 -117
- package/src/strategies/patrol.ts +12 -12
- package/src/strategies/player-targets.ts +13 -13
- package/src/strategies/report-patrol.ts +12 -12
- package/src/strategies/shrimp-memory.knowledge.md +19 -19
- package/src/strategies/shrimp-memory.ts +26 -26
- package/src/strategies/social-task.test.ts +28 -28
- package/src/strategies/social-task.ts +50 -50
- package/src/strategies/spawn.ts +82 -82
- package/src/strategies/speech-module.ts +123 -123
- package/src/strategies/strategy-loop.test.ts +15 -0
- package/src/strategies/strategy-loop.ts +776 -771
- package/src/strategies/task-kill-report.ts +18 -18
- package/src/strategies/task-only.ts +12 -12
- package/src/strategies/task-report.ts +23 -23
- package/src/strategies/types.ts +109 -109
- package/src/strategies/warrior-memory.knowledge.md +21 -21
- package/src/strategies/warrior-memory.ts +17 -17
|
@@ -1,150 +1,150 @@
|
|
|
1
|
-
import type { WalkableGrid } from './walkable-grid.js';
|
|
2
|
-
|
|
3
|
-
// Geodesic distance field over the walkable grid, integer octile costs:
|
|
4
|
-
// straight 5, diagonal 7 (~1:1.4). One tile = 4px = 5 units.
|
|
5
|
-
export const COST_STRAIGHT = 5;
|
|
6
|
-
export const COST_DIAG = 7;
|
|
7
|
-
export const UNITS_PER_PX = COST_STRAIGHT / 4;
|
|
8
|
-
export const PX_PER_UNIT = 4 / COST_STRAIGHT;
|
|
9
|
-
|
|
10
|
-
export interface FieldResult {
|
|
11
|
-
/** Geodesic cost in units from the source; Infinity if unreached. Valid until the next compute(). */
|
|
12
|
-
distOf(cell: number): number;
|
|
13
|
-
/** Per angle bucket, the farthest settled cell with dist <= ringUnits; -1 for empty buckets. */
|
|
14
|
-
ringCandidates: Int32Array;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class FieldCalculator {
|
|
18
|
-
private readonly dist: Int32Array;
|
|
19
|
-
private readonly stamp: Int32Array;
|
|
20
|
-
private gen = 0;
|
|
21
|
-
private source = -1;
|
|
22
|
-
|
|
23
|
-
constructor(private readonly grid: WalkableGrid) {
|
|
24
|
-
this.dist = new Int32Array(grid.width * grid.height);
|
|
25
|
-
this.stamp = new Int32Array(grid.width * grid.height);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Dial's-bucket Dijkstra from sourceCell, settling cells up to maxCostUnits.
|
|
30
|
-
* Diagonal steps require both orthogonal neighbors walkable (no corner
|
|
31
|
-
* cutting, matching the server's supercover line checks).
|
|
32
|
-
*/
|
|
33
|
-
compute(sourceCell: number, maxCostUnits: number, ringUnits: number, bucketCount = 16): FieldResult {
|
|
34
|
-
const { grid, dist, stamp } = this;
|
|
35
|
-
const W = grid.width;
|
|
36
|
-
this.gen++;
|
|
37
|
-
this.source = sourceCell;
|
|
38
|
-
const gen = this.gen;
|
|
39
|
-
|
|
40
|
-
const ringCandidates = new Int32Array(bucketCount).fill(-1);
|
|
41
|
-
const ringDist = new Int32Array(bucketCount).fill(-1);
|
|
42
|
-
const sx = sourceCell % W;
|
|
43
|
-
const sy = Math.floor(sourceCell / W);
|
|
44
|
-
|
|
45
|
-
const buckets: (number[] | undefined)[] = new Array(maxCostUnits + COST_DIAG + 1);
|
|
46
|
-
dist[sourceCell] = 0;
|
|
47
|
-
stamp[sourceCell] = gen;
|
|
48
|
-
buckets[0] = [sourceCell];
|
|
49
|
-
|
|
50
|
-
for (let c = 0; c <= maxCostUnits; c++) {
|
|
51
|
-
const bucket = buckets[c];
|
|
52
|
-
if (!bucket) continue;
|
|
53
|
-
for (const cell of bucket) {
|
|
54
|
-
if (dist[cell] !== c || stamp[cell] !== gen) continue; // stale entry
|
|
55
|
-
const tx = cell % W;
|
|
56
|
-
const ty = Math.floor(cell / W);
|
|
57
|
-
if (c > 0 && c <= ringUnits) {
|
|
58
|
-
const b = Math.floor(((Math.atan2(ty - sy, tx - sx) + Math.PI) / (2 * Math.PI)) * bucketCount) % bucketCount;
|
|
59
|
-
if (c > ringDist[b]) {
|
|
60
|
-
ringDist[b] = c;
|
|
61
|
-
ringCandidates[b] = cell;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
const e = grid.isWalkable(tx + 1, ty);
|
|
65
|
-
const w = grid.isWalkable(tx - 1, ty);
|
|
66
|
-
const s = grid.isWalkable(tx, ty + 1);
|
|
67
|
-
const n = grid.isWalkable(tx, ty - 1);
|
|
68
|
-
this.relax(cell + 1, c + COST_STRAIGHT, e, buckets, gen);
|
|
69
|
-
this.relax(cell - 1, c + COST_STRAIGHT, w, buckets, gen);
|
|
70
|
-
this.relax(cell + W, c + COST_STRAIGHT, s, buckets, gen);
|
|
71
|
-
this.relax(cell - W, c + COST_STRAIGHT, n, buckets, gen);
|
|
72
|
-
this.relax(cell + W + 1, c + COST_DIAG, e && s && grid.isWalkable(tx + 1, ty + 1), buckets, gen);
|
|
73
|
-
this.relax(cell + W - 1, c + COST_DIAG, w && s && grid.isWalkable(tx - 1, ty + 1), buckets, gen);
|
|
74
|
-
this.relax(cell - W + 1, c + COST_DIAG, e && n && grid.isWalkable(tx + 1, ty - 1), buckets, gen);
|
|
75
|
-
this.relax(cell - W - 1, c + COST_DIAG, w && n && grid.isWalkable(tx - 1, ty - 1), buckets, gen);
|
|
76
|
-
}
|
|
77
|
-
buckets[c] = undefined;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const distOf = (cell: number) => (stamp[cell] === gen ? dist[cell] : Infinity);
|
|
81
|
-
return { distOf, ringCandidates };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private relax(cell: number, cost: number, walkable: boolean, buckets: (number[] | undefined)[], gen: number): void {
|
|
85
|
-
if (!walkable) return;
|
|
86
|
-
if (this.stamp[cell] === gen && this.dist[cell] <= cost) return;
|
|
87
|
-
this.dist[cell] = cost;
|
|
88
|
-
this.stamp[cell] = gen;
|
|
89
|
-
(buckets[cost] ??= []).push(cell);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Walk from fromCell down the latest field's gradient toward its source,
|
|
94
|
-
* stopping once >= budgetUnits is spent (the last step may overshoot).
|
|
95
|
-
* Unreached cells stay frozen in place. Returns visited cells incl. ends.
|
|
96
|
-
*/
|
|
97
|
-
descend(fromCell: number, budgetUnits: number): { end: number; cells: number[] } {
|
|
98
|
-
const cells = [fromCell];
|
|
99
|
-
if (this.distOfCurrent(fromCell) === Infinity) return { end: fromCell, cells };
|
|
100
|
-
let cur = fromCell;
|
|
101
|
-
let spent = 0;
|
|
102
|
-
while (spent < budgetUnits && this.distOfCurrent(cur) > 0) {
|
|
103
|
-
const next = this.bestNeighbor(cur);
|
|
104
|
-
if (next < 0) break;
|
|
105
|
-
spent += this.distOfCurrent(cur) - this.distOfCurrent(next);
|
|
106
|
-
cur = next;
|
|
107
|
-
cells.push(cur);
|
|
108
|
-
}
|
|
109
|
-
return { end: cur, cells };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Shrimp step path: cells from the latest field's source to fromCell. */
|
|
113
|
-
pathToSource(fromCell: number): number[] {
|
|
114
|
-
return this.descend(fromCell, Number.MAX_SAFE_INTEGER).cells.reverse();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private distOfCurrent(cell: number): number {
|
|
118
|
-
return this.stamp[cell] === this.gen ? this.dist[cell] : Infinity;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private bestNeighbor(cell: number): number {
|
|
122
|
-
const { grid } = this;
|
|
123
|
-
const W = grid.width;
|
|
124
|
-
const tx = cell % W;
|
|
125
|
-
const ty = Math.floor(cell / W);
|
|
126
|
-
const e = grid.isWalkable(tx + 1, ty);
|
|
127
|
-
const w = grid.isWalkable(tx - 1, ty);
|
|
128
|
-
const s = grid.isWalkable(tx, ty + 1);
|
|
129
|
-
const n = grid.isWalkable(tx, ty - 1);
|
|
130
|
-
let best = -1;
|
|
131
|
-
let bestDist = this.distOfCurrent(cell);
|
|
132
|
-
const consider = (c: number, walkable: boolean) => {
|
|
133
|
-
if (!walkable) return;
|
|
134
|
-
const d = this.distOfCurrent(c);
|
|
135
|
-
if (d < bestDist) {
|
|
136
|
-
bestDist = d;
|
|
137
|
-
best = c;
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
consider(cell + 1, e);
|
|
141
|
-
consider(cell - 1, w);
|
|
142
|
-
consider(cell + W, s);
|
|
143
|
-
consider(cell - W, n);
|
|
144
|
-
consider(cell + W + 1, e && s && grid.isWalkable(tx + 1, ty + 1));
|
|
145
|
-
consider(cell + W - 1, w && s && grid.isWalkable(tx - 1, ty + 1));
|
|
146
|
-
consider(cell - W + 1, e && n && grid.isWalkable(tx + 1, ty - 1));
|
|
147
|
-
consider(cell - W - 1, w && n && grid.isWalkable(tx - 1, ty - 1));
|
|
148
|
-
return best;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
1
|
+
import type { WalkableGrid } from './walkable-grid.js';
|
|
2
|
+
|
|
3
|
+
// Geodesic distance field over the walkable grid, integer octile costs:
|
|
4
|
+
// straight 5, diagonal 7 (~1:1.4). One tile = 4px = 5 units.
|
|
5
|
+
export const COST_STRAIGHT = 5;
|
|
6
|
+
export const COST_DIAG = 7;
|
|
7
|
+
export const UNITS_PER_PX = COST_STRAIGHT / 4;
|
|
8
|
+
export const PX_PER_UNIT = 4 / COST_STRAIGHT;
|
|
9
|
+
|
|
10
|
+
export interface FieldResult {
|
|
11
|
+
/** Geodesic cost in units from the source; Infinity if unreached. Valid until the next compute(). */
|
|
12
|
+
distOf(cell: number): number;
|
|
13
|
+
/** Per angle bucket, the farthest settled cell with dist <= ringUnits; -1 for empty buckets. */
|
|
14
|
+
ringCandidates: Int32Array;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class FieldCalculator {
|
|
18
|
+
private readonly dist: Int32Array;
|
|
19
|
+
private readonly stamp: Int32Array;
|
|
20
|
+
private gen = 0;
|
|
21
|
+
private source = -1;
|
|
22
|
+
|
|
23
|
+
constructor(private readonly grid: WalkableGrid) {
|
|
24
|
+
this.dist = new Int32Array(grid.width * grid.height);
|
|
25
|
+
this.stamp = new Int32Array(grid.width * grid.height);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Dial's-bucket Dijkstra from sourceCell, settling cells up to maxCostUnits.
|
|
30
|
+
* Diagonal steps require both orthogonal neighbors walkable (no corner
|
|
31
|
+
* cutting, matching the server's supercover line checks).
|
|
32
|
+
*/
|
|
33
|
+
compute(sourceCell: number, maxCostUnits: number, ringUnits: number, bucketCount = 16): FieldResult {
|
|
34
|
+
const { grid, dist, stamp } = this;
|
|
35
|
+
const W = grid.width;
|
|
36
|
+
this.gen++;
|
|
37
|
+
this.source = sourceCell;
|
|
38
|
+
const gen = this.gen;
|
|
39
|
+
|
|
40
|
+
const ringCandidates = new Int32Array(bucketCount).fill(-1);
|
|
41
|
+
const ringDist = new Int32Array(bucketCount).fill(-1);
|
|
42
|
+
const sx = sourceCell % W;
|
|
43
|
+
const sy = Math.floor(sourceCell / W);
|
|
44
|
+
|
|
45
|
+
const buckets: (number[] | undefined)[] = new Array(maxCostUnits + COST_DIAG + 1);
|
|
46
|
+
dist[sourceCell] = 0;
|
|
47
|
+
stamp[sourceCell] = gen;
|
|
48
|
+
buckets[0] = [sourceCell];
|
|
49
|
+
|
|
50
|
+
for (let c = 0; c <= maxCostUnits; c++) {
|
|
51
|
+
const bucket = buckets[c];
|
|
52
|
+
if (!bucket) continue;
|
|
53
|
+
for (const cell of bucket) {
|
|
54
|
+
if (dist[cell] !== c || stamp[cell] !== gen) continue; // stale entry
|
|
55
|
+
const tx = cell % W;
|
|
56
|
+
const ty = Math.floor(cell / W);
|
|
57
|
+
if (c > 0 && c <= ringUnits) {
|
|
58
|
+
const b = Math.floor(((Math.atan2(ty - sy, tx - sx) + Math.PI) / (2 * Math.PI)) * bucketCount) % bucketCount;
|
|
59
|
+
if (c > ringDist[b]) {
|
|
60
|
+
ringDist[b] = c;
|
|
61
|
+
ringCandidates[b] = cell;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const e = grid.isWalkable(tx + 1, ty);
|
|
65
|
+
const w = grid.isWalkable(tx - 1, ty);
|
|
66
|
+
const s = grid.isWalkable(tx, ty + 1);
|
|
67
|
+
const n = grid.isWalkable(tx, ty - 1);
|
|
68
|
+
this.relax(cell + 1, c + COST_STRAIGHT, e, buckets, gen);
|
|
69
|
+
this.relax(cell - 1, c + COST_STRAIGHT, w, buckets, gen);
|
|
70
|
+
this.relax(cell + W, c + COST_STRAIGHT, s, buckets, gen);
|
|
71
|
+
this.relax(cell - W, c + COST_STRAIGHT, n, buckets, gen);
|
|
72
|
+
this.relax(cell + W + 1, c + COST_DIAG, e && s && grid.isWalkable(tx + 1, ty + 1), buckets, gen);
|
|
73
|
+
this.relax(cell + W - 1, c + COST_DIAG, w && s && grid.isWalkable(tx - 1, ty + 1), buckets, gen);
|
|
74
|
+
this.relax(cell - W + 1, c + COST_DIAG, e && n && grid.isWalkable(tx + 1, ty - 1), buckets, gen);
|
|
75
|
+
this.relax(cell - W - 1, c + COST_DIAG, w && n && grid.isWalkable(tx - 1, ty - 1), buckets, gen);
|
|
76
|
+
}
|
|
77
|
+
buckets[c] = undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const distOf = (cell: number) => (stamp[cell] === gen ? dist[cell] : Infinity);
|
|
81
|
+
return { distOf, ringCandidates };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private relax(cell: number, cost: number, walkable: boolean, buckets: (number[] | undefined)[], gen: number): void {
|
|
85
|
+
if (!walkable) return;
|
|
86
|
+
if (this.stamp[cell] === gen && this.dist[cell] <= cost) return;
|
|
87
|
+
this.dist[cell] = cost;
|
|
88
|
+
this.stamp[cell] = gen;
|
|
89
|
+
(buckets[cost] ??= []).push(cell);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Walk from fromCell down the latest field's gradient toward its source,
|
|
94
|
+
* stopping once >= budgetUnits is spent (the last step may overshoot).
|
|
95
|
+
* Unreached cells stay frozen in place. Returns visited cells incl. ends.
|
|
96
|
+
*/
|
|
97
|
+
descend(fromCell: number, budgetUnits: number): { end: number; cells: number[] } {
|
|
98
|
+
const cells = [fromCell];
|
|
99
|
+
if (this.distOfCurrent(fromCell) === Infinity) return { end: fromCell, cells };
|
|
100
|
+
let cur = fromCell;
|
|
101
|
+
let spent = 0;
|
|
102
|
+
while (spent < budgetUnits && this.distOfCurrent(cur) > 0) {
|
|
103
|
+
const next = this.bestNeighbor(cur);
|
|
104
|
+
if (next < 0) break;
|
|
105
|
+
spent += this.distOfCurrent(cur) - this.distOfCurrent(next);
|
|
106
|
+
cur = next;
|
|
107
|
+
cells.push(cur);
|
|
108
|
+
}
|
|
109
|
+
return { end: cur, cells };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Shrimp step path: cells from the latest field's source to fromCell. */
|
|
113
|
+
pathToSource(fromCell: number): number[] {
|
|
114
|
+
return this.descend(fromCell, Number.MAX_SAFE_INTEGER).cells.reverse();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private distOfCurrent(cell: number): number {
|
|
118
|
+
return this.stamp[cell] === this.gen ? this.dist[cell] : Infinity;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private bestNeighbor(cell: number): number {
|
|
122
|
+
const { grid } = this;
|
|
123
|
+
const W = grid.width;
|
|
124
|
+
const tx = cell % W;
|
|
125
|
+
const ty = Math.floor(cell / W);
|
|
126
|
+
const e = grid.isWalkable(tx + 1, ty);
|
|
127
|
+
const w = grid.isWalkable(tx - 1, ty);
|
|
128
|
+
const s = grid.isWalkable(tx, ty + 1);
|
|
129
|
+
const n = grid.isWalkable(tx, ty - 1);
|
|
130
|
+
let best = -1;
|
|
131
|
+
let bestDist = this.distOfCurrent(cell);
|
|
132
|
+
const consider = (c: number, walkable: boolean) => {
|
|
133
|
+
if (!walkable) return;
|
|
134
|
+
const d = this.distOfCurrent(c);
|
|
135
|
+
if (d < bestDist) {
|
|
136
|
+
bestDist = d;
|
|
137
|
+
best = c;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
consider(cell + 1, e);
|
|
141
|
+
consider(cell - 1, w);
|
|
142
|
+
consider(cell + W, s);
|
|
143
|
+
consider(cell - W, n);
|
|
144
|
+
consider(cell + W + 1, e && s && grid.isWalkable(tx + 1, ty + 1));
|
|
145
|
+
consider(cell + W - 1, w && s && grid.isWalkable(tx - 1, ty + 1));
|
|
146
|
+
consider(cell - W + 1, e && n && grid.isWalkable(tx + 1, ty - 1));
|
|
147
|
+
consider(cell - W - 1, w && n && grid.isWalkable(tx - 1, ty - 1));
|
|
148
|
+
return best;
|
|
149
|
+
}
|
|
150
|
+
}
|