1

I have a basic backtracking algorithm that generates mazes for me. But sometimes it does not "visit" all tiles/cells. I'm wondering what is wrong, the algorithm does backtrack properly and it should check all directions on each tile/cell but the "unvisited" tiles/cells do not get touched at all.

This is the backtracking algorithm:

void GenerateMaze(Coordinate tilePos)
    {
        //Mark the current position visited
        tileMap[tilePos.x, tilePos.y].visited = true;
        //Randomize directions
        Shuffle<Coordinate>(directions);
        foreach(Coordinate d in directions)
        {
            //Check if the new position is within bounds
            if (tilePos.x + d.x >= 0 && tilePos.x + d.x < mapWidth && tilePos.y + d.y >= 0 && tilePos.y + d.y < mapHeight)
            {
                //Check if the tile is already visited
                if (!tileMap[tilePos.x + d.x, tilePos.y + d.y].visited)
                {
                    //Carve through walls from this tile to next
                    Carve(tilePos, d);
                    //Recursively call this method on the next tile
                    GenerateMaze(new Coordinate(tilePos.x + d.x, tilePos.y + d.y));
                }
            }
        }
    }

In case you are interested, this is the Carve method:

private void Carve(Coordinate position, Coordinate direction)
    {
        if (direction.Equals(new Coordinate(-1, 0)))
        {
            Debug.Log("Carving West from: ");
            tileMap[position.x, position.y].west = true;
            tileMap[position.x + direction.x, position.y + direction.y].east = true;
        }
        else if (direction.Equals(new Coordinate(1, 0)))
        {
            tileMap[position.x, position.y].east = true;
            tileMap[position.x + direction.x, position.y + direction.y].west = true;
        }
        else if (direction.Equals(new Coordinate(0, -1)))
        {
            tileMap[position.x, position.y].south = true;
            tileMap[position.x + direction.x, position.y + direction.y].north = true;
        }
        else if (direction.Equals(new Coordinate(0, 1)))
        {
            tileMap[position.x, position.y].north = true;
            tileMap[position.x + direction.x, position.y + direction.y].south = true;
        }
    }

It just sets the correct wall flags to true depending on the direction the algorithm is going.

In the image bellow you see the maze has 3 "unvisited" tiles. This mostly happens in corners. enter image description here

Here it leaves a single tile untouched but this time not on the sides. enter image description here

On a 10x10 maze this seems to happen about 1/10 times. The problem tiles stay unvisited so the algorithm does not process them at all. But since it travels past them and every direction of there neighbors is tested they really should be joining the maze. So what can be wrong?

Madmenyo
  • 8,389
  • 7
  • 52
  • 99
  • ehm .... it seems to me that you will only visit tiles where your (pseudo-) random walk brings you to - so if you cannot come back to a cell because everything around it is already visited you will not visit it ... the *backtrack* will not help you as your visited flags are global so the actions in one branch will still be there after the backtrack (please don't use global-state with recursive functions) – Random Dev Mar 30 '15 at 09:37
  • @CarstenKönig Not sure what you mean. It should be visiting every cell and check all the directions of every cell. If a valid move/direction is found it should always come back eventually to check for the other directions. Here is a ruby implementation: http://weblog.jamisbuck.org/2010/12/27/maze-generation-recursive-backtracking – Madmenyo Mar 31 '15 at 11:53
  • @CarstenKönig If I simply do `GenerateMaze(new Coordinate(x + 1, y));` on a unconnected cell it fixes the problem. But why didn't it fix it on the way back in the recursion? Each direction should be checked so it should have caught the unvisited cell. – Madmenyo Mar 31 '15 at 12:02
  • I completely second @CarstenKönig this check `if (!tileMap[tilePos.x + d.x, tilePos.y + d.y].visited)` doesn't appear in the buckblog source and watching the run through of the algorithm on the webpage it seems to have no issue revisiting already carved out nodes until all nodes are visited. – Dead.Rabit Apr 08 '15 at 15:53
  • Can you share what is the content of `directions`? – Pham Trung Apr 09 '15 at 08:20

1 Answers1

3

The problem is

Shuffle<Coordinate>(directions);

At each step, you shuffle the content in directions

But, also remember that, in each step, you iterate through every coordinate in directions

foreach(Coordinate d in directions)
{
     //Visit child node
}

So,because you are discovering the matrix using DFS style, thus, while you are iterating the directions in the parent node, you also visit all its child node. And again, shuffling the directions while visiting each child, this may randomly destroy the iterating process in parent node by messing up the current order of element in directions.

Simple example

In parent, directions order is (0,1,2,3)

Visit first child (direction 0)-> shuffle directions (1,0,2,3)

Go back to parent node, now you will skip one node (direction 1), as the directions content has been changed.

Change this DFS into BFS will fix this problem.

Pseudo code:

Queue<Coordinate> q;
q.add(start)
while(q is not empty){
    Coordinate point = q.dequeue();
    shuffle directions
    for(each direction in directions){
        Add unvisited child node into q
    }
}
Pham Trung
  • 11,204
  • 2
  • 24
  • 43
  • Off course this just might be it. Never worked with `Queu`, I could also copy the direction `List`. Using Queue must be faster, never used it though. Will have a look in a couple of hours. – Madmenyo Apr 09 '15 at 09:44
  • One thing that jumps in my mind is that if the direction list of the parent shuffles along with the direction of the child shouldn't it leave "blank" cells a lot more often? Isn't the `foreach` loop already "made" for the parent? – Madmenyo Apr 09 '15 at 09:54
  • 1
    @MennoGouw I think it is depend on the type of collection you are using for `directions`, [Link](http://stackoverflow.com/questions/11179156/how-is-foreach-implemented-in-c). If it is List, as far as I understood when taking a look at the [documentation](https://msdn.microsoft.com/en-us/library/b0yss765.aspx) : `An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined.`, so the behavior cannot be predicted in our case. – Pham Trung Apr 09 '15 at 10:05
  • Points well spent, thanks for offering the solution with `Queue` this seems the best solution since cloning or deep copying the complete list is over-kill especially on a large grid. Currently I just tested it by initializing the list inside the recursive method and shuffling it there as well. – Madmenyo Apr 09 '15 at 16:32