from __future__ import annotations import random from typing import Tuple, Iterator, TYPE_CHECKING, List import tcod.los import tile_types import entity_factories from game_map import GameMap if TYPE_CHECKING: from engine import Engine class RectangularRoom: def __init__(self, x: int, y: int, width: int, height: int): self.x1 = x self.y1 = y self.x2 = x + width self.y2 = y + height @property def center(self) -> Tuple[int, int]: return (self.x1 + self.x2) // 2, (self.y1 + self.y2) // 2 @property def inner(self) -> Tuple[slice, slice]: return slice(self.x1 + 1, self.x2), slice(self.y1 + 1, self.y2) def intersects(self, other: RectangularRoom) -> bool: """Return True if this room overlaps with another RectangularRoom.""" return ( self.x1 <= other.x2 and self.x2 >= other.x1 and self.y1 <= other.y2 and self.y2 >= other.y1 ) def tunnel_between( start: Tuple[int, int], end: Tuple[int, int] ) -> Iterator[Tuple[int, int]]: """return an L shape tunel between points""" x1, y1 = start x2, y2 = end if random.random() < 0.5: corner_x, corner_y = x2, y1 else: corner_x, corner_y = x1, y2 for x, y in tcod.los.bresenham((x1, y1), (corner_x, corner_y)).tolist(): yield x, y for x, y in tcod.los.bresenham((corner_x, corner_y), (x2, y2)).tolist(): yield x, y def place_entities( room: RectangularRoom, dungeon: GameMap, maximum_monsters: int ) -> None: number_of_monsters = random.randint(0, maximum_monsters) for i in range(number_of_monsters): x = random.randint(room.x1 + 1, room.x2 - 1) y = random.randint(room.y1 + 1, room.y2 - 1) if not any(entity.x == x and entity.y == y for entity in dungeon.entities): if random.random() <= 0.8: entity_factories.orc.spawn(dungeon, x, y) else: entity_factories.troll.spawn(dungeon, x, y) def generate_dungeon( max_rooms: int, room_min_size: int, room_max_size: int, map_width: int, map_height: int, max_monsters_per_room: int, engine: Engine, ) -> GameMap: player = engine.player dungeon = GameMap(engine, map_width, map_height, entities=[player]) rooms: List[RectangularRoom] = [] for room in range(max_rooms): room_width = random.randint(room_min_size, room_max_size) room_height = random.randint(room_min_size, room_max_size) x = random.randint(0, dungeon.width - room_width - 1) y = random.randint(0, dungeon.height - room_height - 1) new_room = RectangularRoom(x, y, room_width, room_height) if any(new_room.intersects(other_room) for other_room in rooms): continue dungeon.tiles[new_room.inner] = tile_types.floor if len(rooms) == 0: # first room player.place(*new_room.center, dungeon) else: for x, y in tunnel_between(rooms[-1].center, new_room.center): dungeon.tiles[x, y] = tile_types.floor place_entities(new_room, dungeon, max_monsters_per_room) rooms.append(new_room) return dungeon