a bit better bfs
This commit is contained in:
parent
9de6293b1a
commit
2d6a6bd3a1
1 changed files with 57 additions and 37 deletions
|
|
@ -364,9 +364,10 @@ public class WorldMap : IDisposable
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// BFS through walkable (Explored) cells to find the nearest frontier
|
||||
/// (Explored cell adjacent to Unknown/Fog). Returns direction toward the
|
||||
/// first step on the shortest path, respecting walls.
|
||||
/// BFS through walkable (Explored) cells to find the best frontier direction.
|
||||
/// Instead of stopping at the nearest frontier, runs the full BFS and counts
|
||||
/// frontier cells reachable per first-step direction. Prefers directions with
|
||||
/// more frontier cells (corridors) over directions with few (dead-end rooms).
|
||||
/// </summary>
|
||||
public (double dirX, double dirY)? FindNearestUnexplored(MapPosition pos, int searchRadius = 400)
|
||||
{
|
||||
|
|
@ -379,24 +380,24 @@ public class WorldMap : IDisposable
|
|||
var rr = searchRadius / step;
|
||||
var gridW = 2 * rr + 1;
|
||||
|
||||
// Visited grid + parent tracking (encode parent as single int)
|
||||
var visited = new bool[gridW * gridW];
|
||||
var parentX = new short[gridW * gridW];
|
||||
var parentY = new short[gridW * gridW];
|
||||
// Propagate first step from start during BFS (avoids per-frontier trace-back)
|
||||
var firstStepX = new short[gridW * gridW];
|
||||
var firstStepY = new short[gridW * gridW];
|
||||
|
||||
var queue = new Queue<(int gx, int gy)>(4096);
|
||||
var startGx = rr;
|
||||
var startGy = rr;
|
||||
visited[startGy * gridW + startGx] = true;
|
||||
parentX[startGy * gridW + startGx] = -1;
|
||||
parentY[startGy * gridW + startGx] = -1;
|
||||
queue.Enqueue((startGx, startGy));
|
||||
|
||||
// 8-connected neighbors
|
||||
ReadOnlySpan<int> dxs = [-1, 0, 1, -1, 1, -1, 0, 1];
|
||||
ReadOnlySpan<int> dys = [-1, -1, -1, 0, 0, 1, 1, 1];
|
||||
|
||||
int foundGx = -1, foundGy = -1;
|
||||
// Count frontier cells per first-step direction
|
||||
var frontierCounts = new Dictionary<int, int>();
|
||||
var firstStepCoords = new Dictionary<int, (short gx, short gy)>();
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
|
|
@ -407,8 +408,9 @@ public class WorldMap : IDisposable
|
|||
var wy = cy + (gy - rr) * step;
|
||||
|
||||
// Check if this explored cell borders Unknown/Fog (= frontier)
|
||||
if (gx != startGx || gy != startGy) // skip player cell
|
||||
if (gx != startGx || gy != startGy)
|
||||
{
|
||||
var cellIdx = gy * gridW + gx;
|
||||
for (var d = 0; d < 8; d++)
|
||||
{
|
||||
var nx = wx + dxs[d] * step;
|
||||
|
|
@ -417,9 +419,15 @@ public class WorldMap : IDisposable
|
|||
var neighbor = _canvas.At<byte>(ny, nx);
|
||||
if (neighbor == (byte)MapCell.Unknown || neighbor == (byte)MapCell.Fog)
|
||||
{
|
||||
foundGx = gx;
|
||||
foundGy = gy;
|
||||
goto Found;
|
||||
var fsKey = firstStepY[cellIdx] * gridW + firstStepX[cellIdx];
|
||||
if (frontierCounts.TryGetValue(fsKey, out var cnt))
|
||||
frontierCounts[fsKey] = cnt + 1;
|
||||
else
|
||||
{
|
||||
frontierCounts[fsKey] = 1;
|
||||
firstStepCoords[fsKey] = (firstStepX[cellIdx], firstStepY[cellIdx]);
|
||||
}
|
||||
break; // don't double-count this cell
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -439,43 +447,54 @@ public class WorldMap : IDisposable
|
|||
if (nwx < 0 || nwx >= size || nwy < 0 || nwy >= size) continue;
|
||||
|
||||
var cell = _canvas.At<byte>(nwy, nwx);
|
||||
if (cell != (byte)MapCell.Explored) continue; // only walk through explored
|
||||
if (cell != (byte)MapCell.Explored) continue;
|
||||
|
||||
visited[idx] = true;
|
||||
parentX[idx] = (short)gx;
|
||||
parentY[idx] = (short)gy;
|
||||
// Propagate first step: direct neighbors of start ARE the first step
|
||||
if (gx == startGx && gy == startGy)
|
||||
{
|
||||
firstStepX[idx] = (short)ngx;
|
||||
firstStepY[idx] = (short)ngy;
|
||||
}
|
||||
else
|
||||
{
|
||||
var parentIdx = gy * gridW + gx;
|
||||
firstStepX[idx] = firstStepX[parentIdx];
|
||||
firstStepY[idx] = firstStepY[parentIdx];
|
||||
}
|
||||
queue.Enqueue((ngx, ngy));
|
||||
}
|
||||
}
|
||||
|
||||
Log.Information("BFS: no reachable frontier within {Radius}px", searchRadius);
|
||||
return null;
|
||||
|
||||
Found:
|
||||
// Trace back to first step from start
|
||||
var traceX = foundGx;
|
||||
var traceY = foundGy;
|
||||
while (true)
|
||||
if (frontierCounts.Count == 0)
|
||||
{
|
||||
var idx = traceY * gridW + traceX;
|
||||
var px = parentX[idx];
|
||||
var py = parentY[idx];
|
||||
if (px == startGx && py == startGy)
|
||||
break; // traceX/traceY is the first step
|
||||
traceX = px;
|
||||
traceY = py;
|
||||
Log.Information("BFS: no reachable frontier within {Radius}px", searchRadius);
|
||||
return null;
|
||||
}
|
||||
|
||||
var dirX = (double)(traceX - startGx);
|
||||
var dirY = (double)(traceY - startGy);
|
||||
// Pick direction with the most frontier cells (prefers corridors over dead ends)
|
||||
var bestKey = -1;
|
||||
var bestCount = 0;
|
||||
foreach (var (key, count) in frontierCounts)
|
||||
{
|
||||
if (count > bestCount)
|
||||
{
|
||||
bestCount = count;
|
||||
bestKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
var (bestGx, bestGy) = firstStepCoords[bestKey];
|
||||
var dirX = (double)(bestGx - startGx);
|
||||
var dirY = (double)(bestGy - startGy);
|
||||
var len = Math.Sqrt(dirX * dirX + dirY * dirY);
|
||||
if (len < 0.001) return (1, 0); // shouldn't happen
|
||||
if (len < 0.001) return (1, 0);
|
||||
|
||||
dirX /= len;
|
||||
dirY /= len;
|
||||
|
||||
var dist = Math.Sqrt((foundGx - startGx) * (foundGx - startGx) + (foundGy - startGy) * (foundGy - startGy)) * step;
|
||||
Log.Debug("BFS: frontier at {Dist:F0}px, first step dir=({Dx:F2},{Dy:F2})", dist, dirX, dirY);
|
||||
Log.Debug("BFS: {DirCount} directions, best={Best} frontier cells, dir=({Dx:F2},{Dy:F2})",
|
||||
frontierCounts.Count, bestCount, dirX, dirY);
|
||||
return (dirX, dirY);
|
||||
}
|
||||
|
||||
|
|
@ -504,7 +523,8 @@ public class WorldMap : IDisposable
|
|||
colored.Set(r, c, new Vec3b(104, 64, 31));
|
||||
else if (v == (byte)MapCell.Wall)
|
||||
colored.Set(r, c, new Vec3b(26, 45, 61));
|
||||
// Fog not rendered — too noisy from spell effects bleeding through overlay
|
||||
else if (v == (byte)MapCell.Fog)
|
||||
colored.Set(r, c, new Vec3b(55, 40, 28));
|
||||
}
|
||||
|
||||
var px = cx - x0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue