Day 5 (c): DCSS’s Insane (-ly cool) corridors

A “typical” BSP-based dungeon generation routine generates functionally clean levels (e.g. all rooms connected, no random or “dead end” corridors [which I hate! *grumble*]). However, it’s also fairly… straightlaced. I’m not sure I’d notice it in a game, but looking at the levels my generator is generating, they feel kind of blah. So I took a look at DCSS, and came to the realization that the DCSS dungeons were fabricated by utterly insane digger gnomes.

The funny thing was, I never questioned the odd layout artifacts that you can get in DCSS; e.g. doors in walls in the middle of a room (and where you can take two steps to walk around it!). In fact, I really do like the layouts, and I just find it odd that I never questioned it – maybe it’s because DCSS is my “brain coma candy…”

At any rate, so I took a look at an example DCSS map (btw; yes, I realize DCSS has multiple map types – I’m just focusing on one for now). Take a gander:

Now tell me – what crazy, addled, pea-brained digger gnome is goin to dig something like that? It’s like a flattened termite’s nest (which, hey, may actually be how gnomes would dig!). Still; it’s fun! So, I want it in my version :).

The Crazy Corridors Algorithm

Taking a look at the image above, it doesn’t take too long to come up with an algorithm that provides crazy corridors while maintaining the “all rooms must be connected (eg no orphans)” requirement. Here’s what I do:

Partition the map just like I did before

Enumerate all leaf nodes (which contain the rooms), and create a list of rooms

Randomize the list of rooms

Connect room ‘n’ to room ‘n + 1’ (not looping at the end of the list). Note that the DiggerGnome needs to be updated (we did this in the last post) to have a mode which says “just keep digging!”, since the previous model had it dig “until you run into something.” Now, we explicitly do not want to stop, and instead keep going right through those rooms. That’s what generates the crazy (and interesting!) look to the level.

And that’s it! Note that you want to randomize the list (step 3), because the list you get by enumerating leaf nodes will return rooms that are next to each other in the partitioning, thus reducing the number of long corridors. We want lots of long corridors to maximize the craziness. We could actually optimize further (e.g. don’t randomize, but instead generate a list of farthest-possible-rooms; and interesting problem but not for now) – but it’s not necessary, as the random approach works well enough.

I have no idea if this is how DCSS does it, but it works well for me. Here’s the high level algorithm:

// Mimic DCSS's approach - get a list of rooms; randomize it; then dig corridors between them all, going right through rooms as we go.// This approach generates odd (eg doors in the middle of nowhere), but cool looking layouts.// 1. Get the list of regions with rooms in them// 2. Randomize the list (so we don't connect too many rooms that are right next to each other; counter-intuitive, I know)// 3. Connect the lists in order (connect #0 to #1, connect #1 to #2, etc). brute force it.// Generate the list of rooms. Don't pass a corridor generator since we're handling it ourselves post-processing.
partitionedTree.BottomsUpByLevelEnumerate(new RoomGeneratorDelegate(roomGenerator), null);
// Get the list of rooms
List<DungeonBSPNode> roomRegions = partitionedTree.GetRoomRegions();
// Randomize the list order (Go go Gadget-C#!)
roomRegions.Sort(delegate { return (Rand.Next(2) == 1) ? -1 : 1; });
// Connect the room regions in the newly randomized list order
DungeonBSPNode previousRoom = null;
foreach (DungeonBSPNode currentRoom in roomRegions)
{
if (previousRoom != null)
BruteForceConnectRooms(previousRoom, currentRoom);
previousRoom = currentRoom;
}

GetRoomRegions() is just a Leaf enumeration of the BSP tree
btw – note the way the list of roomRegions gets sorted :). Gotta love C# (oy, gonna be some pain when I migrate to Objective C).

And here is the code for the BruteForceConnectRooms method. This code is very similar to the DefaultCorridorGenerator; while I could have merged it, I worry about conflating/confusing the functions (which already take a bit of parsing to fully grok). I may move them together later, but for now am keeping them separate and simply assuming I’ll never have to change anything in them ;-).

Next up – not sure yet. May be time to move away from the level generation and into the actual game flow, but I may play around with a bit of dungeon population. (Oop, just remembered that I still need to re-enable Special Rooms with the corridors; had some good thoughts on that in the car – guess that’s next).