def generate(self): grid = MazeArray(self.H, self.W) current = (randrange(1, self.H, 2), randrange(1, self.W, 2)) grid[current] = 0 active = self._find_neighbors(current, grid, True) active = [current] # continue until you have no more neighbors to move to while active: if random() < self.backtrack_chance: current = active[-1] else: current = choice(active) # find a visited neighbor next_neighbors = self._find_neighbors(current, grid, True) if len(next_neighbors) == 0: active = [a for a in active if a != current] continue nn = choice(next_neighbors) active += [nn] grid[nn] = 0 grid[(current[0] + nn[0]) // 2, (current[1] + nn[1]) // 2] = 0 return grid
def generate(self): grid = MazeArray(self.H, self.W) # The first row is always empty, because you can't carve North for col in range(1, self.W - 1): grid[(1, col)] = 0 # loop through the remaining rows and columns for row in range(3, self.H, 2): # create a run of cells run = [] for col in range(1, self.W, 2): # remove the wall to the current cell grid[row, col] = 0 # add the current cell to the run run.append((row, col)) carve_east = random() > self.bias # carve East or North (can't carve East into the East wall if carve_east and col < (self.W - 2): grid[row, col + 1] = 0 else: north = choice(run) grid[north[0] - 1, north[1]] = 0 run = [] return grid
def generate(self): grid = MazeArray(self.H, self.W) # choose a random starting position current = (randrange(1, self.H, 2), randrange(1, self.W, 2)) grid[current] = 0 # created a weighted list of all vertices connected in the graph neighbors = self._find_neighbors(current, grid, True) # loop over all current neighbors, until empty visited = 1 while visited < self.h * self.w: # find neighbor with lowest weight, make it current nn = randrange(len(neighbors)) current = neighbors[nn] visited += 1 grid[current] = 0 neighbors = neighbors[:nn] + neighbors[nn + 1:] # connect that neighbor to a random neighbor with grid[posi] == 0 nearest_n = self._find_neighbors(current, grid)[0] grid[(current[0] + nearest_n[0]) // 2, (current[1] + nearest_n[1]) // 2] = 0 # find all unvisited neighbors of current, add them to neighbors unvisited = self._find_neighbors(current, grid, True) neighbors = list(set(neighbors + unvisited)) return grid
def generate(self): # Adjust complexity and density relative to maze size if self.complexity <= 1.0: self.complexity = int(self.complexity * (5 * (self.H + self.W))) if self.density <= 1.0: self.density = int(self.density * (self.h * self.w)) # Build actual maze grid = MazeArray(self.H, self.W, 0) # Fill borders grid[0, :] = grid[-1, :] = 1 grid[:, 0] = grid[:, -1] = 1 # create walls for i in range(self.density): y, x = randrange(0, self.H, 2), randrange(0, self.W, 2) grid[y, x] = 1 for j in range(self.complexity): neighbours = self._find_neighbors((y, x), grid, True) # is wall neighbours += self._find_neighbors((y, x), grid) # is open if len(neighbours): r, c = choice(neighbours) if grid[r, c] == 0: grid[r, c] = 1 grid[r + (y - r) // 2, c + (x - c) // 2] = 1 x, y = c, r # ensure all corners are filled for r in range(2, self.H - 2, 2): for c in range(2, self.W - 2, 2): grid[r, c] = 1 return grid
def generate(self): grid = MazeArray(self.H, self.W) current = (randrange(1, self.H, 2), randrange(1, self.W, 2)) grid[current] = 0 num_visited = 1 while num_visited < self.h * self.w: # find neighbors neighbors = self._find_neighbors(current, grid, True) # how many neighbors have already been visited? if len(neighbors) == 0: # mark random neighbor as current current = choice(self._find_neighbors(current, grid)) continue # loop through neighbors for neighbor in neighbors: if grid[neighbor]: # open up wall to new neighbor grid[((neighbor[0] + current[0]) // 2, (neighbor[1] + current[1]) // 2)] = 0 # mark neighbor as visited grid[neighbor] = 0 # bump the number visited num_visited += 1 # current becomes new neighbor current = neighbor # break loop break return grid
def generate(self): VERTICAL, HORIZONTAL = 0, 1 grid = MazeArray(self.H, self.W, 0) grid[:, 0] = grid[:, -1] = 1 grid[0, :] = grid[-1, :] = 1 region_stack = [((1, 1), (self.H - 2, self.W - 2))] while region_stack: current_region = region_stack[-1] region_stack = region_stack[:-1] min_y = current_region[0][0] max_y = current_region[1][0] min_x = current_region[0][1] max_x = current_region[1][1] height = max_y - min_y + 1 width = max_x - min_x + 1 if height <= 1 or width <= 1: continue if width < height: cut_direction = HORIZONTAL # with 100% chance elif width > height: cut_direction = VERTICAL # with 100% chance else: if width == 2: continue cut_direction = randrange(2) # make cut # select cut position (can't be completely on the edge of the region) cut_length = (height, width)[(cut_direction + 1) % 2] if cut_length < 3: continue cut_posi = randrange(1, cut_length, 2) # select new door position door_posi = randrange(0, (height, width)[cut_direction], 2) # add walls to correct places if cut_direction == 0: # vertical for row in range(min_y, max_y + 1): grid[row, min_x + cut_posi] = 1 grid[min_y + door_posi, min_x + cut_posi] = 0 else: # horizontal for col in range(min_x, max_x + 1): grid[min_y + cut_posi, col] = 1 grid[min_y + cut_posi, min_x + door_posi] = 0 # add new regions to stack if cut_direction == 0: # vertical region_stack.append( ((min_y, min_x), (max_y, min_x + cut_posi - 1))) region_stack.append( ((min_y, min_x + cut_posi + 1), (max_y, max_x))) else: # horizontal region_stack.append( ((min_y, min_x), (min_y + cut_posi - 1, max_x))) region_stack.append( ((min_y + cut_posi + 1, min_x), (max_y, max_x))) return grid
def generate(self): grid = MazeArray(self.H, self.W) for row in range(1, self.H, 2): for col in range(1, self.W, 2): current = (row, col) grid[current] = 0 neighbor = self._find_neighbor(current) grid[neighbor] = 0 return grid
def __init__(self, h0, w0, rooms=None, grid=None, hunt_order='random'): # if the user provides a grid, that overrides h & w if grid: h = (grid.height - 1) // 2 w = (grid.width - 1) // 2 self.backup_grid = grid.copy() else: h = h0 w = w0 self.backup_grid = MazeArray(2 * h + 1, 2 * w + 1) self.grid = None self.rooms = rooms super(DungeonRooms, self).__init__(h, w) # the user can define what order to hunt for the next cell in if hunt_order == 'random': self._hunt_order = self._hunt_random elif hunt_order == 'serpentine': self._hunt_order = self._hunt_serpentine else: self._hunt_order = self._hunt_random
def generate(self): grid = MazeArray(self.H, self.W) # find an arbitrary starting position grid[randrange(1, self.H, 2), randrange(1, self.W, 2)] = 0 num_visited = 1 current = self._hunt(grid, num_visited) # perform many random walks, to fill the maze while current != (-1, -1): walk = self._generate_random_walk(grid, current) num_visited += self._solve_random_walk(grid, walk, current) current = self._hunt(grid, num_visited) return grid
def generate(self): grid = MazeArray(self.H, self.W) # find an arbitrary starting position current = (randrange(1, self.H, 2), randrange(1, self.W, 2)) grid[current] = 0 # perform many random walks, to fill the maze num_trials = 0 while current != (-1, -1): self._walk(grid, current) current = self._hunt(grid, num_trials) num_trials += 1 return grid
def generate(self): grid = MazeArray(self.H, self.W) current = (randrange(1, self.H, 2), randrange(1, self.W, 2)) track = [current] grid[current] = 0 while track: current = track[-1] neighbors = self._find_neighbors(current, grid, True) if len(neighbors) == 0: track = track[:-1] else: nn = neighbors[0] grid[nn] = 0 grid[(nn[0] + current[0]) // 2, (nn[1] + current[1]) // 2] = 0 track += [nn] return grid
class DungeonRooms(MazeGenAlgo): """ The Algorithm This is a variation on Hunt-and-Kill where the initial maze has rooms carved out of it, instead of being completely flat. Optional Parameters rooms: List(List(tuple, tuple)) A list of lists, containing the top-left and bottom-right grid coords of where you want rooms to be created. For best results, the corners of each room should have odd-numbered coordinates. grid: MazeArray A pre-built maze array filled with one, or many, rooms. hunt_order: String ['random', 'serpentine'] Determines how the next cell to hunt from will be chosen. (default 'random') """ def __init__(self, h0, w0, rooms=None, grid=None, hunt_order='random'): # if the user provides a grid, that overrides h & w if grid: h = (grid.height - 1) // 2 w = (grid.width - 1) // 2 self.backup_grid = grid.copy() else: h = h0 w = w0 self.backup_grid = MazeArray(2 * h + 1, 2 * w + 1) self.grid = None self.rooms = rooms super(DungeonRooms, self).__init__(h, w) # the user can define what order to hunt for the next cell in if hunt_order == 'random': self._hunt_order = self._hunt_random elif hunt_order == 'serpentine': self._hunt_order = self._hunt_serpentine else: self._hunt_order = self._hunt_random def generate(self): # define grid and rooms self.grid = self.backup_grid.copy() self._carve_rooms(self.rooms) # select start position for algorithm current = self._choose_start() self.grid[current] = 0 # find an arbitrary starting position current = (randrange(1, self.H, 2), randrange(1, self.W, 2)) self.grid[current] = 0 # perform many random walks, to fill the maze num_trials = 0 while current != (-1, -1): self._walk(current) current = self._hunt(num_trials) num_trials += 1 # fix any unconnected wall sections self._reconnect_maze() return self.grid def _carve_rooms(self, rooms): """Open up user-defined rooms in a maze.""" if rooms is None: return for room in rooms: try: top_left, bottom_right = room self._carve_room(top_left, bottom_right) self._carve_door(top_left, bottom_right) except Exception: # If the user tries to create an invalid room, it is simply ignored. pass def _carve_room(self, top_left, bottom_right): """Open up a single user-defined room in a maze.""" for row in range(top_left[0], bottom_right[0] + 1): for col in range(top_left[1], bottom_right[1] + 1): self.grid[row, col] = 0 def _carve_door(self, top_left, bottom_right): """Open up a single door in a user-defined room, IF that room does not already have a whole wall of doors.""" even_squares = filter(lambda i: i % 2 == 0, list(top_left) + list(bottom_right)) if len(even_squares) > 0: return # find possible doors on all sides of room possible_doors = [] odd_rows = filter(lambda i: i % 2 == 1, range(top_left[0] - 1, bottom_right[0] + 2)) odd_cols = filter(lambda i: i % 2 == 1, range(top_left[1] - 1, bottom_right[1] + 2)) if top_left[0] > 2: possible_doors += zip([top_left[0] - 1] * len(odd_rows), odd_rows) if top_left[1] > 2: possible_doors += zip(odd_cols, [top_left[1] - 1] * len(odd_cols)) if bottom_right[0] < self.grid.height - 2: possible_doors += zip([bottom_right[0] + 1] * len(odd_rows), odd_rows) if bottom_right[1] < self.grid.width - 2: possible_doors += zip(odd_cols, [bottom_right[1] + 1] * len(odd_cols)) door = choice(possible_doors) self.grid[door] = 0 def _walk(self, start): """ This is a standard random walk. It must start from a visited cell. And it completes when the current cell has no unvisited neighbors. """ if self.grid[start] == 0: current = start unvisited_neighbors = self._find_neighbors(current, self.grid, True) while len(unvisited_neighbors) > 0: neighbor = choice(unvisited_neighbors) self.grid[neighbor] = 0 self.grid[(neighbor[0] + current[0]) // 2, (neighbor[1] + current[1]) // 2] = 0 current = neighbor unvisited_neighbors = self._find_neighbors( current, self.grid, True) def _hunt(self, count): """ Based on how this algorithm was configured, choose hunt for the next starting point. """ return self._hunt_order(count) def _hunt_random(self, count): """ Select the next cell to walk from, randomly. """ if count >= (self.H * self.W): return (-1, -1) return (randrange(1, self.H, 2), randrange(1, self.W, 2)) def _hunt_serpentine(self, count): """ Select the next cell to walk from by cycling through every grid cell in order. """ cell = (1, 1) found = False while not found: cell = (cell[0], cell[1] + 2) if cell[1] > (self.W - 2): cell = (cell[0] + 2, 1) if cell[0] > (self.H - 2): return (-1, -1) if self.grid[cell] == 0 and len( self._find_neighbors(cell, self.grid, True)) > 0: found = True return cell def _choose_start(self): """Choose a random starting location, that is not already inside a room. If no such room exists, the input grid was invalid. """ current = (randrange(1, self.H, 2), randrange(1, self.W, 2)) LIMIT = self.H * self.W * 2 num_tries = 1 # keep looping until you find an unvisited cell while num_tries < LIMIT: current = (randrange(1, self.H, 2), randrange(1, self.W, 2)) if self.grid[current] == 1: return current num_tries += 1 if num_tries >= LIMIT: raise UnboundError('The grid input to DungeonRooms was invalid.') return current def _reconnect_maze(self): """If a maze is not fully connected, open up walls until it is.""" passages = self._find_all_passages() self._fix_disjoint_passages(passages) def _find_all_passages(self): """Place all connected passage cells into a set. Disjoint passages will be in different sets. """ passages = [] # go through all cells in the maze for r in range(1, self.grid.height, 2): for c in range(1, self.grid.width, 2): ns = self._find_unblocked_neighbors((r, c)) current = set(ns + [(r, c)]) # determine which passage(s) the current neighbors belong in found = False for i, passage in enumerate(passages): intersect = current.intersection(passage) if len(intersect) > 0: passages[i] = passages[i].union(current) found = True break # the current neighbors might be a disjoint set if not found: passages.append(current) return self._join_intersecting_sets(passages) def _fix_disjoint_passages(self, disjoint_passages): """All passages in a maze should be connected""" while len(disjoint_passages) > 1: found = False while not found: # randomly select a cell in the first passage cell = choice(list(disjoint_passages[0])) neighbors = self._find_neighbors(cell, self.grid) # determine if that cell has a neighbor in any other passage for passage in disjoint_passages[1:]: intersect = [c for c in neighbors if c in passage] # if so, remove the dividing wall, combine the two passages if len(intersect) > 0: mid = self._midpoint(intersect[0], cell) self.grid[mid] = 0 disjoint_passages[0] = disjoint_passages[0].union( passage) disjoint_passages.remove(passage) found = True break def _join_intersecting_sets( self, list_of_sets): # TODO: method could be a function """combine sets that have non-zero intersections""" for i in range(len(list_of_sets) - 1): if list_of_sets[i] is None: continue for j in range(i + 1, len(list_of_sets)): if list_of_sets[j] is None: continue intersect = list_of_sets[i].intersection(list_of_sets[j]) if len(intersect) > 0: list_of_sets[i] = list_of_sets[i].union(list_of_sets[j]) list_of_sets[j] = None return filter(lambda l: l is not None, list_of_sets) def _find_unblocked_neighbors(self, posi): """Find all the grid neighbors of the current position; visited, or not. """ r, c = posi ns = [] if r > 1 and self.grid[r - 1, c] == False and self.grid[r - 2, c] == False: ns.append((r - 2, c)) if r < self.grid.height - 2 and self.grid[ r + 1, c] == False and self.grid[r + 2, c] == False: ns.append((r + 2, c)) if c > 1 and self.grid[r, c - 1] == False and self.grid[r, c - 2] == False: ns.append((r, c - 2)) if c < self.grid.width - 2 and self.grid[ r, c + 1] == False and self.grid[r, c + 2] == False: ns.append((r, c + 2)) shuffle(ns) return ns def _midpoint(self, a, b): # TODO: method could be a function """Find the wall cell between to passage cells""" return (a[0] + b[0]) // 2, (a[1] + b[1]) // 2
class DungeonRooms(MazeGenAlgo): """ The Algorithm This is a variation on Hunt-and-Kill where the initial maze has rooms carved out of it, instead of being completely flat. Optional Parameters rooms: List(List(tuple, tuple)) A list of lists, containing the top-left and bottom-right grid coords of where you want rooms to be created. For best results, the corners of each room should have odd-numbered coordinates. grid: MazeArray A pre-built maze array filled with one, or many, rooms. hunt_order: String ['random', 'serpentine'] Determines how the next cell to hunt from will be chosen. (default 'random') """ def __init__(self, h0, w0, rooms=None, grid=None, hunt_order='random'): # if the user provides a grid, that overrides h & w if grid: h = (grid.height - 1) // 2 w = (grid.width - 1) // 2 self.backup_grid = grid.copy() else: h = h0 w = w0 self.backup_grid = MazeArray(2 * h + 1, 2 * w + 1) self.grid = None self.rooms = rooms super(DungeonRooms, self).__init__(h, w) # the user can define what order to hunt for the next cell in if hunt_order == 'random': self._hunt_order = self._hunt_random elif hunt_order == 'serpentine': self._hunt_order = self._hunt_serpentine else: self._hunt_order = self._hunt_random def generate(self): # define grid and rooms self.grid = self.backup_grid.copy() self._carve_rooms(self.rooms) # select start position for algorithm current = self._choose_start() self.grid[current] = 0 # find an arbitrary starting position current = (randrange(1, self.H, 2), randrange(1, self.W, 2)) self.grid[current] = 0 # perform many random walks, to fill the maze num_trials = 0 while current != (-1, -1): self._walk(current) current = self._hunt(num_trials) num_trials += 1 # fix any unconnected wall sections self._reconnect_maze() return self.grid def _carve_rooms(self, rooms): """Open up user-defined rooms in a maze.""" if rooms is None: return for room in rooms: try: top_left, bottom_right = room self._carve_room(top_left, bottom_right) self._carve_door(top_left, bottom_right) except Exception: # If the user tries to create an invalid room, it is simply ignored. pass def _carve_room(self, top_left, bottom_right): """Open up a single user-defined room in a maze.""" for row in range(top_left[0], bottom_right[0] + 1): for col in range(top_left[1], bottom_right[1] + 1): self.grid[row, col] = 0 def _carve_door(self, top_left, bottom_right): """Open up a single door in a user-defined room, IF that room does not already have a whole wall of doors.""" even_squares = filter(lambda i: i % 2 == 0, list(top_left) + list(bottom_right)) if len(even_squares) > 0: return # find possible doors on all sides of room possible_doors = [] odd_rows = filter(lambda i: i % 2 == 1, range(top_left[0] - 1, bottom_right[0] + 2)) odd_cols = filter(lambda i: i % 2 == 1, range(top_left[1] - 1, bottom_right[1] + 2)) if top_left[0] > 2: possible_doors += zip([top_left[0] - 1] * len(odd_rows), odd_rows) if top_left[1] > 2: possible_doors += zip(odd_cols, [top_left[1] - 1] * len(odd_cols)) if bottom_right[0] < self.grid.height - 2: possible_doors += zip([bottom_right[0] + 1] * len(odd_rows), odd_rows) if bottom_right[1] < self.grid.width - 2: possible_doors += zip(odd_cols, [bottom_right[1] + 1] * len(odd_cols)) door = choice(possible_doors) self.grid[door] = 0 def _walk(self, start): """ This is a standard random walk. It must start from a visited cell. And it completes when the current cell has no unvisited neighbors. """ if self.grid[start] == 0: current = start unvisited_neighbors = self._find_neighbors(current, self.grid, True) while len(unvisited_neighbors) > 0: neighbor = choice(unvisited_neighbors) self.grid[neighbor] = 0 self.grid[(neighbor[0] + current[0]) // 2, (neighbor[1] + current[1]) // 2] = 0 current = neighbor unvisited_neighbors = self._find_neighbors(current, self.grid, True) def _hunt(self, count): """ Based on how this algorithm was configured, choose hunt for the next starting point. """ return self._hunt_order(count) def _hunt_random(self, count): """ Select the next cell to walk from, randomly. """ if count >= (self.H * self.W): return (-1, -1) return (randrange(1, self.H, 2), randrange(1, self.W, 2)) def _hunt_serpentine(self, count): """ Select the next cell to walk from by cycling through every grid cell in order. """ cell = (1, 1) found = False while not found: cell = (cell[0], cell[1] + 2) if cell[1] > (self.W - 2): cell = (cell[0] + 2, 1) if cell[0] > (self.H - 2): return (-1, -1) if self.grid[cell] == 0 and len(self._find_neighbors(cell, self.grid, True)) > 0: found = True return cell def _choose_start(self): """Choose a random starting location, that is not already inside a room. If no such room exists, the input grid was invalid. """ current = (randrange(1, self.H, 2), randrange(1, self.W, 2)) LIMIT = self.H * self.W * 2 num_tries = 1 # keep looping until you find an unvisited cell while num_tries < LIMIT: current = (randrange(1, self.H, 2), randrange(1, self.W, 2)) if self.grid[current] == 1: return current num_tries += 1 if num_tries >= LIMIT: raise UnboundError('The grid input to DungeonRooms was invalid.') return current def _reconnect_maze(self): """If a maze is not fully connected, open up walls until it is.""" passages = self._find_all_passages() self._fix_disjoint_passages(passages) def _find_all_passages(self): """Place all connected passage cells into a set. Disjoint passages will be in different sets. """ passages = [] # go through all cells in the maze for r in range(1, self.grid.height, 2): for c in range(1, self.grid.width, 2): ns = self._find_unblocked_neighbors((r, c)) current = set(ns + [(r, c)]) # determine which passage(s) the current neighbors belong in found = False for i, passage in enumerate(passages): intersect = current.intersection(passage) if len(intersect) > 0: passages[i] = passages[i].union(current) found = True break # the current neighbors might be a disjoint set if not found: passages.append(current) return self._join_intersecting_sets(passages) def _fix_disjoint_passages(self, disjoint_passages): """All passages in a maze should be connected""" while len(disjoint_passages) > 1: found = False while not found: # randomly select a cell in the first passage cell = choice(list(disjoint_passages[0])) neighbors = self._find_neighbors(cell, self.grid) # determine if that cell has a neighbor in any other passage for passage in disjoint_passages[1:]: intersect = [c for c in neighbors if c in passage] # if so, remove the dividing wall, combine the two passages if len(intersect) > 0: mid = self._midpoint(intersect[0], cell) self.grid[mid] = 0 disjoint_passages[0] = disjoint_passages[0].union(passage) disjoint_passages.remove(passage) found = True break def _join_intersecting_sets(self, list_of_sets): # TODO: method could be a function """combine sets that have non-zero intersections""" for i in range(len(list_of_sets) - 1): if list_of_sets[i] is None: continue for j in range(i + 1, len(list_of_sets)): if list_of_sets[j] is None: continue intersect = list_of_sets[i].intersection(list_of_sets[j]) if len(intersect) > 0: list_of_sets[i] = list_of_sets[i].union(list_of_sets[j]) list_of_sets[j] = None return filter(lambda l: l is not None, list_of_sets) def _find_unblocked_neighbors(self, posi): """Find all the grid neighbors of the current position; visited, or not. """ r, c = posi ns = [] if r > 1 and self.grid[r-1, c] == False and self.grid[r-2, c] == False: ns.append((r-2, c)) if r < self.grid.height-2 and self.grid[r+1, c] == False and self.grid[r+2, c] == False: ns.append((r+2, c)) if c > 1 and self.grid[r, c-1] == False and self.grid[r, c-2] == False: ns.append((r, c-2)) if c < self.grid.width-2 and self.grid[r, c+1] == False and self.grid[r, c+2] == False: ns.append((r, c+2)) shuffle(ns) return ns def _midpoint(self, a, b): # TODO: method could be a function """Find the wall cell between to passage cells""" return (a[0] + b[0]) // 2, (a[1] + b[1]) // 2
def generate(self): grid = MazeArray(self.H, self.W) grid = self.sub_gen(grid) return grid