initial BFS movement

This commit is contained in:
Boki 2026-02-13 16:56:44 -05:00
parent 25257e1517
commit 490fb8bdba
7 changed files with 258 additions and 101 deletions

View file

@ -323,57 +323,119 @@ public class WorldMap : IDisposable
return totalBlocks > 0 ? (double)cleanBlocks / totalBlocks : 1.0;
}
public (double dirX, double dirY)? FindNearestUnexplored(MapPosition pos, int searchRadius = 200)
/// <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.
/// </summary>
public (double dirX, double dirY)? FindNearestUnexplored(MapPosition pos, int searchRadius = 400)
{
var cx = (int)Math.Round(pos.X);
var cy = (int)Math.Round(pos.Y);
var bestAngle = double.NaN;
var bestScore = 0;
const int sectorCount = 16;
var fogRadius = _config.CaptureSize / 2;
// BFS at half resolution for speed (step=2 → ~200x200 effective grid for r=400)
const int step = 2;
var size = _config.CanvasSize;
var rr = searchRadius / step;
var gridW = 2 * rr + 1;
for (var sector = 0; sector < sectorCount; sector++)
// 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];
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;
while (queue.Count > 0)
{
var angle = 2 * Math.PI * sector / sectorCount;
var score = 0;
var (gx, gy) = queue.Dequeue();
for (var r = fogRadius - 20; r <= fogRadius + searchRadius; r += 5)
// Map grid coords back to canvas coords
var wx = cx + (gx - rr) * step;
var wy = cy + (gy - rr) * step;
// Check if this explored cell borders Unknown/Fog (= frontier)
if (gx != startGx || gy != startGy) // skip player cell
{
for (var spread = -15; spread <= 15; spread += 5)
for (var d = 0; d < 8; d++)
{
var sampleAngle = angle + spread * Math.PI / 180;
var sx = cx + (int)(r * Math.Cos(sampleAngle));
var sy = cy + (int)(r * Math.Sin(sampleAngle));
if (sx < 0 || sx >= _config.CanvasSize || sy < 0 || sy >= _config.CanvasSize)
continue;
var cell = _canvas.At<byte>(sy, sx);
if (cell == (byte)MapCell.Fog)
score += 2; // prefer visible fog (we know there's unexplored area)
else if (cell == (byte)MapCell.Unknown)
score++;
var nx = wx + dxs[d] * step;
var ny = wy + dys[d] * step;
if (nx < 0 || nx >= size || ny < 0 || ny >= size) continue;
var neighbor = _canvas.At<byte>(ny, nx);
if (neighbor == (byte)MapCell.Unknown || neighbor == (byte)MapCell.Fog)
{
foundGx = gx;
foundGy = gy;
goto Found;
}
}
}
if (score > bestScore)
// Expand to walkable neighbors
for (var d = 0; d < 8; d++)
{
bestScore = score;
bestAngle = angle;
var ngx = gx + dxs[d];
var ngy = gy + dys[d];
if (ngx < 0 || ngx >= gridW || ngy < 0 || ngy >= gridW) continue;
var idx = ngy * gridW + ngx;
if (visited[idx]) continue;
var nwx = cx + (ngx - rr) * step;
var nwy = cy + (ngy - rr) * step;
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
visited[idx] = true;
parentX[idx] = (short)gx;
parentY[idx] = (short)gy;
queue.Enqueue((ngx, ngy));
}
}
if (bestScore == 0 || double.IsNaN(bestAngle))
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)
{
Log.Information("No unexplored area found within search radius");
return null;
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;
}
var dirX = Math.Cos(bestAngle);
var dirY = Math.Sin(bestAngle);
Log.Debug("Best exploration direction: angle={Angle:F1}deg score={Score}",
bestAngle * 180 / Math.PI, bestScore);
var dirX = (double)(traceX - startGx);
var dirY = (double)(traceY - startGy);
var len = Math.Sqrt(dirX * dirX + dirY * dirY);
if (len < 0.001) return (1, 0); // shouldn't happen
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);
return (dirX, dirY);
}