rooms and corridors
An empty room with one door is a reliable place to fight. Stand in the doorway and let things come to you. Give the room a second door and that little plan starts to unravel.
That’s the part of room generation I find interesting. A rectangle is easy to dig. Deciding where it belongs, what breaks up the floor, and how you get out of it takes a little more care.
Here are a few ways to approach it. The dungeon examples are 28 tiles wide.
# means wall, . means floor, and letters mark room centres. Walking is
four-way. The letters are labels, not creatures waiting in the dark.
Try a rectangle and see if it fits
Start with solid walls. Pick a width, a height and a position. Accept the room if it fits inside the map and leaves at least one tile of wall between its floor and any earlier room. Otherwise, throw that candidate away and try another.
This example uses seed 13, floor widths from four to seven tiles, and heights from three to five. Six rooms made it through:
############################
####################.......#
####################...C...#
############.....###.......#
#####.....##..F..###########
#####.....##.....###########
#####..B..##################
#####.....##################
#####.....##################
###############......#######
###############......#######
######......###...D..#######
######......###......#.....#
######...A..###......#.....#
######......##########..E..#
######################.....#
############################
They are labelled in the order they were accepted. To connect them, each room after A gets a corridor to the nearest earlier room, measured by the horizontal and vertical distance between their centres. The corridor goes along one axis, then the other, with the order chosen randomly.
############################
####################.......#
#######................C...#
#######.####.....###.......#
#####.....##..F..###########
#####.....##.....###########
#####..B.......#############
#####.....##################
#####.....##################
#######.#######......#######
#######.#######......#######
######............D..#######
######......###......#.....#
######...A..###......#.....#
######......######......E..#
######################.....#
############################
Every addition joins something already connected to A. That’s enough to make the whole floor reachable. The corridors are allowed to cross earlier floor; a stricter generator would need to route around rooms it should leave alone.
Give the placement loop an attempt limit. Near the end, most guesses may land on occupied space. This script stops after six rooms or 120 attempts, whichever comes first. It can return fewer rooms. If a level needs a minimum number, check that explicitly instead of hoping the loop will eventually squeeze them in.
The Python roguelike tutorial’s room-and-tunnel approach
Divide up the space first
Binary space partitioning, usually shortened to BSP, starts by giving each room somewhere to go. Split the available rectangle in two, split the pieces again, and stop while each piece is still large enough to hold a room and its surrounding walls.
Here the first cut is vertical. Each side then gets a horizontal cut at a different height. These are planning boundaries, not dungeon walls:
+--------------------------+
| | |
| | |
| | |
| A | C |
| | |
| | |
|--------------| |
| |-----------|
| | |
| | |
| | |
| B | D |
| | |
| | |
| | |
+--------------------------+
Choose a room inside each final piece. Then work back up the tree, connecting the two child areas at each split. In this small example, A joins B, C joins D, and a corridor between B and D joins the two halves.
############################
############################
##......####################
##...A..####################
##......#########...########
#####.###########.C.########
#####.###########...########
#####.############.#########
#####.############.#########
#####.....########.#########
#####..B..#######......#####
#####...............D..#####
#################......#####
############################
############################
############################
############################
The tree gives you a straightforward connectivity rule. If each child area is connected internally, joining the two makes their parent connected too. Repeat that until you reach the whole map.
There is still a lot of stone in this example. Reserving a region doesn’t mean its room has to fill it. Larger minimum rooms would use more of that space. Uneven cuts and different stopping depths can give a large hall several smaller neighbours, instead of dividing everything into similar little compartments.
Give some rooms a shape worth keeping
Random dimensions only get you so far. A room template can carry a more deliberate arrangement: a crypt with pillars, an L-shaped store, a shrine with an awkward approach. The generator chooses where it fits and which way it faces.
This little pillar room has two door sockets, shown as +:
######+######
#...........#
#..#.....#..#
#...........#
+.....#.....#
#...........#
#..#.....#..#
#...........#
#############
Turn it clockwise:
####+####
#.......#
#.......#
#.#...#.#
#.......#
#.......#
#...#...+
#.......#
#.......#
#.#...#.#
#.......#
#.......#
#########
The sockets turn with the walls. The north socket is now on the east side; the west socket has moved north. Losing that relationship is an easy way to put a perfectly good room behind an unbroken wall.
For placement, reserve the whole footprint, check the surrounding tiles, and route passages to the sockets. Check that the sockets connect through the room as well. A decorative pillar can block a one-tile passage just as effectively as a wall.
There is room for variation inside a template. Swap the contents of an alcove, leave a shelf empty, change which doorway is open. I would keep the features that make the room readable. If every part changes independently, the shrine soon becomes another collection of unrelated objects.
Cogmind’s notes on fitting prefabs into rooms
Leave a second route
The connections deserve their own sketch. Here each letter is a whole room and each line is a passage. This is a schematic, separate from the tile maps above:
A---B---C
| |
D---E
|
F
B, C, E and D form a loop. A and F remain dead ends. You can keep a small side chamber for something worth finding without making every excursion end in the same walk back.
A connected graph of six rooms needs at least five links. With exactly five, it is a tree: there is only one route between any two rooms. An extra link between previously unlinked rooms creates a cycle. That can be more useful than adding another room to the end of a corridor.
The graph is only the plan. Carved corridors can cross, and a locked door can remove a route that looked available. Walk the actual tiles from the entrance before placing the stairs. If there are keys, check that a key can be reached without passing through the door it opens.
Then put a few creatures in and look again. A large room with every doorway on one wall may still play like a dead end. A small room with a pillar and two exits can give you several bad ideas to choose from. I’d rather have that than ten more empty rectangles.
Examples to change
The script prints the six map examples above. Scattered placement uses seed 13; the partitioned layout uses seed 7. The pillar room is a fixed template. Everything runs with the Python standard library.