def generate(height: int, width: int) -> Grid: """ generates a random, perfect, maze using binary tree algorithm. Binary Tree algorithm is one of the simplest maze generation algorithms: 1. start at a corner of the maze (in this case it will be the North West) 2. iterate through the cells row by row 3. for each cell pick a random East or South wall to remove 4. repeat until all cells have been visited :param height: number of rows in the generated maze :param width: number of columns in the generated maze :return: a Grid containing the random maze """ grid = Grid(height, width) for cell in grid.cell_iterator(): neighbors = [] # if a cell has a neighbor to the south, add it to neighbors if cell.south: neighbors.append(cell.south) # if a cell has a neighbor to the east, add it to neighbors if cell.east: neighbors.append(cell.east) # choose a random neighbor from neighbors and create a link to it if neighbors: cell.link(random.choice(neighbors)) return grid
def generate(height: int, width: int) -> Grid: """ Aldous-Broder maze generation. Aldous-Broder generates perfect mazes, (mazes without loops), using "random-walks". This avoids creating mazes with biases (like Binary Tree) and instead produces mazes with lots of winding passages. The idea behind it is as follows: 1. Start anywhere in the grid you want, and choose a random neighbor. 2. Move to that neighbor, and if it has not previously been visited, link it to the prior cell. 3. Repeat until every cell has been visited. :param height: :param width: :return: """ grid = Grid(height, width) # the current cell being visited current = grid.random_cell() unvisited_count = grid.size() - 1 while unvisited_count > 0: # choose a random neighbor of the current cell rand_neighbor = random.choice(current.neighbors()) # if the random neighbor is not linked to anything, then link current to it if not rand_neighbor.linked_cells(): current.link(rand_neighbor) unvisited_count -= 1 # make rand_neighbor the new current cell current = rand_neighbor return grid
def generate(height: int, width: int) -> Grid: """ Generates a random, perfect, maze using Wilson's algorithm Like Aldous-Broder, this algorithm depends on the idea of a random walk, but with a twist. It performs what is called a loop-erased random walk, which means that as it goes, if the path it is forming happens to intersect with itself and form a loop, it erases that loop before continuing on. 1. choose a point on the grid and mark it visited. 2. choose any unvisited cell in the grid and perform a loop-erased random walk until you reach a visited cell. 3. link all the cells in the current random walk to the visited cell 4. repeat step 2 until all cells in the grid have been visited :param height: number of rows to generate :param width: number of columns to generate :return: a Grid containing the generated maze """ grid = Grid(height, width) # initialize the unvisited list to contain all cells in the grid unvisited = [cell for cell in grid.cell_iterator()] # choose a random cell in unvisited to become "visited" unvisited.remove(random.choice(unvisited)) while unvisited: # choose a random, unvisited cell to begin at cell = random.choice(unvisited) # add the cell to the random walk "path" path = [cell] # start randomly visiting unvisited cells while cell in unvisited: cell = random.choice(cell.neighbors()) if cell in path: # if we've already visited a cell, (creating a loop), remove the last cell from the path path = path[0:path.index(cell) + 1] else: # add cell to the path as it has not been added to the path path.append(cell) # link together all cell in the path, then remove each cell in the path from the unvisited list for i in range(len(path) - 1): path[i].link(path[i + 1]) unvisited.remove(path[i]) return grid
def generate(height: int, width: int) -> Grid: """ Generates a random, perfect, maze using the Hunt and Kill maze generation algorithm. Hunt-and-kill is similar to Aldous-Broder but slightly different. Aldous-Broder allows you to step into any cell (even previously visited ones), while hunt-and-kill requires you to walk on only unvisited cells. If you walk into a corner, you begin the "hunt" mode, which is where you start from the top of the maze, look for the first cell that is a neighbor of the cells in the current walk you are performing, and then link into the walk. Then repeat the random walk until all cells are visited. Hunt-and-Kill is known to produce mazes with longer winding and meandering corridors than other algorithms. That is to say, hunt-and-kill produces mazes with fewer dead ends. :param height: the number of rows to generate :param width: the number of columns to generate :return: a Grid object that contains the maze """ grid = Grid(height, width) current = grid.random_cell() while current: # get all linked neighbors of the current cell that are not linked to other cells unvisited_neighbors = list( filter(lambda cell: not cell.linked_cells(), current.neighbors())) # start randomly walking cells. If the current has unvisited neighbors, we will link current # to a random, unvisited neighbor, and then make that random neighbor the current if unvisited_neighbors: rand_neighbor = random.choice(unvisited_neighbors) current.link(rand_neighbor) current = rand_neighbor else: current = None # start the hunt phase. Starting from the top row of the grid, begin looking # for the first cell that is unvisited AND that has visited neighbors for cell in grid.cell_iterator(): visited_neighbors = list( filter(lambda cell: cell.linked_cells(), cell.neighbors())) # if a cell is unvisited but one of its neighbors is, then link cell to a random # neighbor and set current cell to the cell being visited if not cell.linked_cells() and visited_neighbors: current = cell current.link(random.choice(visited_neighbors)) break return grid
def generate(height: int, width: int) -> Grid: """ generates a random, perfect, maze using the sidewinder algorithm Sidewinder is similar to binary tree but does have some differences. In a nutshell, it goes like this: 1. Work through the grid row-wise, starting with the cell at 0,0. Initialize the “run” set to be empty. 2. Add the current cell to the “run” set. 3. For the current cell, randomly decide whether to carve east or not. 4. If a passage was carved, make the new cell the current cell and repeat steps 2-4. 5. If a passage was not carved, choose any one of the cells in the run set and carve a passage north. Then empty the run set, set the next cell in the row to be the current cell, and repeat steps 2-5. 6. Continue until all rows have been processed. :param height: the number of rows to generate :param width: the number of columns to generate :return: a grid containing the maze """ def should_close_out(cell): """ returns true if a current run of Cells should be closed out. A run should be closed out if we are at the eastern most cell of a row, OR if we are not on the northernmost row and a random true value is generated :param cell: :return: """ return not cell.east or (cell.north and random.choice([True, False])) grid = Grid(height, width) for cell in grid.cell_iterator(): runs = [cell] # if a run should be closed out, then choose a random cell from the run, and link to that cells # north neighbor if should_close_out(cell) and runs: rand_cell = random.choice(runs) if rand_cell.north: rand_cell.link(rand_cell.north) runs.clear() elif cell.east: cell.link(cell.east) return grid
def __init__(self, screen: Union[Surface, SurfaceType], solution: Solution, solution_start: Tuple[int, int]): grid_instance: Grid = Grid.get_instance() self.__screen = screen self.__solution: Solution = solution self.__grid: Dict[Tuple[int, int], Cell] = grid_instance.grid self.__solutionStartX: int = solution_start[0] self.__solutionStartY: int = solution_start[1]
def generate(height: int, width: int) -> Grid: """ generates a random, perfect, maze using recursive backtracker algorithm Here’s how the recursive backtracker algorithm works: 1. Choose a random starting point in the grid. 2. Randomly choose a random neighbor that has not been visited and link to it. This neighbor becomes the new current cell. 3. If all neighbor cells have been visited, back up to the last cell that has unvisited neighbors and repeat. 4. The algorithm ends when the process has backed all the way up to the starting point. In essence, this carves passages using a depth-first search, that backtracks once it carves itself into a corner. Also, like hunt-and-kill, recursive-backtracker also produces mazes that are full of long and meandering passages. :param height: the number of rows in the maze :param width: the number of columns in the maze :return: a Grid containing the generated maze """ grid = Grid(height, width) # stack holds the cells to be visited stack = [grid.random_cell()] while stack: # get the last element of the stack current = stack[-1] # get all linked neighbors of current that are NOT linked to any other cells neighbors = list( filter(lambda nbr: not nbr.linked_cells(), current.neighbors())) if not neighbors: # we are in a corner, so backtrack by popping the current cell off of the stack stack.pop() else: # choose a random neighbor, link to it, and make it the new current by pushing it on the stack neighbor = random.choice(neighbors) current.link(neighbor) stack.append(neighbor) return grid
def generate(height: int, width: int) -> Grid: """ Generates a random, perfect maze using Prim's algorithm. Cells in the grid will be assigned random weights in order to help generate a more random maze :param height: number of rows to generate :param width: number of columns to generate :return: a Grid object containing the maze """ def unlinked_neighbors(cell): """returns a list of cell's neighbors, that are not linked to some other cell""" return list( filter(lambda nbr: not nbr.linked_cells(), cell.neighbors())) def sort_by_weight(cells): """sorts a list of cells, in place, by their weight""" cells.sort(key=lambda c: c.weight) grid = Grid(height, width) # assign random weights to all cells in the grid for cell in grid.cell_iterator(): cell.weight = random.randint(1, 100) to_visit = [grid.random_cell()] while to_visit: # sort cells by their weights, ideally we would use a priority queue sort_by_weight(to_visit) cell = to_visit[0] neighbors = unlinked_neighbors(cell) if neighbors: # sort neighbors by weight sort_by_weight(neighbors) neighbor = neighbors[0] cell.link(neighbor) to_visit.append(neighbor) else: to_visit.remove(cell) return grid
def __init__(self, screen: Union[Surface, SurfaceType]): grid_instance: Grid = Grid.get_instance() self.__screen = screen self.__grid: Dict[Tuple[int, int], Cell] = grid_instance.generate_grid()
def tests_row_iterator(self): grid = Grid(4, 4) row_count = 0 for row in grid.row_iterator(): row_count += 1 self.assertEqual(row_count, 4)
def test_str_(self): grid = Grid(4, 3) print(grid)
def tests_cell_iterator(self): grid = Grid(4, 3) cell_count = 0 for cell in grid.cell_iterator(): cell_count += 1 self.assertEqual(cell_count, 12)
def __init__(self, solution_start: Tuple[int, int]): grid_instance: Grid = Grid.get_instance() self._grid: Dict[Tuple[int, int], Cell] = grid_instance.grid self._solutionStartX: int = solution_start[0] self._solutionStartY: int = solution_start[1]