mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-13 14:14:10 +00:00
Add local corridor widening for adaptive pathfinding
- Introduced a new approach to local corridor widening in the `CoarseToFineWaterPath` module, allowing for more efficient pathfinding by expanding the search corridor based on visited coarse regions. - Implemented a mechanism to avoid global radius increases, enhancing performance and reducing unnecessary searches. - Updated `MultiSourceAnyTargetBFS` to support marking visited coarse regions during BFS, facilitating the local widening process. - Adjusted parameters for maximum attempts and corridor radius to optimize pathfinding behavior.
This commit is contained in:
@@ -91,6 +91,16 @@ function getStampSet(gm: GameMap): StampSet {
|
||||
stampSetCache.set(gm, set);
|
||||
return set;
|
||||
}
|
||||
|
||||
// Separate stamp array for "visited coarse regions" marking to avoid clobbering the allowed corridor stamp.
|
||||
const visitedStampSetCache = new WeakMap<GameMap, StampSet>();
|
||||
function getVisitedStampSet(gm: GameMap): StampSet {
|
||||
const cached = visitedStampSetCache.get(gm);
|
||||
if (cached) return cached;
|
||||
const set: StampSet = { stamp: 1, data: new Uint32Array(gm.width() * gm.height()) };
|
||||
visitedStampSetCache.set(gm, set);
|
||||
return set;
|
||||
}
|
||||
function nextStamp(set: StampSet): number {
|
||||
const next = (set.stamp + 1) >>> 0;
|
||||
set.stamp = next === 0 ? 1 : next;
|
||||
@@ -137,6 +147,38 @@ function markCoarseCorridor(
|
||||
}
|
||||
}
|
||||
|
||||
function widenAllowedByVisitedRing(
|
||||
coarseWidth: number,
|
||||
coarseHeight: number,
|
||||
allowedStamp: Uint32Array,
|
||||
allowed: number,
|
||||
visitedStamp: Uint32Array,
|
||||
visited: number,
|
||||
): boolean {
|
||||
let widened = false;
|
||||
for (let y = 0; y < coarseHeight; y++) {
|
||||
const row = y * coarseWidth;
|
||||
for (let x = 0; x < coarseWidth; x++) {
|
||||
const idx = row + x;
|
||||
if (visitedStamp[idx] !== visited) continue;
|
||||
const y0 = Math.max(0, y - 1);
|
||||
const y1 = Math.min(coarseHeight - 1, y + 1);
|
||||
const x0 = Math.max(0, x - 1);
|
||||
const x1 = Math.min(coarseWidth - 1, x + 1);
|
||||
for (let yy = y0; yy <= y1; yy++) {
|
||||
const nRow = yy * coarseWidth;
|
||||
for (let xx = x0; xx <= x1; xx++) {
|
||||
const n = nRow + xx;
|
||||
if (allowedStamp[n] === allowed) continue;
|
||||
allowedStamp[n] = allowed;
|
||||
widened = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return widened;
|
||||
}
|
||||
|
||||
export function findWaterPathFromSeedsCoarseToFine(
|
||||
fineMap: GameMap,
|
||||
seedNodes: readonly TileRef[],
|
||||
@@ -228,20 +270,23 @@ export function findWaterPathFromSeedsCoarseToFine(
|
||||
}
|
||||
|
||||
const corridorRadius0 = Math.max(0, coarseToFine.corridorRadius ?? 2);
|
||||
const maxAttempts = Math.max(1, coarseToFine.maxAttempts ?? 2);
|
||||
const radiusMultiplier = Math.max(1, coarseToFine.radiusMultiplier ?? 2);
|
||||
const maxAttempts = Math.max(1, coarseToFine.maxAttempts ?? 3);
|
||||
|
||||
const corridorSet = getStampSet(coarseMap);
|
||||
for (let attempt = 0, radius = corridorRadius0; attempt < maxAttempts; attempt++) {
|
||||
const corridorStamp = nextStamp(corridorSet);
|
||||
markCoarseCorridor(
|
||||
coarseWidth,
|
||||
coarseHeight,
|
||||
corridorSet.data,
|
||||
corridorStamp,
|
||||
coarseResult.path,
|
||||
radius,
|
||||
);
|
||||
// Allowed corridor stamp is stable across attempts (widening is cumulative).
|
||||
const allowedSet = getStampSet(coarseMap);
|
||||
const allowed = nextStamp(allowedSet);
|
||||
markCoarseCorridor(
|
||||
coarseWidth,
|
||||
coarseHeight,
|
||||
allowedSet.data,
|
||||
allowed,
|
||||
coarseResult.path,
|
||||
corridorRadius0,
|
||||
);
|
||||
|
||||
const visitedSet = getVisitedStampSet(coarseMap);
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||
const visited = nextStamp(visitedSet);
|
||||
|
||||
const refined = fineBfs.findWaterPathFromSeeds(
|
||||
fineMap,
|
||||
@@ -252,14 +297,31 @@ export function findWaterPathFromSeedsCoarseToFine(
|
||||
...bfsOpts,
|
||||
allowedMask: {
|
||||
tileToRegion: mapping.fineToCoarse,
|
||||
regionStamp: corridorSet.data,
|
||||
stamp: corridorStamp,
|
||||
regionStamp: allowedSet.data,
|
||||
stamp: allowed,
|
||||
},
|
||||
visitedMaskOut: {
|
||||
tileToRegion: mapping.fineToCoarse,
|
||||
regionStamp: visitedSet.data,
|
||||
stamp: visited,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (refined !== null) return refined;
|
||||
|
||||
radius *= radiusMultiplier;
|
||||
if (attempt === maxAttempts - 1) break;
|
||||
|
||||
// Local corridor widening: expand by 1 ring around the coarse regions actually visited
|
||||
// in this failed attempt. Widening is cumulative (newly allowed regions stay allowed).
|
||||
const widened = widenAllowedByVisitedRing(
|
||||
coarseWidth,
|
||||
coarseHeight,
|
||||
allowedSet.data,
|
||||
allowed,
|
||||
visitedSet.data,
|
||||
visited,
|
||||
);
|
||||
if (!widened) break;
|
||||
}
|
||||
|
||||
// Final fallback: unrestricted fine BFS.
|
||||
|
||||
@@ -20,6 +20,17 @@ export type MultiSourceAnyTargetBFSOptions = {
|
||||
regionStamp: Uint32Array;
|
||||
stamp: number;
|
||||
};
|
||||
/**
|
||||
* Optional region marking output.
|
||||
*
|
||||
* Intended for local corridor widening: during BFS, mark which coarse regions were
|
||||
* actually visited (cheap stamp write, allocation-free).
|
||||
*/
|
||||
visitedMaskOut?: {
|
||||
tileToRegion: Uint32Array;
|
||||
regionStamp: Uint32Array;
|
||||
stamp: number;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -79,6 +90,7 @@ export class MultiSourceAnyTargetBFS {
|
||||
let tail = 0;
|
||||
|
||||
const allowed = opts.allowedMask;
|
||||
const visitedOut = opts.visitedMaskOut;
|
||||
|
||||
const count = Math.min(seedNodes.length, seedOrigins.length);
|
||||
for (let i = 0; i < count; i++) {
|
||||
@@ -96,6 +108,9 @@ export class MultiSourceAnyTargetBFS {
|
||||
this.visitedStamp[node] = stamp;
|
||||
this.prev[node] = -1;
|
||||
this.startOf[node] = origin;
|
||||
if (visitedOut) {
|
||||
visitedOut.regionStamp[visitedOut.tileToRegion[node]!] = visitedOut.stamp;
|
||||
}
|
||||
this.queue[tail++] = node;
|
||||
}
|
||||
|
||||
@@ -127,8 +142,8 @@ export class MultiSourceAnyTargetBFS {
|
||||
) {
|
||||
// skip
|
||||
} else {
|
||||
this.visit(n, node, stamp);
|
||||
this.queue[tail++] = n;
|
||||
this.visit(n, node, stamp, visitedOut);
|
||||
this.queue[tail++] = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,8 +156,8 @@ export class MultiSourceAnyTargetBFS {
|
||||
) {
|
||||
// skip
|
||||
} else {
|
||||
this.visit(s, node, stamp);
|
||||
this.queue[tail++] = s;
|
||||
this.visit(s, node, stamp, visitedOut);
|
||||
this.queue[tail++] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,8 +170,8 @@ export class MultiSourceAnyTargetBFS {
|
||||
) {
|
||||
// skip
|
||||
} else {
|
||||
this.visit(wv, node, stamp);
|
||||
this.queue[tail++] = wv;
|
||||
this.visit(wv, node, stamp, visitedOut);
|
||||
this.queue[tail++] = wv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,8 +184,8 @@ export class MultiSourceAnyTargetBFS {
|
||||
) {
|
||||
// skip
|
||||
} else {
|
||||
this.visit(ev, node, stamp);
|
||||
this.queue[tail++] = ev;
|
||||
this.visit(ev, node, stamp, visitedOut);
|
||||
this.queue[tail++] = ev;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,8 +206,8 @@ export class MultiSourceAnyTargetBFS {
|
||||
) {
|
||||
// skip
|
||||
} else {
|
||||
this.visit(nw, node, stamp);
|
||||
this.queue[tail++] = nw;
|
||||
this.visit(nw, node, stamp, visitedOut);
|
||||
this.queue[tail++] = nw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,8 +224,8 @@ export class MultiSourceAnyTargetBFS {
|
||||
) {
|
||||
// skip
|
||||
} else {
|
||||
this.visit(ne, node, stamp);
|
||||
this.queue[tail++] = ne;
|
||||
this.visit(ne, node, stamp, visitedOut);
|
||||
this.queue[tail++] = ne;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,8 +242,8 @@ export class MultiSourceAnyTargetBFS {
|
||||
) {
|
||||
// skip
|
||||
} else {
|
||||
this.visit(sw, node, stamp);
|
||||
this.queue[tail++] = sw;
|
||||
this.visit(sw, node, stamp, visitedOut);
|
||||
this.queue[tail++] = sw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -245,8 +260,8 @@ export class MultiSourceAnyTargetBFS {
|
||||
) {
|
||||
// skip
|
||||
} else {
|
||||
this.visit(se, node, stamp);
|
||||
this.queue[tail++] = se;
|
||||
this.visit(se, node, stamp, visitedOut);
|
||||
this.queue[tail++] = se;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,10 +270,18 @@ export class MultiSourceAnyTargetBFS {
|
||||
return null;
|
||||
}
|
||||
|
||||
private visit(node: TileRef, from: TileRef, stamp: number) {
|
||||
private visit(
|
||||
node: TileRef,
|
||||
from: TileRef,
|
||||
stamp: number,
|
||||
visitedOut: MultiSourceAnyTargetBFSOptions["visitedMaskOut"],
|
||||
) {
|
||||
this.visitedStamp[node] = stamp;
|
||||
this.prev[node] = from;
|
||||
this.startOf[node] = this.startOf[from];
|
||||
if (visitedOut) {
|
||||
visitedOut.regionStamp[visitedOut.tileToRegion[node]!] = visitedOut.stamp;
|
||||
}
|
||||
}
|
||||
|
||||
private reconstructPath(target: TileRef): TileRef[] {
|
||||
|
||||
Reference in New Issue
Block a user