Friday, May 07, 2004

Let's have some fun with graph theory. Today, I'm showing how to generate a random maze with Quickgraph. Before going into the code, let see how we are going to make it.

The maths

Consider that you have a regular lattice like in the image below. If you consider each edge as a path, the lattice represents a highly connected network. The question is: which edge should you delete to make it a maze ?

The answer to this questions is trivial in terms of graph theory: you just need to extract a tree out of the graph and this will be a maze.  In fact in a tree, there is only 1 way from the root to each of the leaf, like in mazes. This leaves us with the problem of extracting a tree from a graph.

The maze generation example was originaly brought up by David Wilson (a researcher from Microsoft). He has designed an efficient algorithm, known as Cycle-Popping Tree Generator, for extracting random tree out of graphs. This algorithm is implemented in QuickGraph so we have all the tools on our hand to start solving the problem.

The code

Let's start by building the graph and the lattice. For the cycle popping algorithm, we need a BidirectionalGraph (a graph where you can iterate the out-edges and the in-edges of the vertices). For the lattice, we create a two dimensional array of vertices and finally, we store the vertex -> lattice index relation into a Hashtable:

// We need a few namespaces for QuickGraph 
using QuickGraph.Concepts;
using QuickGraph.Concepts.Traversals;
using QuickGraph.Algorithms.RandomWalks;
using QuickGraph;
using QuickGraph.Providers;
using QuickGraph.Collections;
using QuickGraph.Representations;

public class MazeGenerator
{
    // the bidirecitonal graph
    private BidirectionalGraph graph = null;
    // 2D array of vertices
    private IVertex[,] latice;
    // v -> (i,j)
    private Hashtable vertexIndices = null;

Next, we write the method that will populate the graph. If the lattice has m rows and n columns, the method will create m*n vertices and (m-1)*(n-1)*2 edges to link those vertices:

public void GenerateGraph(int rows, int columns)
{
    this.latice=new IVertex[rows,columns];
    this.vertexIndices = new Hashtable();
    this.graph = new BidirectionalGraph(false); // false = does not allow parallel edges

    // adding vertices
    for(int i=0;icolumns;++j)
        {
            IVertex v =this.graph.AddVertex();
            this.latice[i,j] = v;
            this.vertexIndices[v]=new DictionaryEntry(i,j);
        }
    }

    // adding edges
    for(int i =0;icolumns-1;++j)
        {
            this.graph.AddEdge(latice[i,j], latice[i,j+1]);
            this.graph.AddEdge(latice[i,j+1], latice[i,j]);
            this.graph.AddEdge(latice[i,j], latice[i+1,j]);
            this.graph.AddEdge(latice[i+1,j], latice[i,j]);
        }
    }
    ...

There is a bit of index gymnastics in the code above, but drawing it on a sheet of paper should make things clear. I will not focus on the drawing code on this blog: I'm using basic features of System.Drawing namespace, nothing really "extreme". So back to our maze problem, we need to apply the cycle-popping algorithm to generate a maze.

Cycle-popping algorithm

The cycle poping algorithm is implement in the QuickGraph.Algorithms.RandomWalks.CyclePoppingRandomTreeAlgorithm class. Using this algorithm is quite straightforward:

// (outi,outj) is the coordinate of the exit vertex in the lattice
// (ini,inj) is the coordinate of the entry vertex in the lattice
public void GenerateWalls(int outi, int outj, int ini, int inj)
{
    // we build the algo and attach it to the graph
    CyclePoppingRandomTreeAlgorithm pop = new CyclePoppingRandomTreeAlgorithm(this.graph);

    // finding the root vertex
    IVertex root = this.latice[outi,outj];
    if (root==null)
        throw new ArgumentException("outi,outj vertex not found");

    // this lines lanches the tree generation 
    pop.RandomTreeWithRoot(root);

At this point, pop contains a dictionary which associates each vertex to it's successor edge in the tree. Remember that the tree is directed towards the root here. This dictionary has two roles: it contains the edges that are part of the maze (that's what we needed) and even better you can build the way-out using the dictionary by following the successors until you hit the root:

    // we add two fields to the class:
    private VertexEdgeDictionary successors = null;
    private EdgeCollection outPath = null;
    ...
    ///////////////////////////////////
    // GenerateMaze continued
    // storing the successors
    this.successors = pop.Successors;

    // build the path to ini, inj
    IVertex v = this.latice[ini,inj];
    if (v==null)
    throw new ArgumentException("ini,inj vertex not found");

    // building the solution path
    this.outPath = new EdgeCollection();
    while (v!=root)
    {
        IEdge e = this.successors[v];
        this.outPath.Add(e);
        v = e.Target;
    }
}

That's it. We can now draw the walls of the maze and draw the solution on top. Thank you Mister Propp and Mister Wilson! (The source of this example is available in the QuickGraph CVS)

posted on Friday, May 07, 2004 11:13:00 PM UTC  #    Comments [8]
Tracked by:
"gwar mp3s" (online) [Trackback]
"leather sheaths" (online) [Trackback]
"spyro cheat codes" (online) [Trackback]
Monday, June 06, 2005 6:05:34 PM UTC
&lt;h2&gt;Labirintusok&lt;/h2&gt;<br>A &lt;a target=&quot;_blank&quot; href=&quot;http://blog.dotnetwiki.org&quot;&gt;http://blog.dotnetwiki.org&lt;/a&gt; blogban olvastam egy rdekes cikket a labirintusksztsrl:<br>&lt;br/&gt;<br>&lt;ul&gt;<br>&quot;The answer to this questions is trivial in terms of graph theory: you just need to extract a tree out of the graph and this will be a maze. In fact in a tree, there is only 1 way from the root to each of the leaf, like in mazes. This leaves us with the problem of extracting a tree from a graph.&quot;<br>&lt;/ul&gt;<br>&lt;br/&gt;<br>A teljes cikk: &lt;a target=&quot;_blank&quot; href=&quot;http://blog.dotnetwiki.org/archive/2004/05/07/187.aspx&quot;&gt;http://blog.dotnetwiki.org/archive/2004/05/07/187.aspx&lt;/a&gt;
ghyBlog
Monday, June 06, 2005 6:05:35 PM UTC
Could this library support a 4D type maze. A true world mazy with levels?
Shnubby
Monday, June 06, 2005 6:05:35 PM UTC
If you can represent your 4D graph as a connected graph, the answer is yes.
Jonathan de Halleux
Monday, June 06, 2005 6:05:39 PM UTC
The source code is different then what is written here. There is a RandomWalks reference in the Algorithms Library there. My current libraries don't contain the RandomWalks class.
Rich
Monday, June 06, 2005 6:05:42 PM UTC
Actually I'm using the <a title="MbUnit, Generating Unit Testing and Model Based Testing Framework for .NET Framework" href="http://mbunit.tigris.org" target="_blank">MbUnit</a> source now and found the library
<br>
<br>Thanks
Rich
Monday, June 06, 2005 6:05:42 PM UTC
Hey, any easy way to guarantee that the unique path from start to finish has a minimum length?
Greg Toller
Monday, June 06, 2005 6:05:52 PM UTC
Not with this algorithm (randomized algo).
<br>
<br>You could do some post processing to introduce loops or isolate branches and reroute the solution through this branch.
Jonathan de Halleux
Monday, June 06, 2005 6:05:53 PM UTC
Please let us know when the <a title="TestDriven.NET" href="http://www.testdriven.net" target="_blank">TestDriven.NET</a> site has the source for this. I lost my other tigris files.
<br>Thanks
Rich
Comments are closed.