diff --git a/erynrl/map.py b/erynrl/map/__init__.py similarity index 83% rename from erynrl/map.py rename to erynrl/map/__init__.py index 18f65f8..a2f0b73 100644 --- a/erynrl/map.py +++ b/erynrl/map/__init__.py @@ -7,21 +7,22 @@ from typing import Iterator, List, Optional import numpy as np import tcod -from . import log -from .geometry import Direction, Point, Rect, Size +from .. import log +from ..geometry import Direction, Point, Rect, Size from .tile import Empty, Floor, Shroud, Wall + class Map: - def __init__(self, size: Size): + def __init__(self, size: Size, room_generator_class=RoomsAndCorridorsGenerator): self.size = size - self.generator = RoomsAndCorridorsGenerator(size=size) + self.generator = room_generator_class(size=size) self.tiles = self.generator.generate() # Map tiles that are currently visible to the player - self.visible = np.full(tuple(self.size), fill_value=False, order='F') + self.visible = np.full(tuple(self.size), fill_value=True, order='F') # Map tiles that the player has explored - self.explored = np.full(tuple(self.size), fill_value=False, order='F') + self.explored = np.full(tuple(self.size), fill_value=True, order='F') @property def rooms(self) -> List['Room']: @@ -54,6 +55,7 @@ class Map: choicelist=[self.tiles['light'], self.tiles['dark']], default=Shroud) + class MapGenerator: def __init__(self, *, size: Size): self.size = size @@ -73,6 +75,7 @@ class MapGenerator: ''' raise NotImplementedError() + class RoomsAndCorridorsGenerator(MapGenerator): '''Generate a rooms-and-corridors style map with BSP.''' @@ -82,8 +85,8 @@ class RoomsAndCorridorsGenerator(MapGenerator): maximum_room_size: Size DefaultConfiguration = Configuration( - minimum_room_size=Size(5, 5), - maximum_room_size=Size(15, 15), + minimum_room_size=Size(7, 7), + maximum_room_size=Size(20, 20), ) def __init__(self, *, size: Size, config: Optional[Configuration] = None): @@ -104,11 +107,16 @@ class RoomsAndCorridorsGenerator(MapGenerator): # Recursively divide the map into squares of various sizes to place rooms in. bsp = tcod.bsp.BSP(x=0, y=0, width=self.size.width, height=self.size.height) + + # Add 2 to the minimum width and height to account for walls + gap_for_walls = 2 bsp.split_recursive( depth=4, - # Add 2 to the minimum width and height to account for walls - min_width=minimum_room_size.width + 2, min_height=minimum_room_size.height + 2, - max_horizontal_ratio=3, max_vertical_ratio=3) + min_width=minimum_room_size.width + gap_for_walls, + min_height=minimum_room_size.height + gap_for_walls, + max_horizontal_ratio=1.1, + max_vertical_ratio=1.1 + ) tiles = np.full(tuple(self.size), fill_value=Empty, order='F') @@ -141,7 +149,8 @@ class RoomsAndCorridorsGenerator(MapGenerator): else: corner = Point(start_point.x, end_point.y) - log.MAP.debug('Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) + log.MAP.debug( + 'Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) log.MAP.debug('|-> start: %s', left_room_bounds) log.MAP.debug('`-> end: %s', right_room_bounds) @@ -155,10 +164,19 @@ class RoomsAndCorridorsGenerator(MapGenerator): # Generate a room size between minimum_room_size and maximum_room_size. The minimum value is # straight-forward, but the maximum value needs to be clamped between minimum_room_size and the size of # the node. - width_range = (minimum_room_size.width, min(maximum_room_size.width, max(minimum_room_size.width, node.width - 2))) - height_range = (minimum_room_size.height, min(maximum_room_size.height, max(minimum_room_size.height, node.height - 2))) + width_range = ( + minimum_room_size.width, + min(maximum_room_size.width, max( + minimum_room_size.width, node.width - 2)) + ) + height_range = ( + minimum_room_size.height, + min(maximum_room_size.height, max( + minimum_room_size.height, node.height - 2)) + ) - size = Size(self.rng.randint(*width_range), self.rng.randint(*height_range)) + size = Size(self.rng.randint(*width_range), + self.rng.randint(*height_range)) origin = Point(node.x + self.rng.randint(1, max(1, node.width - size.width - 1)), node.y + self.rng.randint(1, max(1, node.height - size.height - 1))) bounds = Rect(origin, size) @@ -193,7 +211,8 @@ class RoomsAndCorridorsGenerator(MapGenerator): bounds = room.bounds # The range of a numpy array slice is [a, b). floor_rect = bounds.inset_rect(top=1, right=1, bottom=1, left=1) - tiles[floor_rect.min_x:floor_rect.max_x + 1, floor_rect.min_y:floor_rect.max_y + 1] = Floor + tiles[floor_rect.min_x:floor_rect.max_x + 1, + floor_rect.min_y:floor_rect.max_y + 1] = Floor for y in range(self.size.height): for x in range(self.size.width): @@ -215,6 +234,16 @@ class RoomsAndCorridorsGenerator(MapGenerator): '''Create a Rect from the given BSP node object''' return Rect(Point(node.x, node.y), Size(node.width, node.height)) + +class ElbowCorridorGenerator: + ... + + +class NetHackCorridorGenerator: + '''A corridor generator that produces doors and corridors that look like Nethack's Dungeons of Doom levels.''' + ... + + class Room: '''An abstract room. It can be any size or shape.''' @@ -222,6 +251,7 @@ class Room: def walkable_tiles(self) -> Iterator[Point]: raise NotImplementedError() + class RectangularRoom(Room): '''A rectangular room defined by a Rect. @@ -241,7 +271,7 @@ class RectangularRoom(Room): return self.bounds.midpoint @property - def walkable_tiles(self) -> Rect: + def walkable_tiles(self) -> Iterator[Point]: floor_rect = self.bounds.inset_rect(top=1, right=1, bottom=1, left=1) for y in range(floor_rect.min_y, floor_rect.max_y + 1): for x in range(floor_rect.min_x, floor_rect.max_x + 1): diff --git a/erynrl/tile.py b/erynrl/map/tile.py similarity index 100% rename from erynrl/tile.py rename to erynrl/map/tile.py