Create a Random Dungeon with Python

RedXIII | June 23, 2021, 3:54 p.m.

Creating a random dungeon generator is a fun and exciting project to learn Python. In this tutorial, we’ll cover a simple, but effective algorithm you can use to create a random dungeon using the Python programming language. Along the way we’ll cover using random numbers to vary the output of our program, ensuring that every dungeon we create is a little bit different. 

And finally, using the Pycairo library, we’ll demonstrate how to draw a picture of your dungeon to share on social media. By the end, you’ll have a cool graphic that you can use to share your new random dungeon generating skills!


Getting Started

I’m going to assume you already have Python installed on your computer. You’ll also need to install the Pycairo library that we’ll use to draw pictures with our Python code.

Open a new command prompt or terminal and type in the following command to install Pycairo:

pip install pycairo

Once pip finishes installing the package, you’ll be all set.

A Closer Look at Generating Dungeons in Python

There are a lot of ways to generate a dungeon. You could easily write a book on the subject. In the name of brevity, we’re going to follow a basic algorithm to create a dungeon.

This algorithm only uses four steps. 

  1. Generate an empty map

  2. Create random rooms, making sure none of them overlap

  3. Connect rooms at random with horizontal and vertical passageways

  4. Draw the map

Once we’ve finished generating the map, we can draw it using Pycairo. Ready? Let’s get started.

Building the Map

Our dungeon will essentially be a grid of squares. Think of it like a chessboard. Each square will either be black or white. We’ll use dark colors for the walls and light colors for the passages and rooms.

 

Our dungeon map will be stored as a Python dictionary. We’ll use key/value pairs to associate each square of the map with x and y coordinates.


First, create some variables to hold the width and height of the map grid. If we were making a chessboard, for instance, we would make the grid 8 by 8 squares. Since we’re making a dungeon, however, we’ll go 50 squares wide and 50 squares tall.

map_width = 50 # number of squares wide

map_height = 50 # number of squares tall


map = {}


def init_map():

    """Initializes the map of key/value pairs."""


    for y in range(map_height):

        for x in range(map_width):

            map[x,y] = 0 # set every square to a wall


def generate_dungeon():

    init_map()

    #init_rooms()

    #connect_rooms()

    #draw_dungeon()


if __name__ == "__main__":

    generate_dungeon()

The init_map() function will create an empty map. Each square—defined by its position in the grid map—is set to a default value of 0. In our dungeon map, 0 will represent a wall and 1 will represent a room or passageway.

Creating the Room Class

Before we can create random rooms, we need to define exactly what a room is. We can do this using a Python class. A class lets us create our own Python objects that hold user defined data and methods.

Before we can create random rooms, we need to define exactly what a room is. We can do this using a Python class. A class lets us create our own Python objects that hold user defined data and methods.


We’ll create a new room class that will hold each room's position on the map. The room class will also keep track of the room’s width and height (in map squares).

class Room:

    """Defines a room of the dungeon."""

    def __init__(self,x,y,width,height):

        self.x = x

        self.y = y

        self.width = width

        self.height = height


    def __str__(self):

        return f"A room at ({self.x},{self.y})"

We’ll include a __str__ method just in case we want to print some information about a room to the console. With the class created, we can move on to generating random rooms.

Generating Dungeon Rooms with Random Numbers

Because we want to use random numbers, we need to import the random library.

import random

Before we can generate random rooms, we’ll need to add a few more variables to the top of our script. These will be used to control the amount of rooms created and the maximum and minimum sizes a room can be.
Later, these values can be adjusted to alter the output of our dungeon program.

min_room_size = 20

max_room_size = 20

max_rooms = 10

min_rooms = 3

max_iters = 3


rooms = []

Next we’ll write a function to initialize and populate our room data. This function will rely on random numbers to generate rooms of various sizes and locations on the map. We can use the randrange() function to create random number data.

def init_rooms():   

 """Initializes the rooms in the dungeon."""


    total_rooms = randrange(min_rooms,max_rooms)


    for i in range(max_iters):

        for r in range(total_rooms):

            if len(rooms) >= max_rooms:

                break


            x = randrange(0,map_width)

            y = randrange(0,map_height)


            width = randrange(min_room_size,max_room_size)

            height = randrange(min_room_size,max_room_size)

            room = Room(x,y,width,height)


            if check_for_overlap(room, rooms):

                pass

            else:

                rooms.append(room)


    for room in rooms:

        for y in range(room.y, room.y+room.height):

            for x in range(room.x, room.x+room.width):

                map[x,y] = 1


One thing to note is that we have to repeat the room generation loop for a certain number of iterations to ensure we get enough rooms. If the list of rooms ever reaches the maximum room limit, we halt room generation.

How to Prevent Overlapping Rooms

To make sure our dungeon has some nice looking rooms, we want to check that no two rooms overlap. We can do this by checking the positions and sizes of the rooms. When we create a new room, we’ll check that it doesn’t overlap any rooms we’ve already created. If it does, we’ll reject it and attempt to create another one.

def check_for_overlap(room, rooms):

    """Return false if the room overlaps any other room."""

    for current_room in rooms:

        xmin1 = room.x

        xmax1 = room.x + room.width

        xmin2 = current_room.x

        xmax2 = current_room.x + current_room.width


        ymin1 = room.y

        ymax1 = room.y + room.height

        ymin2 = current_room.y

        ymax2 = current_room.y + current_room.height


        if (xmin1 <= xmax2 and xmax1 >= xmin2) and \

           (ymin1 <= ymax2 and ymax1 >= ymin2):

            return True


    return False

Connect the Rooms

At this point our program can create rooms in the dungeon, but they don’t connect. Anyone hoping to navigate the dungeon would be out of luck. We need a function that will carve passageways between the rooms.

We’ll use an easy method to do this. Using the location data of each room, we’ll draw horizontal and vertical corridors between random rooms. This will leave us with a gnarly dungeon worthy of an adventurer’s time.

def connect_rooms():

    """Draws passages randomly between the rooms."""


    shuffle(rooms)

    for i in range(len(rooms)-1):

        roomA = rooms[i]

        roomB = rooms[i+1]


        for x in range(roomA.x,roomB.x):

            map[x,roomA.y] = 1

        for y in range(roomA.y, roomB.y):

            map[roomA.x,y] = 1


        for x in range(roomB.x,roomA.x):

            map[x,roomA.y] = 1

        for y in range(roomB.y, roomA.y):

            map[roomA.x,y] = 1

Drawing the Dungeon Map with Pycairo

The last step is to create a visual of the dungeon map so we can view our work. We’ll do this using Pycairo, a library for creating graphics with Python. 


Make sure that you’ve imported Pycairo.

import cairo

With Pycairo, we can create a surface to draw on. We’ll pass in the size of the image (500 x 500 pixels). With a nested for loop, we can check every square of the map and draw a 10 x 10 rectangle based on its location. 


The color of the rectangle will depend on the type of square. A dark grey is used for the walls and a light grey is used for the passages and rooms.


The set_source_rgb() method is used to change the color of the rectangle before they are filled by the fill() method. We’re using an RGB format where each value is a floating point between 0 and 1.

def draw_dungeon():

    """Draw the dungeon with cario rectangles."""

    surface = cairo.ImageSurface(cairo.FORMAT_RGB24,500,500)

    ctx = cairo.Context(surface)


    for y in range(50):

        for x in range(50):

            r = randrange(1,10)

            if map[x,y] == 0:

                ctx.set_source_rgb(0.3,0.3,0.3)

            else:

                ctx.set_source_rgb(0.5,0.5,0.5)

            ctx.rectangle(x*10, y*10, 10, 10)

            ctx.fill()

    surface.write_to_png("dungeon.png")

    print("Total rooms: " + str(len(rooms)))


Feel free to experiment with different colors to create a map that fits better with your personal style.

Summary

Hopefully you’ve learned something from this example. Generating a random dungeon in Python is a fun project, and one you can easily expand upon. 


Can you think of ways to improve the generator? At the moment, we have no way in or out of the dungeon. Perhaps you could write functions to add these locations.

Related Posts

If you enjoyed this tutorial and would like to learn more about Python or computer programming, check out these links for guides and how-to's from Start Prism.


About Us

Learning at the speed of light.

We created Start Prism to help students learn programming. You can find exercises and recent tutorials below.

Topics Quizzes Tutorials

0 comments

Leave a comment