Rabbit review

This commit is contained in:
Arkadiusz Sygulski
2026-01-11 23:35:05 +01:00
parent 13b4142317
commit 89242094f8
5 changed files with 45 additions and 55 deletions
+10 -2
View File
@@ -47,10 +47,18 @@ export class AirPathFinder implements PathFinder<TileRef> {
let nextY = y;
const ratio = Math.floor(1 + Math.abs(dstY - y) / (Math.abs(dstX - x) + 1));
if (random.chance(ratio) && x !== dstX) {
if (x === dstX) {
// Can only move in Y
nextY += y < dstY ? 1 : -1;
} else if (y === dstY) {
// Can only move in X
nextX += x < dstX ? 1 : -1;
} else {
nextY += y < dstY ? 1 : -1;
if (random.chance(ratio)) {
nextX += x < dstX ? 1 : -1;
} else {
nextY += y < dstY ? 1 : -1;
}
}
return this.game.ref(nextX, nextY);
+10 -3
View File
@@ -102,9 +102,16 @@ export class PathFinderStepper<T> implements SteppingPathFinder<T> {
findPath(from: T | T[], to: T): T[] | null {
if (this.config.preCheck) {
const first = Array.isArray(from) ? from[0] : from;
const result = this.config.preCheck(first, to);
if (result?.status === PathStatus.NOT_FOUND) return null;
const fromArray = Array.isArray(from) ? from : [from];
const allFailed = fromArray.every((f) => {
const result = this.config.preCheck!(f, to);
return result?.status === PathStatus.NOT_FOUND;
});
if (allFailed) {
return null;
}
}
return this.finder.findPath(from, to);
@@ -11,12 +11,16 @@ export class MinHeap implements PriorityQueue {
private priorities: Float32Array;
private size = 0;
constructor(capacity: number) {
constructor(private capacity: number) {
this.heap = new Int32Array(capacity);
this.priorities = new Float32Array(capacity);
}
push(node: number, priority: number): void {
if (this.size >= this.capacity) {
throw new Error(`MinHeap capacity exceeded: ${this.capacity}`);
}
let i = this.size++;
this.heap[i] = node;
this.priorities[i] = priority;
@@ -18,24 +18,19 @@ export class ShoreCoercingTransformer implements PathFinder<number> {
) {}
findPath(from: TileRef | TileRef[], to: TileRef): TileRef[] | null {
// Coerce from tiles
const fromArray = Array.isArray(from) ? from : [from];
const coercedFromArray: Array<{
water: TileRef;
original: TileRef | null;
}> = [];
const waterToOriginal = new Map<TileRef, TileRef | null>();
const waterFrom: TileRef[] = [];
for (const f of fromArray) {
const coerced = this.coerceToWater(f);
if (coerced.water !== null) {
coercedFromArray.push({
water: coerced.water,
original: coerced.original,
});
waterFrom.push(coerced.water);
waterToOriginal.set(coerced.water, coerced.original);
}
}
if (coercedFromArray.length === 0) {
if (waterFrom.length === 0) {
return null;
}
@@ -45,52 +40,27 @@ export class ShoreCoercingTransformer implements PathFinder<number> {
return null;
}
// Build water-only from array
const waterFrom =
coercedFromArray.length === 1
? coercedFromArray[0].water
: coercedFromArray.map((c) => c.water);
// Search on water tiles
const path = this.inner.findPath(waterFrom, coercedTo.water);
if (!path || path.length === 0) {
return null;
}
// Fix extremes: find which source was used and prepend/append originals
const result = [...path];
// Find the original for the source that was used (closest to path start)
if (coercedFromArray.length > 0) {
const pathStart = result[0];
let bestOriginal: TileRef | null = null;
let minDist = Infinity;
for (const { water, original } of coercedFromArray) {
if (original !== null) {
const dist = this.map.manhattanDist(pathStart, water);
if (dist < minDist) {
minDist = dist;
bestOriginal = original;
}
}
}
// Prepend original if we have one and it's not already at start
if (bestOriginal !== null && result[0] !== bestOriginal) {
result.unshift(bestOriginal);
}
// Look up the actual path start in the map
const originalShore = waterToOriginal.get(path[0]);
if (originalShore !== undefined && originalShore !== null) {
path.unshift(originalShore);
}
// Append original to if different
if (
coercedTo.original !== null &&
result[result.length - 1] !== coercedTo.original
path[path.length - 1] !== coercedTo.original
) {
result.push(coercedTo.original);
path.push(coercedTo.original);
}
return result;
return path;
}
/**
+8 -7
View File
@@ -8,8 +8,8 @@
* npx tsx tests/pathfinding/benchmark/compare.ts --synthetic <map-name> <adapters>
*
* Examples:
* npx tsx tests/pathfinding/benchmark/compare.ts default hpa,legacy
* npx tsx tests/pathfinding/benchmark/compare.ts --synthetic giantworldmap hpa,legacy,a.optimized
* npx tsx tests/pathfinding/benchmark/compare.ts default hpa,a.baseline
* npx tsx tests/pathfinding/benchmark/compare.ts --synthetic giantworldmap hpa,hpa.cached,a.full
*/
import {
@@ -111,17 +111,18 @@ Usage:
Arguments:
<scenario> Name of the scenario (default: "default")
<adapters> Comma-separated list of adapters to compare (e.g., "hpa,legacy,a.optimized")
<adapters> Comma-separated list of adapters to compare (e.g., "hpa,a.baseline")
Examples:
npx tsx tests/pathfinding/benchmark/compare.ts default hpa,legacy
npx tsx tests/pathfinding/benchmark/compare.ts --synthetic giantworldmap hpa,legacy,a.optimized
npx tsx tests/pathfinding/benchmark/compare.ts default hpa,a.baseline
npx tsx tests/pathfinding/benchmark/compare.ts --synthetic giantworldmap hpa,hpa.cached,a.full
Available adapters:
a.baseline - A* on minimap (inlined)
a.generic - A* on minimap (adapter)
a.full - A* on full map
hpa - Hierarchical pathfinding (no cache)
hpa.cached - Hierarchical pathfinding (with cache)
legacy - Legacy A* algorithm
a.optimized - Optimized A* algorithm
`);
}