Example #1
0
    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
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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
Example #7
0
    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
Example #8
0
    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
Example #9
0
    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
Example #10
0
    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
Example #11
0
    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
Example #12
0
    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
Example #13
0
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
Example #14
0
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
Example #15
0
    def generate(self):
        grid = MazeArray(self.H, self.W)

        grid = self.sub_gen(grid)

        return grid