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, difficulty=5): 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 = wall_density + (difficulty * 0.08) # More severe increase in wall density adjusted_wall_density = min(adjusted_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, difficulty) elif generation_algorithm == "Prim's": maze = prim_maze(rows, cols, adjusted_wall_density, difficulty) 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, difficulty): 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 = [] # Iterative backtracker logic while stack: r, c = stack[-1] # Get the current cell from the stack 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)) # Add the next cell to the stack carved = True main_path.append((nr, nc)) # Track the main path break # Stop after carving one passage if not carved: stack.pop() # Backtrack if no carving was possible # Add complexity based on difficulty - Branch off from the main path branch_factor = difficulty * 2 # Scale with difficulty (higher means more branches) for _ in range(branch_factor): # Use `_` to indicate we don't need the index if len(main_path) > 2: # Only branch if there’s space 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 false branch break # Stop after adding one branch # Add random walls based on wall_density (avoid adding walls along the main path) 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.05: maze[r][c] = 1 # Add wall return maze def prim_maze(rows, cols, wall_density, difficulty): 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)) # Track main path add_walls(opposite_r, opposite_c) # Add complexity based on difficulty - More branches from the main path branch_factor = difficulty * 2 # More branches with higher difficulty for _ in range(branch_factor): # Use `_` to indicate we don't need the index 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 # 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.05: maze[r][c] = 1 # Add wall 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