Exemple #1
0
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
Exemple #3
0
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
Exemple #4
0
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
Exemple #5
0
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
Exemple #8
0
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()
Exemple #10
0
 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)
Exemple #11
0
 def test_str_(self):
     grid = Grid(4, 3)
     print(grid)
Exemple #12
0
 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]