Break up room and corridor generation into generate and apply phases
- Generate creates rooms and corridors, and apply applies them to a tile grid. - Add up and down stairs generation to the Room Generators. - Clean up Room.wall_points and Room.floor_points to make it easier to write a generic apply() method on RoomGenerator
This commit is contained in:
parent
d4c4b5d879
commit
c59dc1b907
4 changed files with 122 additions and 72 deletions
|
@ -17,7 +17,10 @@ class Map:
|
|||
|
||||
self.generator = generator
|
||||
self.tiles = np.full(tuple(size), fill_value=Empty, order='F')
|
||||
self.generator.generate(self.tiles)
|
||||
generator.generate(self.tiles)
|
||||
|
||||
self.up_stairs = generator.up_stairs
|
||||
self.down_stairs = generator.down_stairs
|
||||
|
||||
# Map tiles that are currently visible to the player
|
||||
self.visible = np.full(tuple(self.size), fill_value=True, order='F')
|
||||
|
|
|
@ -46,37 +46,49 @@ class ElbowCorridorGenerator(CorridorGenerator):
|
|||
self.corridors: List[List[Point]] = []
|
||||
|
||||
def generate(self, rooms: List[Room]) -> bool:
|
||||
if len(rooms) < 2:
|
||||
return True
|
||||
|
||||
for (left_room, right_room) in pairwise(rooms):
|
||||
left_room_bounds = left_room.bounds
|
||||
right_room_bounds = right_room.bounds
|
||||
|
||||
log.MAP.debug(' left: %s, %s', left_room, left_room_bounds)
|
||||
log.MAP.debug('right: %s, %s', right_room, right_room_bounds)
|
||||
|
||||
start_point = left_room_bounds.midpoint
|
||||
end_point = right_room_bounds.midpoint
|
||||
|
||||
# Randomly choose whether to move horizontally then vertically or vice versa
|
||||
if random.random() < 0.5:
|
||||
corner = Point(end_point.x, start_point.y)
|
||||
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('|-> start: %s', left_room_bounds)
|
||||
log.MAP.debug('`-> end: %s', right_room_bounds)
|
||||
|
||||
corridor: List[Point] = []
|
||||
|
||||
for x, y in tcod.los.bresenham(tuple(start_point), tuple(corner)).tolist():
|
||||
corridor.append(Point(x, y))
|
||||
for x, y in tcod.los.bresenham(tuple(corner), tuple(end_point)).tolist():
|
||||
corridor.append(Point(x, y))
|
||||
corridor = self._generate_corridor_between(left_room, right_room)
|
||||
self.corridors.append(corridor)
|
||||
|
||||
for i in range(len(rooms) - 2):
|
||||
corridor = self._generate_corridor_between(rooms[i], rooms[i + 2])
|
||||
self.corridors.append(corridor)
|
||||
|
||||
return True
|
||||
|
||||
def _generate_corridor_between(self, left_room, right_room):
|
||||
left_room_bounds = left_room.bounds
|
||||
right_room_bounds = right_room.bounds
|
||||
|
||||
log.MAP.debug(' left: %s, %s', left_room, left_room_bounds)
|
||||
log.MAP.debug('right: %s, %s', right_room, right_room_bounds)
|
||||
|
||||
start_point = left_room_bounds.midpoint
|
||||
end_point = right_room_bounds.midpoint
|
||||
|
||||
# Randomly choose whether to move horizontally then vertically or vice versa
|
||||
if random.random() < 0.5:
|
||||
corner = Point(end_point.x, start_point.y)
|
||||
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('|-> start: %s', left_room_bounds)
|
||||
log.MAP.debug('`-> end: %s', right_room_bounds)
|
||||
|
||||
corridor: List[Point] = []
|
||||
|
||||
for x, y in tcod.los.bresenham(tuple(start_point), tuple(corner)).tolist():
|
||||
corridor.append(Point(x, y))
|
||||
|
||||
for x, y in tcod.los.bresenham(tuple(corner), tuple(end_point)).tolist():
|
||||
corridor.append(Point(x, y))
|
||||
|
||||
return corridor
|
||||
|
||||
def apply(self, tiles):
|
||||
for corridor in self.corridors:
|
||||
for pt in corridor:
|
||||
|
|
|
@ -8,7 +8,7 @@ import tcod
|
|||
from ... import log
|
||||
from ...geometry import Direction, Point, Rect, Size
|
||||
from ..room import Room, RectangularRoom
|
||||
from ..tile import Empty, Floor, Wall
|
||||
from ..tile import Empty, Floor, StairsUp, StairsDown, Wall
|
||||
|
||||
|
||||
class RoomGenerator:
|
||||
|
@ -17,8 +17,17 @@ class RoomGenerator:
|
|||
def __init__(self, *, size: Size):
|
||||
self.size = size
|
||||
self.rooms: List[Room] = []
|
||||
self.up_stairs: List[Point] = []
|
||||
self.down_stairs: List[Point] = []
|
||||
|
||||
def generate(self) -> bool:
|
||||
def generate(self):
|
||||
'''Generate rooms and stairs'''
|
||||
did_generate_rooms = self._generate()
|
||||
|
||||
if did_generate_rooms:
|
||||
self._generate_stairs()
|
||||
|
||||
def _generate(self) -> bool:
|
||||
'''
|
||||
Generate a list of rooms.
|
||||
|
||||
|
@ -33,6 +42,11 @@ class RoomGenerator:
|
|||
raise NotImplementedError()
|
||||
|
||||
def apply(self, tiles: np.ndarray):
|
||||
'''Apply the generated rooms to a tile array'''
|
||||
self._apply(tiles)
|
||||
self._apply_stairs(tiles)
|
||||
|
||||
def _apply(self, tiles: np.ndarray):
|
||||
'''
|
||||
Apply the generated list of rooms to an array of tiles. Subclasses must implement this.
|
||||
|
||||
|
@ -41,7 +55,33 @@ class RoomGenerator:
|
|||
tiles: np.ndarray
|
||||
The array of tiles to update.
|
||||
'''
|
||||
raise NotImplementedError()
|
||||
for room in self.rooms:
|
||||
for pt in room.floor_points:
|
||||
tiles[pt.x, pt.y] = Floor
|
||||
|
||||
for room in self.rooms:
|
||||
for pt in room.wall_points:
|
||||
if tiles[pt.x, pt.y] != Empty:
|
||||
continue
|
||||
tiles[pt.x, pt.y] = Wall
|
||||
|
||||
def _generate_stairs(self):
|
||||
up_stair_room = random.choice(self.rooms)
|
||||
down_stair_room = None
|
||||
if len(self.rooms) >= 2:
|
||||
while down_stair_room is None or down_stair_room == up_stair_room:
|
||||
down_stair_room = random.choice(self.rooms)
|
||||
else:
|
||||
down_stair_room = up_stair_room
|
||||
|
||||
self.up_stairs.append(random.choice(list(up_stair_room.walkable_tiles)))
|
||||
self.down_stairs.append(random.choice(list(down_stair_room.walkable_tiles)))
|
||||
|
||||
def _apply_stairs(self, tiles):
|
||||
for pt in self.up_stairs:
|
||||
tiles[pt.x, pt.y] = StairsUp
|
||||
for pt in self.down_stairs:
|
||||
tiles[pt.x, pt.y] = StairsDown
|
||||
|
||||
|
||||
class BSPRoomGenerator(RoomGenerator):
|
||||
|
@ -59,16 +99,12 @@ class BSPRoomGenerator(RoomGenerator):
|
|||
maximum_room_size=Size(20, 20),
|
||||
)
|
||||
|
||||
def __init__(self, *, size: Size, config: Optional[Configuration] = None):
|
||||
super().__init__(size=size)
|
||||
self.configuration = config if config else BSPRoomGenerator.DefaultConfiguration
|
||||
class BSPRoomGenerator(RoomGenerator):
|
||||
'''Generate a rooms-and-corridors style map with BSP.'''
|
||||
|
||||
self.rng: tcod.random.Random = tcod.random.Random()
|
||||
|
||||
self.rooms: List[RectangularRoom] = []
|
||||
self.tiles: Optional[np.ndarray] = None
|
||||
|
||||
def generate(self) -> bool:
|
||||
def _generate(self) -> bool:
|
||||
if self.rooms:
|
||||
return True
|
||||
|
||||
|
@ -89,7 +125,7 @@ class BSPRoomGenerator(RoomGenerator):
|
|||
)
|
||||
|
||||
# Generate the rooms
|
||||
rooms: List['RectangularRoom'] = []
|
||||
rooms: List[Room] = []
|
||||
|
||||
room_attrname = f'{__class__.__name__}.room'
|
||||
|
||||
|
@ -145,30 +181,6 @@ class BSPRoomGenerator(RoomGenerator):
|
|||
|
||||
return True
|
||||
|
||||
def apply(self, tiles: np.ndarray):
|
||||
for room in self.rooms:
|
||||
for wall_position in room.walls:
|
||||
if tiles[wall_position.x, wall_position.y] != Floor:
|
||||
tiles[wall_position.x, wall_position.y] = Wall
|
||||
|
||||
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
|
||||
|
||||
for y in range(self.size.height):
|
||||
for x in range(self.size.width):
|
||||
pos = Point(x, y)
|
||||
if tiles[x, y] != Floor:
|
||||
continue
|
||||
|
||||
neighbors = (pos + direction for direction in Direction.all())
|
||||
for neighbor in neighbors:
|
||||
if tiles[neighbor.x, neighbor.y] != Empty:
|
||||
continue
|
||||
tiles[neighbor.x, neighbor.y] = Wall
|
||||
|
||||
def __rect_from_bsp_node(self, node: tcod.bsp.BSP) -> Rect:
|
||||
'''Create a Rect from the given BSP node object'''
|
||||
return Rect(Point(node.x, node.y), Size(node.width, node.height))
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from typing import Iterator
|
||||
|
||||
from ..geometry import Point
|
||||
from ..geometry import Point, Rect
|
||||
|
||||
|
||||
class Room:
|
||||
'''An abstract room. It can be any size or shape.'''
|
||||
|
||||
def __init__(self, bounds):
|
||||
self.bounds = bounds
|
||||
def __init__(self, bounds: Rect):
|
||||
self.bounds: Rect = bounds
|
||||
|
||||
@property
|
||||
def center(self) -> Point:
|
||||
|
@ -15,13 +15,18 @@ class Room:
|
|||
return self.bounds.midpoint
|
||||
|
||||
@property
|
||||
def walls(self) -> Iterator[Point]:
|
||||
'''An iterator over all the wall tiles of this room.'''
|
||||
def wall_points(self) -> Iterator[Point]:
|
||||
'''An iterator over all the points that make up the walls of this room.'''
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def floor_points(self) -> Iterator[Point]:
|
||||
'''An iterator over all the points that make of the floor of this room'''
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def walkable_tiles(self) -> Iterator[Point]:
|
||||
'''An iterator over all the walkable tiles in this room.'''
|
||||
'''An iterator over all the points that are walkable in this room.'''
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
|
@ -43,16 +48,34 @@ class RectangularRoom(Room):
|
|||
yield Point(x, y)
|
||||
|
||||
@property
|
||||
def walls(self) -> Iterator[Point]:
|
||||
def wall_points(self) -> Iterator[Point]:
|
||||
bounds = self.bounds
|
||||
|
||||
min_y = bounds.min_y
|
||||
max_y = bounds.max_y
|
||||
min_x = bounds.min_x
|
||||
max_x = bounds.max_x
|
||||
for y in range(min_y, max_y + 1):
|
||||
for x in range(min_x, max_x + 1):
|
||||
if y == min_y or y == max_y or x == min_x or x == max_x:
|
||||
yield Point(x, y)
|
||||
|
||||
for x in range(min_x, max_x + 1):
|
||||
yield Point(x, min_y)
|
||||
yield Point(x, max_y)
|
||||
|
||||
for y in range(min_y + 1, max_y):
|
||||
yield Point(min_x, y)
|
||||
yield Point(max_x, y)
|
||||
|
||||
@property
|
||||
def floor_points(self) -> Iterator[Point]:
|
||||
inset_bounds = self.bounds.inset_rect(1, 1, 1, 1)
|
||||
|
||||
min_y = inset_bounds.min_y
|
||||
max_y = inset_bounds.max_y
|
||||
min_x = inset_bounds.min_x
|
||||
max_x = inset_bounds.max_x
|
||||
|
||||
for x in range(min_x, max_x + 1):
|
||||
for y in range(min_y, max_y + 1):
|
||||
yield Point(x, y)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'{self.__class__.__name__}({self.bounds})'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue