# Random Acyclic Maze Generator with given Entry and Exit point

• Last Updated : 10 May, 2022

Given two integers N and M, the task is to generate any N * M sized maze containing only 0 (representing a wall) and 1 (representing an empty space where one can move) with the entry point as P0 and exit point P1 and there is only one path between any two movable positions.

Note: P0 and P1 will be marked as 2 and 3 respectively and one can move through the moveable positions in 4 directions (up, down, right and left).

Examples:

Input: N = 5, M = 5, P0 = (0, 0), P1 = (4, 4)
Output: maze = [ [ 2 1 1 1 1 ],
[ 1 0 1 0 1 ],
[ 1 0 1 0 0 ],
[ 1 1 0 1 0 ],
[ 0 1 1 1 3 ] ]
Explanation: It is valid because there is no cycle,
and there is no unreachable walkable position.
Some other options could be
[ [ 2 1 1 1 1 ],
[ 1 0 1 0 1 ],
[ 1 0 1 0 0 ],
[ 1 1 1 1 0 ],
[ 0 0 0 1 3 ] ]
or
[ [ 2 1 1 0 1 ],
[ 1 0 1 0 1 ],
[ 1 0 1 0 0 ],
[ 1 0 1 1 0 ],
[ 1 0 0 1 3 ] ].
But these are not valid because in the first one there is a cycle in the maze
and in the second one (0, 4) and (1, 4) cannot be reached from the starting point.

Approach: The problem can be solved based on the following idea:

Use a DFS which starts from the P0 position and moves to any of the neighbours but does not make a cycle and ends at P1. In this way, there will only be 1 path between any two movable positions.

Follow the below steps to implement the idea:

• Initialize a stack (S) for the iterative DFS, the matrix that will be returned as the random maze.
• Insert the entry point P0 into the stack.
• While S is not empty, repeat the following steps:
• Remove a position (say P) from S and mark it as seen.
• If marking the position walkable, forms a cycle then don’t include it as a moveable position.
• Otherwise, set the position as walkable.
• Insert the neighbours of P which are not visited in random order into the stack.
• Random insertion in the stack guarantees that the maze being generated is random.
• If any of the neighbours is the same as the P1 then insert it at the top so that we do not skip this position because of cycle formation.
• Mark the initial position P0 (with 2) and final position P1 (with 3)
• Return the maze.

Below is the implementation for the above approach:

## Python3

 `# Python3 code to implement the approach ` ` `  `from` `random ``import` `randint ` ` `  `# Class to define structure of a node ` `class` `Node: ` `    ``def` `__init__(``self``, value ``=` `None``,  ` `               ``next_element ``=` `None``): ` `        ``self``.val ``=` `value ` `        ``self``.``next` `=` `next_element ` ` `  `# Class to implement a stack ` `class` `stack: ` `   `  `    ``# Constructor ` `    ``def` `__init__(``self``): ` `        ``self``.head ``=` `None` `        ``self``.length ``=` `0` ` `  `    ``# Put an item on the top of the stack ` `    ``def` `insert(``self``, data): ` `        ``self``.head ``=` `Node(data, ``self``.head) ` `        ``self``.length ``+``=` `1` ` `  `    ``# Return the top position of the stack ` `    ``def` `pop(``self``): ` `        ``if` `self``.length ``=``=` `0``: ` `            ``return` `None` `        ``else``: ` `            ``returned ``=` `self``.head.val ` `            ``self``.head ``=` `self``.head.``next` `            ``self``.length ``-``=` `1` `            ``return` `returned ` ` `  `    ``# Return False if the stack is empty  ` `    ``# and true otherwise ` `    ``def` `not_empty(``self``): ` `        ``return` `bool``(``self``.length) ` ` `  `    ``# Return the top position of the stack ` `    ``def` `top(``self``): ` `        ``return` `self``.head.val ` ` `  `# Function to generate the random maze ` `def` `random_maze_generator(r, c, P0, Pf): ` `    ``ROWS, COLS ``=` `r, c ` `     `  `    ``# Array with only walls (where paths will  ` `    ``# be created) ` `    ``maze ``=` `list``(``list``(``0` `for` `_ ``in` `range``(COLS))  ` `                       ``for` `_ ``in` `range``(ROWS)) ` `     `  `    ``# Auxiliary matrices to avoid cycles ` `    ``seen ``=` `list``(``list``(``False` `for` `_ ``in` `range``(COLS))  ` `                           ``for` `_ ``in` `range``(ROWS)) ` `    ``previous ``=` `list``(``list``((``-``1``, ``-``1``)  ` `     ``for` `_ ``in` `range``(COLS)) ``for` `_ ``in` `range``(ROWS)) ` ` `  `    ``S ``=` `stack() ` `     `  `    ``# Insert initial position ` `    ``S.insert(P0)  ` ` `  `    ``# Keep walking on the graph using dfs ` `    ``# until we have no more paths to traverse  ` `    ``# (create) ` `    ``while` `S.not_empty(): ` ` `  `        ``# Remove the position of the Stack ` `        ``# and mark it as seen ` `        ``x, y ``=` `S.pop() ` `        ``seen[x][y] ``=` `True` ` `  `        ``# Check if it will create a cycle ` `        ``# if the adjacent position is valid  ` `        ``# (is in the maze) and the position  ` `        ``# is not already marked as a path  ` `        ``# (was traversed during the dfs) and  ` `        ``# this position is not the one before it ` `        ``# in the dfs path it means that  ` `        ``# the current position must not be marked. ` `         `  `        ``# This is to avoid cycles with adj positions ` `        ``if` `(x ``+` `1` `< ROWS) ``and` `maze[x ``+` `1``][y] ``=``=` `1` `\ ` `        ``and` `previous[x][y] !``=` `(x ``+` `1``,  y): ` `            ``continue` `        ``if` `(``0` `< x) ``and` `maze[x``-``1``][y] ``=``=` `1` `\ ` `        ``and` `previous[x][y] !``=` `(x``-``1``,  y): ` `            ``continue` `        ``if` `(y ``+` `1` `< COLS) ``and` `maze[x][y ``+` `1``] ``=``=` `1` `\ ` `        ``and` `previous[x][y] !``=` `(x, y ``+` `1``): ` `            ``continue` `        ``if` `(y > ``0``) ``and` `maze[x][y``-``1``] ``=``=` `1` `\ ` `        ``and` `previous[x][y] !``=` `(x, y``-``1``): ` `            ``continue` ` `  `        ``# Mark as walkable position ` `        ``maze[x][y] ``=` `1` ` `  `        ``# Array to shuffle neighbours before  ` `        ``# insertion ` `        ``to_stack ``=` `[] ` ` `  `        ``# Before inserting any position, ` `        ``# check if it is in the boundaries of  ` `        ``# the maze ` `        ``# and if it were seen (to avoid cycles) ` ` `  `        ``# If adj position is valid and was not seen yet ` `        ``if` `(x ``+` `1` `< ROWS) ``and` `seen[x ``+` `1``][y] ``=``=` `False``: ` `             `  `            ``# Mark the adj position as seen ` `            ``seen[x ``+` `1``][y] ``=` `True` `             `  `            ``# Memorize the position to insert the  ` `            ``# position in the stack ` `            ``to_stack.append((x ``+` `1``,  y)) ` `             `  `            ``# Memorize the current position as its  ` `            ``# previous position on the path ` `            ``previous[x ``+` `1``][y] ``=` `(x, y) ` `         `  `        ``if` `(``0` `< x) ``and` `seen[x``-``1``][y] ``=``=` `False``: ` `             `  `            ``# Mark the adj position as seen ` `            ``seen[x``-``1``][y] ``=` `True` `             `  `            ``# Memorize the position to insert the  ` `            ``# position in the stack ` `            ``to_stack.append((x``-``1``,  y)) ` `             `  `            ``# Memorize the current position as its  ` `            ``# previous position on the path ` `            ``previous[x``-``1``][y] ``=` `(x, y) ` `         `  `        ``if` `(y ``+` `1` `< COLS) ``and` `seen[x][y ``+` `1``] ``=``=` `False``: ` `             `  `            ``# Mark the adj position as seen ` `            ``seen[x][y ``+` `1``] ``=` `True` `             `  `            ``# Memorize the position to insert the  ` `            ``# position in the stack ` `            ``to_stack.append((x, y ``+` `1``)) ` `             `  `            ``# Memorize the current position as its ` `            ``# previous position on the path ` `            ``previous[x][y ``+` `1``] ``=` `(x, y) ` `         `  `        ``if` `(y > ``0``) ``and` `seen[x][y``-``1``] ``=``=` `False``: ` `             `  `            ``# Mark the adj position as seen ` `            ``seen[x][y``-``1``] ``=` `True` `             `  `            ``# Memorize the position to insert the  ` `            ``# position in the stack ` `            ``to_stack.append((x, y``-``1``)) ` `             `  `            ``# Memorize the current position as its  ` `            ``# previous position on the path ` `            ``previous[x][y``-``1``] ``=` `(x, y) ` `         `  `        ``# Indicates if Pf is a neighbour position ` `        ``pf_flag ``=` `False` `        ``while` `len``(to_stack): ` `             `  `            ``# Remove random position ` `            ``neighbour ``=` `to_stack.pop(randint(``0``, ``len``(to_stack)``-``1``)) ` `             `  `            ``# Is the final position,  ` `            ``# remember that by marking the flag ` `            ``if` `neighbour ``=``=` `Pf: ` `                ``pf_flag ``=` `True` `             `  `            ``# Put on the top of the stack ` `            ``else``: ` `                ``S.insert(neighbour) ` `         `  `        ``# This way, Pf will be on the top  ` `        ``if` `pf_flag: ` `            ``S.insert(Pf) ` `                 `  `    ``# Mark the initial position ` `    ``x0, y0 ``=` `P0 ` `    ``xf, yf ``=` `Pf ` `    ``maze[x0][y0] ``=` `2` `    ``maze[xf][yf] ``=` `3` `     `  `    ``# Return maze formed by the traversed path ` `    ``return` `maze ` ` `  `# Driver code ` `if` `__name__ ``=``=` `"__main__"``: ` `    ``N ``=` `5` `    ``M ``=` `5` `    ``P0 ``=` `(``0``, ``0``) ` `    ``P1 ``=` `(``4``, ``4``) ` `    ``maze ``=` `random_maze_generator(N, M, P0, P1) ` `    ``for` `line ``in` `maze: ` `        ``print``(line)`

Output

```[2, 0, 0, 1, 1]
[1, 1, 1, 0, 1]
[0, 0, 1, 1, 1]
[1, 1, 0, 0, 1]
[0, 1, 1, 1, 3]
```

Time Complexity: O(N * M)

• As the algorithm is basically a DFS with more conditions, the time complexity is the time complexity of the DFS: O(V+E) (where V is the number of vertices and E is the number of edges).
• In this case, the number of vertices is the number of squares in the matrix: N * M. Each “vertex” (square) has at most 4 “edges” (4 adjacent squares) so E < 4*N*M. So O(V+E) will be O(5*N*M) i.e. O(N*M).

Auxiliary Space: O(N * M)

• Each auxiliary matrix needs O(N*M) of space.
• The stack can’t have more than N*M squares inside it, because it never holds (in this implementation) the same square more than one time.
• The sum of all space mentioned will be O(N*M).

My Personal Notes arrow_drop_up
Related Articles