Friday, September 20, 2024
Google search engine
HomeData Modelling & AIRandom Acyclic Maze Generator with given Entry and Exit point

Random Acyclic Maze Generator with given Entry and Exit point

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)


Javascript




// JavaScript code to implement the approach
 
// Class to define structure of a node
class Node {
    constructor(value = null, next_element = null) {
        this.val = value;
        this.next = next_element;
    }
}
 
// Class to implement a stack
class Stack {
    // Constructor
    constructor() {
        this.head = null;
        this.length = 0;
    }
 
    // Put an item on the top of the stack
    insert(data) {
        this.head = new Node(data, this.head);
        this.length += 1;
    }
 
    // Return the top position of the stack
    pop() {
        if (this.length === 0) {
            return null;
        } else {
            let returned = this.head.val;
            this.head = this.head.next;
            this.length -= 1;
            return returned;
        }
    }
 
    // Return False if the stack is empty
    // and true otherwise
    not_empty() {
        return Boolean(this.length);
    }
 
    // Return the top position of the stack
    top() {
        return this.head.val;
    }
}
 
// Function to generate the random maze
function random_maze_generator(r, c, P0, Pf) {
    const ROWS = r;
    const COLS = c;
    // Array with only walls (where paths will
    // be created)
    const maze = [...Array(ROWS)].map(() => Array(COLS).fill(0));
 
    // Auxiliary matrices to avoid cycles
    const seen = [...Array(ROWS)].map(() => Array(COLS).fill(false));
    const previous = [...Array(ROWS)].map(() => Array(COLS).fill([-1, -1]));
    const S = [];
 
    // Insert initial position
    S.push(P0);
 
    // Keep walking on the graph using dfs
    // until we have no more paths to traverse
    // (create)
    while (S.length > 0) {
        // Remove the position of the Stack
        // and mark it as seen
        const [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 && maze[x + 1][y] === 1 && !isEqual(previous[x][y], [x + 1, y])) continue;
        if (x > 0 && maze[x - 1][y] === 1 && !isEqual(previous[x][y], [x - 1, y])) continue;
        if (y + 1 < COLS && maze[x][y + 1] === 1 && !isEqual(previous[x][y], [x, y + 1])) continue;
        if (y > 0 && maze[x][y - 1] === 1 && !isEqual(previous[x][y], [x, y - 1])) continue;
 
        // Mark as walkable position
        maze[x][y] = 1;
        // Array to shuffle neighbours before
        // insertion
        const 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 && !seen[x + 1][y]) {
            // Mark the adj position as seen
            seen[x + 1][y] = true;
            // Memorize the position to insert the
            // position in the stack
            to_stack.push([x + 1, y]);
            // Memorize the current position as its
            // previous position on the path
            previous[x + 1][y] = [x, y];
        }
 
        if (x > 0 && !seen[x - 1][y]) {
            // Mark the adj position as seen
            seen[x - 1][y] = true;
 
            // Memorize the position to insert the
            // position in the stack
            to_stack.push([x - 1, y]);
 
            // Memorize the current position as its
            // previous position on the path
            previous[x - 1][y] = [x, y];
        }
 
        if (y + 1 < COLS && !seen[x][y + 1]) {
            // Mark the adj position as seen
            seen[x][y + 1] = true;
            // Memorize the position to insert the
            // position in the stack
            to_stack.push([x, y + 1]);
            // Memorize the current position as its
            // previous position on the path
            previous[x][y + 1] = [x, y];
        }
 
        if (y > 0 && !seen[x][y - 1]) {
            // Mark the adj position as seen
            seen[x][y - 1] = true;
            // Memorize the position to insert the
            // position in the stack
            to_stack.push([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
        let pf_flag = false;
 
        while (to_stack.length > 0) {
            const index = Math.floor(Math.random() * to_stack.length);
            const neighbour = to_stack.splice(index, 1)[0];
 
            // Is the final position,
            // remember that by marking the flag
            if (isEqual(neighbour, Pf)) {
                pf_flag = true;
            } else {
                // Put on the top of the stack
 
                S.push(neighbour);
            }
        }
 
        // This way, Pf will be on the top
        if (pf_flag) {
            S.push(Pf);
        }
    }
 
    // Mark the initial position
    const [x0, y0] = P0;
    const [xf, yf] = Pf;
    maze[x0][y0] = 2;
    maze[xf][yf] = 3;
 
    // Return maze formed by the traversed path
    return maze;
}
 
// Helper function to compare two arrays for equality
function isEqual(arr1, arr2) {
    return arr1[0] === arr2[0] && arr1[1] === arr2[1];
}
 
// Driver code
const N = 5;
const M = 5;
const P0 = [0, 0];
const P1 = [4, 4];
const maze = random_maze_generator(N, M, P0, P1);
for (const line of maze) {
    console.log(line);
}
 
// Contributed by adityasharmadev01


C++




#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
using namespace std;
 
const int WALL = 0;
const int EMPTY = 1;
const int START = 2;
const int END = 3;
const int DX[] = {0, 0, 1, -1};
const int DY[] = {1, -1, 0, 0};
 
class MazeGenerator {
private:
    int n, m;
    vector<vector<int>> maze;
    mt19937 random;
 
public:
    MazeGenerator(int n, int m, vector<int>& start, vector<int>& end) {
        this->n = n;
        this->m = m;
        this->maze = vector<vector<int>>(n, vector<int>(m, WALL));
        this->random = mt19937(random_device()());
 
        // Initialize maze with walls
        for (int i = 0; i < n; i++) {
            fill(maze[i].begin(), maze[i].end(), WALL);
        }
 
        // Set start and end points
        maze[start[0]][start[1]] = START;
        maze[end[0]][end[1]] = END;
 
        // Generate maze
        dfs(start[0], start[1]);
 
        // Print maze (for debugging purposes)
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                cout << maze[i][j] << " ";
            }
            cout << endl;
        }
    }
 
private:
    void dfs(int x, int y) {
        vector<int> directions = {0, 1, 2, 3};
        shuffle(directions.begin(), directions.end(), random);
 
        for (int d : directions) {
            int nx = x + DX[d];
            int ny = y + DY[d];
 
            if (nx < 0 || nx >= n || ny < 0 || ny >= m || maze[nx][ny] != WALL) {
                continue;
            }
 
            // Carve path
            maze[x + DX[d] / 2][y + DY[d] / 2] = EMPTY;
            maze[nx][ny] = EMPTY;
 
            // Recursively visit next cell
            dfs(nx, ny);
        }
    }
};
 
int main() {
    int n = 5, m = 5;
    vector<int> start = {0, 0}, end = {4, 4};
    MazeGenerator maze(n, m, start, end);
    return 0;
}
 
//This code is contributed by Akash Jha


Java




import java.util.*;
 
public class MazeGenerator {
    private static final int WALL = 0;
    private static final int EMPTY = 1;
    private static final int START = 2;
    private static final int END = 3;
    private static final int[] DX = {0, 0, 1, -1};
    private static final int[] DY = {1, -1, 0, 0};
    private final int n, m;
    private final int[][] maze;
    private final Random random;
 
    public MazeGenerator(int n, int m, int[] start, int[] end) {
        this.n = n;
        this.m = m;
        this.maze = new int[n][m];
        this.random = new Random();
 
        // Initialize maze with walls
        for (int i = 0; i < n; i++) {
            Arrays.fill(maze[i], WALL);
        }
 
        // Set start and end points
        maze[start[0]][start[1]] = START;
        maze[end[0]][end[1]] = END;
 
        // Generate maze
        dfs(start[0], start[1]);
 
        // Print maze (for debugging purposes)
        for (int i = 0; i < n; i++) {
            System.out.println(Arrays.toString(maze[i]));
        }
    }
 
    private void dfs(int x, int y) {
        List<Integer> directions = Arrays.asList(0, 1, 2, 3);
        Collections.shuffle(directions, random);
 
        for (int d : directions) {
            int nx = x + DX[d];
            int ny = y + DY[d];
 
            if (nx < 0 || nx >= n || ny < 0 || ny >= m || maze[nx][ny] != WALL) {
                continue;
            }
 
            // Carve path
            maze[x + DX[d] / 2][y + DY[d] / 2] = EMPTY;
            maze[nx][ny] = EMPTY;
 
            // Recursively visit next cell
            dfs(nx, ny);
        }
    }
 
    public static void main(String[] args) {
        int n = 5, m = 5;
        int[] start = {0, 0}, end = {4, 4};
        MazeGenerator maze = new MazeGenerator(n, m, start, end);
    }
}
//This code is contributed by Akash Jha


C#




using System;
using System.Collections.Generic;
 
class MazeGenerator {
    private const int WALL = 0;
    private const int EMPTY = 1;
    private const int START = 2;
    private const int END = 3;
    private readonly int n, m;
    private readonly int[, ] maze;
    private readonly Random random;
 
    public MazeGenerator(int n, int m, int[] start,
                         int[] end)
    {
        this.n = n;
        this.m = m;
        this.maze = new int[n, m];
        this.random = new Random();
 
        // Initialize maze with walls
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                maze[i, j] = WALL;
            }
        }
 
        // Set start and end points
        maze[start[0], start[1]] = START;
        maze[end[0], end[1]] = END;
 
        // Generate maze
        dfs(start[0], start[1]);
 
        // Print maze (for debugging purposes)
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                Console.Write(maze[i, j] + " ");
            }
            Console.WriteLine();
        }
    }
 
    private void dfs(int x, int y)
    {
        List<int> directions
            = new List<int>() { 0, 1, 2, 3 };
        directions.Sort((a, b) =
                            > random.Next(2) == 0 ? -1 : 1);
 
        foreach(int d in directions)
        {
            int nx = x + DX[d];
            int ny = y + DY[d];
 
            if (nx < 0 || nx >= n || ny < 0 || ny >= m
                || maze[nx, ny] != WALL) {
                continue;
            }
 
            // Carve path
            maze[x + DX[d] / 2, y + DY[d] / 2] = EMPTY;
            maze[nx, ny] = EMPTY;
 
            // Recursively visit next cell
            dfs(nx, ny);
        }
    }
 
    private static readonly int[] DX = { 0, 0, 1, -1 };
    private static readonly int[] DY = { 1, -1, 0, 0 };
}
 
class Program {
    static void Main(string[] args)
    {
        int n = 5, m = 5;
        int[] start = new int[] { 0, 0 },
              end = new int[] { 4, 4 };
        MazeGenerator maze
            = new MazeGenerator(n, m, start, end);
    }
}
 
//This code is contributed by Akash Jha


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). 

Feeling lost in the world of random DSA topics, wasting time without progress? It’s time for a change! Join our DSA course, where we’ll guide you on an exciting journey to master DSA efficiently and on schedule.
Ready to dive in? Explore our Free Demo Content and join our DSA course, trusted by over 100,000 neveropen!

RELATED ARTICLES

Most Popular

Recent Comments