def make_corridors(self, level: 'Level') -> SquareStore: def heuristic(pos1: tuple, pos2: tuple) -> Union[float, int]: """A* heuristic for corridor creation (Manhattan distance).""" if any(level.locate(p) for p in (pos1, pos2)): return inf # Avoid squares that contain something other than corridors return abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1]) result = SquareStore() graph = nx.grid_2d_graph(Position.SCREEN_W, Position.SCREEN_H) # Sorts the rooms and then swaps them, in order to get a not-so-random dungeon rooms = sorted(list(level.rooms), key= lambda r: r.top_left) swaps = 0 while d(1, 4) > 1 and swaps < len(level.rooms): a, b = sample(range(len(level.rooms)), 2) rooms[a], rooms[b] = rooms[b], rooms[a] swaps += 1 graph.remove_nodes_from(pos for r in rooms for pos in r)#.squares) for r1, r2 in zip(rooms, rooms[1:]): start, end = CorridorFactory._rnd_doorway(r1), CorridorFactory._rnd_doorway(r2) del r1[start] del r2[end] for p in start, end: graph.add_node(p) graph.add_edges_from((p, n) for n in p.neighbors(False) if level.locate(n) is None) result[position(*p)] = Square(SquareType.DOORWAY) path = nx.astar_path(graph, start, end, heuristic) result.update({position(*pos): Square(SquareType.CORRIDOR) for pos in path[1:-1]}) graph.remove_nodes_from((start, end)) # Avoid using doorways in next computations return result
def create(cls) -> 'Room': """Factory method.""" width = cls._MIN_DIM + d(*cls._W_DICE) - 1 height = cls._MIN_DIM + d(*cls._H_DICE) - 1 top_left = position( randint(0, Position.SCREEN_W - width-1), randint(0, Position.SCREEN_H - height-1)) return Room(top_left, width, height)
def _build_squares(self) -> Dict[Position, Square]: """ Creates the dictionary of squares for the room. """ b = self.bbox sq = {p: Square(t) for p, t in zip(self.corners, (SquareType.WALL_TL, SquareType.WALL_BL, SquareType.WALL_BR, SquareType.WALL_TR))} for idx in range(4): if idx % 2: # Odd = horizontal wall sq.update({ position(i, b[idx]): Square(SquareType.WALL_H) for i in range(b[0]+1, b[2]) }) else: # Even = vertical wall sq.update({ position(b[idx], i): Square(SquareType.WALL_V) for i in range(b[1]+1, b[3]) }) # Fill sq.update({position(i, j): Square(SquareType.ROOM) for i in range(b[0]+1, b[2]) for j in range(b[1]+1, b[3])}) return sq
def _rnd_doorway(room: Room) -> Position: tl, __, br, __ = room.corners candidates = [ tl + (randint(1, room.width-1), 0), tl + (0, randint(1, room.height-1)), br - (randint(1, room.width-1), 0), br - (0, randint(1, room.height-1)) ] candidates[:] = [ pos for pos in candidates if CorridorFactory._is_valid_doorway(pos) and pos in room#.squares and room[pos].type in (SquareType.WALL_H, SquareType.WALL_V) ] if candidates: p = choice(candidates) return position(*p) else: return CorridorFactory._rnd_doorway(room)
def corners(self) -> Tuple[Position, Position, Position, Position]: """Returns positions of corners in CCW order (TL, BL, BR, TR).""" return tuple(position(self.bbox[i], self.bbox[j]) for i, j in ((0, 1), (0, 3), (2, 3), (2, 1)))