Intro
I am building a rogue like RPG, currently it is still a work in progress, but I would like to get an intermediate review of my Procedural map generation code.
I've chosen to use the Binary Space Partitioning algo, because it would make my life easier in the future when I will add corridors to the rooms.
There are some TODO's left,
- Creating random rooms within the leaves
- Adding corridors to connect the (random-sized) rooms
but they are not up for review.
Code
from queue import Queue
from random import choice, randint
from collections import namedtuple
from enum import Enum
Position = namedtuple("Position", ["y", "x"])
class Tile(Enum):
WALL = '#'
EMPTY = ' '
ROCK = '.'
class Split(Enum):
HORIZONTAL = 0
VERTICAL = 1
class Room():
def __init__(self, lu, rd):
self.lu = lu
self.rd = rd
@property
def position_map(self):
return self._get_positions()
@property
def border_map(self):
return self._get_border()
@property
def width(self):
return self.rd.x - self.lu.x
@property
def height(self):
return self.rd.y - self.lu.y
def _get_positions(self):
return [
Position(row, col)
for col in range(self.lu.x + 1, self.rd.x)
for row in range(self.lu.y + 1, self.rd.y)
]
def _get_border(self):
return [Position(y, self.lu.x) for y in range(self.lu.y, self.rd.y)] + \
[Position(y, self.rd.x) for y in range(self.lu.y, self.rd.y)] + \
[Position(self.lu.y, x) for x in range(self.lu.x, self.rd.x)] + \
[Position(self.rd.y, x) for x in range(self.lu.x, self.rd.x + 1)]
class Leaf():
def __init__(self, lu, rd, parent, min_room_space):
self.lu = lu
self.rd = rd
self.parent = parent
self.min_room_space = min_room_space
self._children = None
self._sibling = None
self._room = None
@property
def children(self):
return self._children
@children.setter
def children(self, value):
self._children = value
@property
def sibling(self):
return self._sibling
@sibling.setter
def sibling(self, value):
self._sibling = value
@property
def room(self):
return self._room or self._generate_room()
@property
def width(self):
return self.rd.x - self.lu.x
@property
def height(self):
return self.rd.y - self.lu.y
def _generate_room(self):
#TODO create random sized room in the leaf
room = Room(self.lu, self.rd)
self._room = room
return room
class Map():
def __init__(self, width, height, min_room_space=10, split_threshold=1.25):
self.width = width
self.height = height
self.lu = Position(0, 0)
self.rd = Position(height-1, width-1)
self.min_room_space = min_room_space
self.split_threshold = split_threshold
self._leaves = None
self.board = [[Tile.ROCK.value] * (self.width) for _ in range(self.height)]
@property
def leaves(self):
return self._leaves
def generate(self):
# Reset the board
self.board = [[Tile.ROCK.value] * (self.width) for _ in range(self.height)]
self._generate_leaves()
for leaf in self.leaves:
for pos in leaf.room.position_map:
self.board[pos.y][pos.x] = Tile.EMPTY.value
for pos in leaf.room.border_map:
self.board[pos.y][pos.x] = Tile.WALL.value
#TODO Create corridors by adding corridors from the children up to the highest in the tree
def _generate_leaves(self):
splitable = Queue()
splitable.put(Leaf(self.lu, self.rd, None, self.min_room_space))
leaves = Queue()
while splitable.qsize() > 0:
leaf = splitable.get()
leaf_a, leaf_b = self._split(leaf)
if leaf_a is None and leaf_b is None:
leaves.put(leaf)
else:
leaf.children = (leaf_a, leaf_b)
leaf_a.sibling = leaf_b
leaf_b.sibling = leaf_a
splitable.put(leaf_a)
splitable.put(leaf_b)
self._leaves = list(leaves.queue)
def _split(self, leaf):
if leaf.width / leaf.height >= self.split_threshold:
return self._split_room(leaf, Split.HORIZONTAL)
elif leaf.height / leaf.width >= self.split_threshold:
return self._split_room(leaf, Split.VERTICAL)
else:
return self._split_room(leaf, choice([Split.VERTICAL, Split.HORIZONTAL]))
def _split_room(self, leaf, direction):
leaf_a = leaf_b = None
if direction == Split.VERTICAL:
if not leaf.height < self.min_room_space * 2:
random_split = randint(leaf.lu.y + self.min_room_space, leaf.rd.y - self.min_room_space)
leaf_a = Leaf(leaf.lu,
Position(random_split, leaf.rd.x),
leaf,
self.min_room_space)
leaf_b = Leaf(Position(random_split + 1, leaf.lu.x),
leaf.rd,
leaf,
self.min_room_space)
elif direction == Split.HORIZONTAL:
if not leaf.width < self.min_room_space * 2:
random_split = randint(leaf.lu.x + self.min_room_space, leaf.rd.x - self.min_room_space)
leaf_a = Leaf(leaf.lu,
Position(leaf.rd.y, random_split),
leaf,
self.min_room_space)
leaf_b = Leaf(Position(leaf.lu.y, random_split + 1),
leaf.rd,
leaf,
self.min_room_space)
return leaf_a, leaf_b
def __str__(self):
return "\n".join("".join(b) for b in self.board)
if __name__ == "__main__":
m = Map(50, 50, 10)
m.generate()
print(m)
Example Output
##################################################
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ############
# ## ## ############
# ## ## ## #
# ## ## ## #
######################################## #
######################################## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ############
# ## ## ############
# ## ## ## #
# ## ## ## #
######################################## #
######################################## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
# ## ## ## #
##################################################
Please critique any and all