import numpy as np import random from collections import deque def generate_maze(rows, cols, generation_algorithm="Recursive Backtracker", seed=None, wall_density=0.3, dead_ends=10, branching_factor=3): if seed is not None: random.seed(seed) np.random.seed(seed) else: seed = random.randint(0, 999999) # Generate a random seed if not provided # Adjust wall density and complexity based on difficulty adjusted_wall_density = min(wall_density, 0.7) # Cap wall density for solvability maze = None is_solved = False # Use a loop to regenerate maze if unsolvable while not is_solved: if generation_algorithm == "Recursive Backtracker": maze = recursive_backtracker_maze(rows, cols, adjusted_wall_density, dead_ends, branching_factor) elif generation_algorithm == "Prim's": maze = prim_maze(rows, cols, adjusted_wall_density, dead_ends, branching_factor) else: raise ValueError(f"Unknown generation algorithm: {generation_algorithm}") # Check if the generated maze is solvable is_solved = is_solvable(maze, (1, 1), (rows - 2, cols - 2)) return maze, seed # Return the generated maze along with the seed def recursive_backtracker_maze(rows, cols, wall_density, dead_ends, branching_factor): maze = np.ones((rows, cols), dtype=int) # Initialize the stack with the starting point stack = [(1, 1)] maze[1][1] = 0 # Start point # Track the main path main_path = [] while stack: r, c = stack[-1] directions = [(2, 0), (-2, 0), (0, 2), (0, -2)] random.shuffle(directions) carved = False for dr, dc in directions: nr, nc = r + dr, c + dc if 0 < nr < rows-1 and 0 < nc < cols-1 and maze[nr][nc] == 1: maze[nr - dr//2][nc - dc//2] = 0 # Remove wall between maze[nr][nc] = 0 # Mark the next cell as a passage stack.append((nr, nc)) carved = True main_path.append((nr, nc)) break if not carved: stack.pop() # Backtrack if no carving was possible # Create branches based on branching_factor for _ in range(branching_factor): if len(main_path) > 2: r, c = random.choice(main_path) directions = [(2, 0), (-2, 0), (0, 2), (0, -2)] random.shuffle(directions) for dr, dc in directions: nr, nc = r + dr, c + dc if 0 < nr < rows-1 and 0 < nc < cols-1 and maze[nr][nc] == 1: maze[nr - dr//2][nc - dc//2] = 0 # Remove wall between maze[nr][nc] = 0 # Create a branch break # Create dead ends based on dead_ends for _ in range(dead_ends): r, c = random.randint(1, rows-2), random.randint(1, cols-2) if maze[r][c] == 0: maze[r][c] = 1 # Add a dead end # Add random walls based on wall_density for r in range(1, rows-1): for c in range(1, cols-1): if maze[r][c] == 0 and random.random() < wall_density * 0.1: maze[r][c] = 1 # Add wall return maze def prim_maze(rows, cols, wall_density, dead_ends, branching_factor): maze = np.ones((rows, cols), dtype=int) start_r, start_c = 1, 1 maze[start_r][start_c] = 0 walls = [] main_path = [(start_r, start_c)] # Track the main path def add_walls(r, c): directions = [(-1,0), (1,0), (0,-1), (0,1)] for dr, dc in directions: nr, nc = r + dr, c + dc if 0 < nr < rows-1 and 0 < nc < cols-1 and maze[nr][nc] == 1: walls.append((nr, nc, r, c)) add_walls(start_r, start_c) while walls: idx = random.randint(0, len(walls)-1) wall = walls.pop(idx) wr, wc, pr, pc = wall opposite_r, opposite_c = wr + (wr - pr), wc + (wc - pc) if 0 < opposite_r < rows-1 and 0 < opposite_c < cols-1: if maze[opposite_r][opposite_c] == 1: maze[wr][wc] = 0 maze[opposite_r][opposite_c] = 0 main_path.append((opposite_r, opposite_c)) add_walls(opposite_r, opposite_c) # Create branches based on branching_factor for _ in range(branching_factor): if len(main_path) > 2: r, c = random.choice(main_path) directions = [(-1,0), (1,0), (0,-1), (0,1)] random.shuffle(directions) for dr, dc in directions: nr, nc = r + dr, c + dc if 0 < nr < rows-1 and 0 < nc < cols-1 and maze[nr][nc] == 1: maze[nr][nc] = 0 # Create a false branch break # Create dead ends based on dead_ends for _ in range(dead_ends): r, c = random.randint(1, rows-2), random.randint(1, cols-2) if maze[r][c] == 0: maze[r][c] = 1 # Add a dead end return maze def is_solvable(maze, start, end): queue = deque([start]) visited = set([start]) while queue: current = queue.popleft() if current == end: return True for neighbor in get_adjacent_cells(current, maze): if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) return False # Helper to get adjacent cells def get_adjacent_cells(cell, maze): row, col = cell directions = [(-1,0), (1,0), (0,-1), (0,1)] neighbors = [] for dr, dc in directions: r, c = row + dr, col + dc if 0 <= r < maze.shape[0] and 0 <= c < maze.shape[1] and maze[r][c] == 0: neighbors.append((r, c)) return neighbors