#StackBounty: #python #python-3.x #game #role-playing-game creating procedural generated RPG Dungeon maps

Bounty: 50

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


Get this bounty!!!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.