This commit is contained in:
2026-04-22 00:29:09 +07:00
parent 8de65bb527
commit 1e2b7a0dd2
12 changed files with 2215 additions and 181 deletions

View File

@@ -4,115 +4,173 @@ using UnityEngine;
namespace Hallucinate.GameSetup.Maze
{
/// <summary>
/// Wilson's Algorithm implementation based on the original provided logic.
/// Ensures paths are sparse and correctly finalized using specific neighbor constraints.
/// </summary>
public class WilsonsAlgorithm : IMazeAlgorithm
{
private const int MinBoundary = 2;
private const int MaxIterationSafety = 5000;
private const int TargetPathNeighbours = 1;
private const int MaxIterationSafety = 5000;
private const int MaxWalkSteps = 5000;
private readonly List<MapLocation> _directions = new List<MapLocation>()
{
new MapLocation(1, 0), new MapLocation(0, 1), new MapLocation(-1, 0), new MapLocation(0, -1)
};
private readonly List<MapLocation> _directions = MapLocation.Directions;
private List<MapLocation> _notUsed = new List<MapLocation>();
public void Generate(MazeGrid grid)
{
int x = Random.Range(MinBoundary, grid.Width - MinBoundary);
int z = Random.Range(MinBoundary, grid.Depth - MinBoundary);
// 1. Create a starting finalized cell (Type.Corridor represents state 2)
int x = Random.Range(MinBoundary, grid.Width - 1);
int z = Random.Range(MinBoundary, grid.Depth - 1);
grid.SetCell(x, z, MazeCellType.Corridor);
while (GetAvailableCells(grid).Count > 1)
int safety = 0;
while (GetAvailableCells(grid) > 1 && safety < MaxIterationSafety)
{
PerformRandomWalk(grid, null, 0);
RandomWalkSync(grid);
safety++;
}
}
public IEnumerator GenerateStepByStep(MazeGrid grid, float interval)
{
int x = Random.Range(MinBoundary, grid.Width - MinBoundary);
int z = Random.Range(MinBoundary, grid.Depth - MinBoundary);
int x = Random.Range(MinBoundary, grid.Width - 1);
int z = Random.Range(MinBoundary, grid.Depth - 1);
grid.SetCell(x, z, MazeCellType.Corridor);
yield return new WaitForSeconds(interval);
while (true)
int safety = 0;
while (GetAvailableCells(grid) > 1 && safety < MaxIterationSafety)
{
var available = GetAvailableCells(grid);
if (available.Count <= 1) break;
yield return PerformRandomWalk(grid, available, interval);
yield return RandomWalk(grid, interval);
safety++;
}
}
private List<MapLocation> GetAvailableCells(MazeGrid grid)
{
List<MapLocation> available = new List<MapLocation>();
for (int z = 1; z < grid.Depth - 1; z++)
{
for (int x = 1; x < grid.Width - 1; x++)
{
if (CountPathNeighbours(grid, x, z) == 0)
available.Add(new MapLocation(x, z));
}
}
return available;
}
private int CountPathNeighbours(MazeGrid grid, int x, int z)
/// <summary>
/// Counts neighbors that are already part of the finalized maze (State 2 / Corridor).
/// </summary>
private int CountFinalizedNeighbours(MazeGrid grid, int x, int z)
{
int count = 0;
foreach (var d in _directions)
{
if (grid.GetCell(x + d.x, z + d.z) == MazeCellType.Corridor) count++;
if (grid.GetCell(x + d.x, z + d.z) == MazeCellType.Corridor)
{
count++;
}
}
return count;
}
private IEnumerator PerformRandomWalk(MazeGrid grid, List<MapLocation> available, float interval)
private int GetAvailableCells(MazeGrid grid)
{
_notUsed.Clear();
for (int z = 1; z < grid.Depth - 1; z++)
{
for (int x = 1; x < grid.Width - 1; x++)
{
if (CountFinalizedNeighbours(grid, x, z) == 0)
{
_notUsed.Add(new MapLocation(x, z));
}
}
}
return _notUsed.Count;
}
private void RandomWalkSync(MazeGrid grid)
{
if (_notUsed.Count == 0) return;
List<MapLocation> inWalk = new List<MapLocation>();
int rStart = Random.Range(0, available.Count);
int cx = available[rStart].x;
int cz = available[rStart].z;
int rStartIndex = Random.Range(0, _notUsed.Count);
int cx = _notUsed[rStartIndex].x;
int cz = _notUsed[rStartIndex].z;
inWalk.Add(new MapLocation(cx, cz));
bool pathFound = false;
int safety = 0;
while (grid.IsInBounds(cx, cz) && !pathFound && safety < MaxIterationSafety)
int loop = 0;
bool validPath = false;
while (cx > 0 && cx < grid.Width - 1 && cz > 0 && cz < grid.Depth - 1 && loop < MaxWalkSteps && !validPath)
{
grid.SetCell(cx, cz, MazeCellType.Processing);
if (interval > 0) yield return new WaitForSeconds(interval);
// Mark as temporary walk (State 0 / Processing)
// Note: We don't set grid cell here in sync mode to avoid triggering events unnecessarily
// but we keep track of neighbors.
if (CountFinalizedNeighbours(grid, cx, cz) > 1) break;
if (CountPathNeighbours(grid, cx, cz) > 1) break;
MapLocation rd = _directions[Random.Range(0, _directions.Count)];
int nx = cx + rd.x;
int nz = cz + rd.z;
int rd = Random.Range(0, _directions.Count);
int nx = cx + _directions[rd].x;
int nz = cz + _directions[rd].z;
if (grid.CountSquareNeighbours(nx, nz, MazeCellType.Processing) < 2)
// User's original constraint: CountSquareNeighbours (nx, nz) < 2
if (CountFinalizedNeighbours(grid, nx, nz) < 2)
{
cx = nx;
cz = nz;
inWalk.Add(new MapLocation(cx, cz));
}
pathFound = CountPathNeighbours(grid, cx, cz) == TargetPathNeighbours;
safety++;
validPath = CountFinalizedNeighbours(grid, cx, cz) == 1;
loop++;
}
if (pathFound)
if (validPath)
{
foreach (var m in inWalk)
{
foreach (MapLocation m in inWalk)
grid.SetCell(m.x, m.z, MazeCellType.Corridor);
if (interval > 0) yield return new WaitForSeconds(interval * 0.5f);
}
}
private IEnumerator RandomWalk(MazeGrid grid, float interval)
{
if (_notUsed.Count == 0) yield break;
List<MapLocation> inWalk = new List<MapLocation>();
int rStartIndex = Random.Range(0, _notUsed.Count);
int cx = _notUsed[rStartIndex].x;
int cz = _notUsed[rStartIndex].z;
inWalk.Add(new MapLocation(cx, cz));
int loop = 0;
bool validPath = false;
while (cx > 0 && cx < grid.Width - 1 && cz > 0 && cz < grid.Depth - 1 && loop < MaxWalkSteps && !validPath)
{
grid.SetCell(cx, cz, MazeCellType.Processing); // State 0
if (interval > 0) yield return new WaitForSeconds(interval);
if (CountFinalizedNeighbours(grid, cx, cz) > 1) break;
MapLocation rd = _directions[Random.Range(0, _directions.Count)];
int nx = cx + rd.x;
int nz = cz + rd.z;
if (CountFinalizedNeighbours(grid, nx, nz) < 2)
{
cx = nx;
cz = nz;
inWalk.Add(new MapLocation(cx, cz));
}
validPath = CountFinalizedNeighbours(grid, cx, cz) == 1;
loop++;
}
if (validPath)
{
foreach (MapLocation m in inWalk)
{
grid.SetCell(m.x, m.z, MazeCellType.Corridor); // State 2
}
}
else
{
foreach (var m in inWalk)
grid.SetCell(m.x, m.z, MazeCellType.Wall);
foreach (MapLocation m in inWalk)
grid.SetCell(m.x, m.z, MazeCellType.Wall); // State 1
}
inWalk.Clear();
}
}
}