You get a bonus - 1 coin for daily activity. Now you have 1 coin

Algorithms for Generating and Traversing a Perfect Maze

Lecture



Maze (Anc. Greek λαβύρινθος) — a structure (usually in two- or three-dimensional space) consisting of confusing paths to an exit (and/or paths leading to dead ends).

Among the ancient Greeks and Romans, a labyrinth referred to a more or less extensive space consisting of numerous halls, chambers, courtyards, and passages arranged according to a complex and confusing plan, with the purpose of confusing an uninitiated person and preventing them from finding a way out. In a broader sense, a labyrinth can represent a deadlock situation or a matter from which it is very difficult to find a way out.

It is believed that if you traverse a maze while keeping one hand on one of its walls, the maze will always be solved, although this is not always true: in a maze with disconnected walls, this method may not work.

Maze generation algorithms

  • Eller's algorithm
  • Naive algorithm
  • Maze on a table
  • BSP trees
  • Maze generation using a cellular automaton
  • Depth-first graph search method
  • The "Sidewinder" algorithm
  • Wilson's algorithm
  • Aldous-Broder algorithm
  • Application of maze generation and traversal algorithms in real life

Eller's algorithm

Eller's algorithm — is a mathematical generator that allows creating mazes in which there is a single path between every two points, meaning the mazes contain no cycles. Compared to other generators, this algorithm is one of the fastest and requires a small amount of RAM — proportional to the length of the maze's row. It only needs to store the last generated row in memory, which allows generating mazes with an unlimited number of rows.

Eller's algorithm is an algorithm for generating a perfect maze. A maze is considered perfect if it has no closed or looped sections, and there is exactly one path from any point to any other point.

The algorithm is a loop that adds new rows. A row contains the same number of cells, which is arbitrarily set at the beginning. Cells belong to sets, which are used to control the possibility of passage between cells. At the moment the current row is generated, cells belonging to the same set are connected to each other, while cells from different sets are located in parts of the maze isolated from one another. In general, the maze's walls are generated randomly, but subject to certain rules that guarantee the absence of loops.

Description of the algorithm

  1. Create the first row. No cell will be part of any set.

  2. Assign a unique set to each cell that doesn't belong to a set.

  3. Create right-hand borders, moving from left to right:

    1. Randomly decide whether to add a border or not

      1. If the current cell and the cell to the right belong to the same set, create a border between them (to prevent loops)

      2. If you decide not to add a border, merge the two sets that the current cell and the cell to the right belong to.

  4. Create bottom borders, moving from left to right:

    • Randomly decide whether to add a border or not. Make sure that every set has at least one cell without a bottom border (to prevent isolated regions)

      1. If a cell is alone in its set, do not create a bottom border

      2. If a cell is the only one in its set without a bottom border, do not create a bottom border

  5. Decide whether you will continue adding rows or want to finish the maze

    1. If you want to add another row, then:

      1. Output the current row

      2. Remove all right-hand borders

      3. Remove cells with a bottom border from their set

      4. Remove all bottom borders

      5. Continue from step 2

    2. If you decide to finish the maze, then:

      1. Add a bottom border to every cell

      2. Moving from left to right:

        1. If the current cell and the cell to the right are members of different sets, then:

          1. Remove the right-hand border

          2. Merge the sets of the current cell and the cell to the right

          3. Output the final row

Depth-first graph search method for maze generation.

Generating and solving a maze using the depth-first graph search method. Illustration of the algorithm's operation.

1. Create the initial matrix. Algorithms for Generating and Traversing a Perfect Maze
2. Choose the starting point. Algorithms for Generating and Traversing a Perfect Maze
3. Move to a random neighboring unvisited cell, as long as such cells exist. Algorithms for Generating and Traversing a Perfect Maze
4. If there are no unvisited neighboring cells, backtrack through the stack Algorithms for Generating and Traversing a Perfect Maze
5. There are unvisited neighboring cells. Move to a random unvisited neighboring cell. Algorithms for Generating and Traversing a Perfect Maze
6. No unvisited cells left. The maze is generated. Algorithms for Generating and Traversing a Perfect Maze

Algorithms for Generating and Traversing a Perfect Maze

Algorithms for Generating and Traversing a Perfect Maze

Program code



Let's proceed step by step, first generating the initial matrix that the algorithm will work with.
For convenience, let's agree that all cell types are defined in an enumeration.

int maze[height][width]; //create the matrix - a two-dimensional array
for(i = 0; i < height; i++){
        for(j = 0; j < width; j++){
            if((i % 2 != 0  && j % 2 != 0) && //if the cell is odd in x and y,
               (i < height-1 && j < width-1))   //and lies within the maze walls
                   maze[i][j] = CELL;       //then it is a CELL
            else maze[i][j] = WALL;           //otherwise it is a WALL.
        }
    }


Now that all the preparations are done, we can proceed to generation.

typedef struct cell{ //structure storing a cell's coordinates in the matrix
    unsigned int x;
    unsigned int y;
} cell;

typedef struct cellString{
    cell* cells;
    unsigned int size;
} cellString;


These structures will greatly simplify exchanging information between functions.

The code snippet responsible for generation:

cell startCell = {1, 1}
cell currentCell = startCell;
cell neighbourCell;
do{
    cellString Neighbours = getNeighbours(width, height, maze, startPoint, 2);
    if(Neighbours.size != 0){ //if the cell has unvisited neighbours
        randNum  = randomRange(0, Neighbours.size-1);
        neighbourCell = cellStringNeighbours.cells[randNum]; //pick a random neighbour
        push(d.startPoint); //put the current point on the stack
        maze = removeWall(currentCell, neighbourCell, maze); //remove the wall between the current and neighbouring points
        currentCell = neighbourCell; //make the neighbouring point the current one and mark it visited
        maze = setMode(d.startPoint, d.maze, VISITED);
        free(cellStringNeighbours.cells);
    }
    else if(stackSize > 0){ //if there are no neighbours, go back to the previous point
        startPoint = pop();
    }
    else{ //if there are no neighbours and no points on the stack, but not all points have been visited, choose a random unvisited one
        cellString cellStringUnvisited = getUnvisitedCells(width, height, maze);
        randNum = randomRange(0, cellStringUnvisited.size-1);
        currentCell = cellStringUnvisited.cells[randNum];
        free(cellStringUnvisited.cells);
    }
while(unvisitedCount() > 0);


As you can see, the implementation of the algorithm is simple and abstracted from theory, as they say, «even a child could handle it».
So as not to overload the article, the code of the functions used in the excerpt above is placed under a spoiler.

The getNeighbours function returns an array of a cell's unvisited neighbours

cellString getNeighbours(unsigned int width, unsigned int height, int** maze, cell c){
    unsigned int i;
    unsigned int x = c.x;
    unsigned int y = c.y;
    cell up = {x, y - distance};
    cell rt = {x + distance, y};
    cell dw = {x, y + distance};
    cell lt = {x - distance, y};
    cell d[4]  = {dw, rt, up, lt};
    unsigned int size = 0;

    cellString cells;
    cells.cells = malloc(4 * sizeof(cell));

    for(i = 0; i < 4; i++){ //for each direction
        if(d[i].x > 0 && d[i].x < width && d[i].y > 0 && d[i].y < height){ //if it does not go beyond the boundaries of the maze
            unsigned int mazeCellCurrent = maze[d[i].y][d[i].x];
            cell     cellCurrent     = d[i];
            if(mazeCellCurrent != WALL && mazeCellCurrent != VISITED){ //and is not visited / is not a wall
                cells.cells[size] = cellCurrent; //write it into the array;
                size++;
            }
        }
    }
    cells.size = size;
    return cells;


The removeWall function removes the wall between two cells:

mazeMatrix removeWall(cell first, cell second, int** maze){
    short int xDiff = second.x - first.x;
    short int yDiff = second.y - first.y;
    short int addX, addY;
    cell target;

    addX = (xDiff != 0) ? (xDiff / abs(xDiff)) : 0;
    addY = (yDiff != 0) ? (yDiff / abs(yDiff)) : 0;

    target.x = first.x + addX; //wall coordinates
    target.y = first.y + addY;

    maze[target.y][target.x] = VISITED;
    return maze;
}

Binary tree algorithm

Formal algorithm (for a north-east bias):

  1. Choose a starting cell;
  2. Choose a random direction for carving a path. If the neighbouring cell in that direction is outside the field's boundaries, carve the cell in the only possible direction;
  3. Move to the next cell;
  4. Repeat 2-3 until all cells have been processed;

Pros:

  • Simple implementation;
  • High speed;
  • Ability to generate infinite mazes;


Cons:

  • Low pattern complexity;
  • Strong diagonal bias;
  • No dead ends along the bias direction;
  • Uniformity of generated mazes;

The «Sidewinder» algorithm

Formal algorithm (for a standard bias):
  1. Choose a starting row;
  2. Choose a starting cell of the row and make it current;
  3. Initialize an empty set;
  4. Add the current cell to the set;
  5. Decide whether to carve a path to the right;
  6. If carved, move to the new cell and make it current. Repeat steps 3-6;
  7. If not carved, choose a random cell from the set and carve a path upward from it. Move to the next row and repeat 2-7;
  8. Continue until every row has been processed;

Pros:

  • Ability to generate infinite mazes;
  • Only 1 empty corridor;
  • More complex pattern compared to the binary tree algorithm;


Cons:

  • More complicated implementation;
  • No dead ends along the bias direction;
  • Strong vertical bias;

Wilson's algorithm

Formal algorithm:

  1. Choose a random vertex not belonging to the spanning tree and add it to the tree;
  2. Choose a random vertex not belonging to the spanning tree and begin traversing the graph (maze) until reaching a vertex already added to the tree; If a cycle is formed, remove it;
  3. Add all vertices of the resulting subgraph to the spanning tree;
  4. Repeat steps 2-3 until all vertices have been added to the spanning tree.

Pros:

  • There is no bias whatsoever;
  • The mazes are completely random, so it is impossible to create a definite algorithm for solving them;
  • Difficulty of solving for a human;
  • No pointless wandering;
  • Speed compared to Aldous-Broder is many times greater;


Cons:

  • Non-trivial implementation;
  • Speed drops at the start of generation;
  • Greater memory requirements than Aldous-Broder;

Aldous-Broder algorithm

Formal algorithm:

  1. Choose a random vertex (cell). Completely random;
  2. Choose a random neighbouring vertex (cell) and move to it. If it has not been visited, add it to the tree (connect it to the previous one, remove the wall between them);
  3. Repeat step 2 until all cells have been visited.

Pros:

  • There is no bias whatsoever;
  • The mazes are completely random, so it is impossible to create a definite algorithm for solving them;
  • Difficulty of solving for a human;
  • Simple implementation;


Cons:

  • Speed. While the maze is being generated, you will have time to grow old and die;
  • Does not allow generating infinite mazes;
  • Strong drop in efficiency toward the end of generation;

Applications of maze generation and solving algorithms in real life

Maze generation and solving algorithms are widely used in various areas of real life. Here are a few examples:

Autonomous vehicles:

  1. Indoor navigation: Robots and self-driving cars use maze-traversal algorithms for navigation inside buildings. This can be useful in large shopping malls, warehouses, medical facilities, and other locations.

  2. Obstacle detection: Obstacle-avoidance algorithms help vehicles avoid collisions with obstacles in real time, which is critical for ensuring safety in densely populated areas.

Industry:

  1. Logistics and warehousing: Automated warehouse systems can use maze-traversal algorithms to optimize the routes for moving goods or transport robots inside a warehouse.

  2. Robot assembly: Maze-traversal algorithms can help robots assemble complex devices where precise and optimal movement between different components is required.

Medicine:

  1. Surgical robots: In surgery, algorithms can be used that allow robots to effectively navigate through complex anatomical structures to perform operations with minimal impact on surrounding tissue.

Gaming industry:

  1. AI-powered gaming applications: In video games, maze-generation algorithms can create unique levels, providing variety and challenge for players.

Computer security:

  1. Network security testing: Maze-traversal algorithms can be applied to test the security of computer systems, searching for possible attack paths or ways to bypass protective mechanisms.

Energy:

  1. Surveying hard-to-reach deposits: Robots equipped with maze-traversal algorithms can be used to explore hard-to-reach energy resource deposits.

Architecture and design:

  1. Optimizing room layouts: In architecture and design, maze-generation algorithms can be used to optimize room layouts and create unique architectural solutions.

These examples highlight the wide range of fields in which maze-generation and maze-traversal algorithms can be useful for solving various problems.

Algorithms for Generating and Traversing a Perfect Maze

Maze-generation and maze-traversal algorithms are a powerful tool that finds application in a wide variety of real-world fields. As an important element of modern technology, they make it possible to solve complex problems in autonomous vehicles, industry, medicine, the gaming industry, computer security, energy, and architecture.

Maze generation can be used to create unique scenarios in video games, optimize warehouse operations, analyze network security, and even design architectural solutions. On the other hand, maze-traversal algorithms are important for robot navigation inside buildings, real-time obstacle avoidance, and for automating processes in surgery and industry.

These algorithms are not only capable of effectively solving problems in specific fields, but also possess versatility, which makes them an important component of various technological research efforts and applications in the modern world

See also

  • [[b75]]

See also

    Comments

    To leave a comment

    If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
    To reply

    Lectures and tutorial on "Algorithms"

    Terms: Algorithms