Skip to content

Commit

Permalink
Maze generator
Browse files Browse the repository at this point in the history
- added an extension GenerateMaze for IRectangle
- added a test for GenerateMaze
  • Loading branch information
Sichii committed May 20, 2024
1 parent fecc823 commit 8b770c2
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 0 deletions.
100 changes: 100 additions & 0 deletions Chaos.Extensions.Geometry/RectangleExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,106 @@ public static bool Contains<TPoint>(this IRectangle rect, TPoint point) where TP
return (rect.Left <= point.X) && (rect.Right >= point.X) && (rect.Top <= point.Y) && (rect.Bottom >= point.Y);
}

/// <summary>
/// Given a start and end point, generates a maze inside the <see cref="Chaos.Geometry.Abstractions.IRectangle" />
/// </summary>
/// <param name="rect">
/// The rect that represents the bounds of the maze
/// </param>
/// <param name="start">
/// The start point of the maze
/// </param>
/// <param name="end">
/// The end point of the maze
/// </param>
/// <returns>
/// </returns>
public static IEnumerable<Point> GenerateMaze(this IRectangle rect, IPoint start, IPoint end)
{
//neighbor pattern
List<(int, int)> pattern =
[
(0, 1),
(1, 0),
(0, -1),
(-1, 0)
];

var height = rect.Height;
var width = rect.Width;
var startNode = Point.From(start);
var endNode = Point.From(end);
var maze = new bool[width, height];
var discoveryQueue = new Stack<Point>();

//initialize maze full of walls
for (var x = 0; x < maze.GetLength(0); x++)
for (var y = 0; y < maze.GetLength(1); y++)
maze[x, y] = true;

//start wtih startNode
//startNode is not a wall
discoveryQueue.Push(startNode);
maze[startNode.X, startNode.Y] = false;

//carve out the maze
while (discoveryQueue.Count > 0)
{
//get current node
var current = discoveryQueue.Peek();
var carved = false;

//shuffle neighbor pattern so we get a random direction
ShuffleInPlace(pattern);

//for each direction in the pattern
foreach ((var dx, var dy) in pattern)
{
//get a point 2 spaces in the direction
var target = new Point(current.X + dx * 2, current.Y + dy * 2);

//if the target is out of bounds or already carved, skip
if (!rect.Contains(target) || !maze[target.X, target.Y])
continue;

//carve out the wall between the current node and the target
//carve out the target node
maze[current.X + dx, current.Y + dy] = false;
maze[target.X, target.Y] = false;

//push the target node onto the stack
discoveryQueue.Push(target);
carved = true;

//don't look at any more of these neighbors
break;
}

//since we carved, pop the node we peeked
if (!carved)
discoveryQueue.Pop();
}

//end node is not a wall
maze[endNode.X, endNode.Y] = false;

//yield all walls in the maze
foreach (var point in rect.GetPoints())
if (maze[point.X, point.Y])
yield return point;

yield break;

static void ShuffleInPlace<T>(IList<T> arr)
{
for (var i = arr.Count - 1; i > 0; i--)
{
var j = Random.Shared.Next(i + 1);
(arr[i], arr[j]) = (arr[j], arr[i]);
}
}
}

/// <summary>
/// Lazily generates points along the outline of the rectangle. The points will be in the order the vertices are
/// listed.
Expand Down
34 changes: 34 additions & 0 deletions Tests/Chaos.Extensions.Geometry.Tests/RectangleExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,40 @@ public void Contains_Should_Return_True_When_Rectangle_Contains_Same_Rectangle()
.BeTrue();
}

[Fact]
public void GenerateMaze_Should_GeneratePerfectMaze()
{
var rect = new Rectangle(
0,
0,
25,
25);

var start = new Point(12, 24);
var end = new Point(12, 0);

var mazeWalls = rect.GenerateMaze(start, end)
.ToList();

var walkablePoints = rect.GetPoints()
.Except(mazeWalls)
.ToList();

walkablePoints.Should()
.Contain(start);

walkablePoints.Should()
.Contain(end);

walkablePoints.FloodFill(start)
.Should()
.Contain(end);

walkablePoints.FloodFill(end)
.Should()
.Contain(start);
}

[Fact]
public void GetOutline_Should_Return_Correct_Outline_Points_For_Rectangle()
{
Expand Down

0 comments on commit 8b770c2

Please sign in to comment.