2026-04-21 23:28:49 +07:00
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace Hallucinate.GameSetup.Maze
|
|
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
/// <summary>
|
|
|
|
|
/// Wilson's Algorithm implementation based on the original provided logic.
|
|
|
|
|
/// Ensures paths are sparse and correctly finalized using specific neighbor constraints.
|
|
|
|
|
/// </summary>
|
2026-04-21 23:28:49 +07:00
|
|
|
public class WilsonsAlgorithm : IMazeAlgorithm
|
|
|
|
|
{
|
|
|
|
|
private const int MinBoundary = 2;
|
2026-04-22 00:29:09 +07:00
|
|
|
private const int MaxIterationSafety = 5000;
|
|
|
|
|
private const int MaxWalkSteps = 5000;
|
2026-04-21 23:28:49 +07:00
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
private readonly List<MapLocation> _directions = MapLocation.Directions;
|
|
|
|
|
private List<MapLocation> _notUsed = new List<MapLocation>();
|
2026-04-21 23:28:49 +07:00
|
|
|
|
|
|
|
|
public void Generate(MazeGrid grid)
|
|
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
// 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);
|
2026-04-21 23:28:49 +07:00
|
|
|
grid.SetCell(x, z, MazeCellType.Corridor);
|
|
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
int safety = 0;
|
|
|
|
|
while (GetAvailableCells(grid) > 1 && safety < MaxIterationSafety)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
RandomWalkSync(grid);
|
|
|
|
|
safety++;
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEnumerator GenerateStepByStep(MazeGrid grid, float interval)
|
|
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
int x = Random.Range(MinBoundary, grid.Width - 1);
|
|
|
|
|
int z = Random.Range(MinBoundary, grid.Depth - 1);
|
2026-04-21 23:28:49 +07:00
|
|
|
grid.SetCell(x, z, MazeCellType.Corridor);
|
|
|
|
|
yield return new WaitForSeconds(interval);
|
|
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
int safety = 0;
|
|
|
|
|
while (GetAvailableCells(grid) > 1 && safety < MaxIterationSafety)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
yield return RandomWalk(grid, interval);
|
|
|
|
|
safety++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-21 23:28:49 +07:00
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
/// <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++;
|
|
|
|
|
}
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
2026-04-22 00:29:09 +07:00
|
|
|
return count;
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
private int GetAvailableCells(MazeGrid grid)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
_notUsed.Clear();
|
2026-04-21 23:28:49 +07:00
|
|
|
for (int z = 1; z < grid.Depth - 1; z++)
|
|
|
|
|
{
|
|
|
|
|
for (int x = 1; x < grid.Width - 1; x++)
|
|
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
if (CountFinalizedNeighbours(grid, x, z) == 0)
|
|
|
|
|
{
|
|
|
|
|
_notUsed.Add(new MapLocation(x, z));
|
|
|
|
|
}
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-22 00:29:09 +07:00
|
|
|
return _notUsed.Count;
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
private void RandomWalkSync(MazeGrid grid)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
if (_notUsed.Count == 0) return;
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
MapLocation rd = _directions[Random.Range(0, _directions.Count)];
|
|
|
|
|
int nx = cx + rd.x;
|
|
|
|
|
int nz = cz + rd.z;
|
|
|
|
|
|
|
|
|
|
// User's original constraint: CountSquareNeighbours (nx, nz) < 2
|
|
|
|
|
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)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
foreach (MapLocation m in inWalk)
|
|
|
|
|
grid.SetCell(m.x, m.z, MazeCellType.Corridor);
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
private IEnumerator RandomWalk(MazeGrid grid, float interval)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
if (_notUsed.Count == 0) yield break;
|
|
|
|
|
|
2026-04-21 23:28:49 +07:00
|
|
|
List<MapLocation> inWalk = new List<MapLocation>();
|
2026-04-22 00:29:09 +07:00
|
|
|
int rStartIndex = Random.Range(0, _notUsed.Count);
|
|
|
|
|
int cx = _notUsed[rStartIndex].x;
|
|
|
|
|
int cz = _notUsed[rStartIndex].z;
|
2026-04-21 23:28:49 +07:00
|
|
|
|
|
|
|
|
inWalk.Add(new MapLocation(cx, cz));
|
|
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
int loop = 0;
|
|
|
|
|
bool validPath = false;
|
|
|
|
|
while (cx > 0 && cx < grid.Width - 1 && cz > 0 && cz < grid.Depth - 1 && loop < MaxWalkSteps && !validPath)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
grid.SetCell(cx, cz, MazeCellType.Processing); // State 0
|
2026-04-21 23:28:49 +07:00
|
|
|
if (interval > 0) yield return new WaitForSeconds(interval);
|
|
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
if (CountFinalizedNeighbours(grid, cx, cz) > 1) break;
|
2026-04-21 23:28:49 +07:00
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
MapLocation rd = _directions[Random.Range(0, _directions.Count)];
|
|
|
|
|
int nx = cx + rd.x;
|
|
|
|
|
int nz = cz + rd.z;
|
2026-04-21 23:28:49 +07:00
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
if (CountFinalizedNeighbours(grid, nx, nz) < 2)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
|
|
|
|
cx = nx;
|
|
|
|
|
cz = nz;
|
|
|
|
|
inWalk.Add(new MapLocation(cx, cz));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
validPath = CountFinalizedNeighbours(grid, cx, cz) == 1;
|
|
|
|
|
loop++;
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 00:29:09 +07:00
|
|
|
if (validPath)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
foreach (MapLocation m in inWalk)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
grid.SetCell(m.x, m.z, MazeCellType.Corridor); // State 2
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-04-22 00:29:09 +07:00
|
|
|
foreach (MapLocation m in inWalk)
|
|
|
|
|
grid.SetCell(m.x, m.z, MazeCellType.Wall); // State 1
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
2026-04-22 00:29:09 +07:00
|
|
|
inWalk.Clear();
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|