I7: Generating a Random Dungeon

I want to make a dungeon that starts out as a bunch of unconnected rooms, but will piece them together randomly as the player explores. “All Roads Lead to Mars” is almost helpful, except it allows the player to travel in any direction. The first room should only have one exit, to the north, and the location the player arrives in after going north should be random. How can I achieve this?

I haven’t tested this, but it’s basically a rewrite of the relevant bit of “All Roads Lead to Mars” with a restriction that says you can only go north, and some stuff hard-coded to reflect that. (If you copy this, you have to turn the spaces into tabs properly.)

Before going in Dungeon Entrance: if the noun is not north: say "You can only go north." instead; otherwise: let further place be the room north from Dungeon Entrance; if further place is a room: continue the action; otherwise: let further place be a random unvisited room [or something like that; maybe you want to define a property that restricts which rooms can be north of Dungeon Entrance]; change the north exit of Dungeon Entrance to further place; change the south exit of further place to Dungeon Entrance.

Of course that leaves the question of what you do in the second room, etc. What I’d do (and I’m kind of faking it here, there are people with lots more I7 experience here, some of whom have probably done basically this) would be to create a relation between rooms and directions that holds if a room has an exit in that direction. (Don’t give each room a list of directions, because lists are huge memory hogs.) Then you can use the “All Roads Lead to Mars” code with an additional check to see if there’s an exit in that direction, and as you’re going along generate the exits for each new room you create and set the relation accordingly. Make sure you don’t generate more exits than you have unvisited rooms.

In fact, if you’ve done all that, you probably don’t need to write a special rule for Dungeon Entrance – just start by setting Dungeon Entrance to relate only to north and the rest will take care of itself.

That’s what I’d try to do if I were doing this – I haven’t even typed the above code into the IDE, so I don’t know if it works. One thing to make sure is that you can’t set the north exit of Dungeon Entrance to itself; I think Dungeon Entrance should be flagged as visited whenever you’re there, but if it isn’t, that could produce a bug.

The extension Dynamic Rooms has an example very similar to what you’re talking about, IIRC. You certainly don’t need Dynamic Rooms to make a random dungeon, but it would make it easier.

Thanks Matt, your solution works perfectly. Instead of using “random unvisited room,” I created a kind of room for each floor of the dungeon, so that I can designate which rooms will be on which floor. I just have to write the same kind of code for each unique room. However, it gets more complicated if there is more than one exit to the room. I don’t think I can write:

Before going in Room with Four Exits: if the noun is not north, south, east, or west:...

Is there any way I could achieve this with wording that Inform will actually understand?

Dynamic Rooms doesn’t seem to do what I want, since I don’t want just a bunch of generic rooms, but rather some actual rooms with descriptions and objects etc. to be placed randomly so that each time the game is played it will be a slightly different experience.

This one should be simple – write out every alternative as a complete sentence:

If the noun is not north and the noun is not south and the noun is not east and the noun is not west...

Or you could use an “Unless.” Watch your ands and ors – it’s natural to write “or” in the code here, but that’ll give you something that’s always true.

However: I don’t know if you want to write a special rule for each room, and I also don’t know exactly how flexible your room descriptions are. (For instance, if you’ve predefined a room with only north and south exits, what happens if the game tries to place it after going east from somewhere?) This is where I was thinking of using a relation. You write something like this:

Leading relates various rooms to various directions. The verb to lead (he leads, they lead, he led, it is led, he is leading) implies the leading relation.

And then you declare that every room leads in the directions where it has permissible exits. So far we’d have:

The Dungeon Entrance leads north. The Room with Four Exits leads north, south, east, and west. [I'm not positive the second sentence will compile; you might have to declare each of those separately]

Then you should be able to write something like:

Before going: unless the location leads the noun: say "You can only go [list of directions led by the location]."; [again, not quite sure of the syntax there]

and so on. You’d also either want to make sure that you either only add rooms on that already lead in the right direction (that is, if you’re going north, make sure you pick a room that leads south) or change the newly added room so its leading relation and description reflect the new exit (so if you’ve just gone north, make sure the room gone into now leads south, and make the description of the exits variable so you know which way you’ve gone).

BTW, the maps this produces will be pretty non-Euclidean; if you go N/S/E/W from somewhere you won’t get back where you started. That’s probably fine, but if you want that not to happen you’d probably have to assign each room a coordinate and make sure that if you arrive back at an already visited coordinate, the room that’s there is the one that’s supposed to be there. That’d be a pain though (and you’d have to make sure that the exits line up).

As ever, all code completely untested and all suggestions not thought through very well!

Yeah, I thought of making a new relation, and it works beautifully. Except I used the word “abscond” instead of “lead”, but it’s just a code detail. I used the Mars example I mentioned earlier to make the rooms stay where they are once they are placed somewhere, so the map stays very Euclidean. You just have to add the following onto the end of the “Before going…” rule for each room:

let reverse be the opposite of the way; let further place be a random unvisited room that absconds [or leads] reverse; change the way exit of current place to further place; change the reverse exit of further place to current place.

Then you can go back and forth reliably every time, until you hit Go! again and start a new game.

I also came across some run-time errors when it ran out of unvisited rooms. This can be avoided by putting in four rooms, making them a kind of room called extra or something, that are dead ends and don’t contain anything. They’ll jump around the map as they’re needed, steering the player away from the edges of the map.

The only thing is that you can’t drop anything in them, because they jump around. I think I’ll fix that by having whatever you drop there end up in the last room you were in (which is a fixed room), explaining it by having the dead end rooms be very tiny.

Anyway, I’m wondering if there is a way to make some rooms rarer than others. So, when we have Inform look for a “random unvisited room”, some rooms have a higher chance of being chosen than others. Is there a way to do this? And if you have to keep making untested and not well thought out suggestions, please do, because they’ve all worked so far!

Right, that’s good – I mangled my example. What I meant was, if you go N-E-S-W, you won’t get back to where you started. Which isn’t necessarily a problem, since nothing says that every corridor is the same length.

Another thing you could do is just forbid the player from going in a certain direction when you’re out of unvisited rooms. Install a check as to whether there are any rooms available, and if not, when the player goes in a direction that the room absconds, say “The corridor dead-ends after a few feet and you retreat back to [the location].” or something like that. Preferably with some variations. It might be possible to keep track of which objects are supposed to be in the dead-end when it’s north of room X and move them on- and off-stage accordingly, but that seems like it’d be annoying.

One thing that occurs to me is that you’ll never get loops this way – if a room has a south exit that you haven’t taken, you’ll never go north from another room and find yourself in that one. The dungeon will have a tree structure, which means a lot of dead ends. Maybe instead of checking for unvisited rooms you could check for rooms with an available south exit, whether it’s because they haven’t been added to the map yet or because they’re on the map but the south exit is unexplored. This could lead to some serious non-Euclideanness, though (go east five times, go north three times, and the last time you go north you wind up back at your starting point because it had an unexplored south exit).

This doesn’t seem as straightforward as you might like. Basically I think you have to define a table with the weight for each room and then write a couple of to-decide phrases that pick a room based on that weighted probability. Victor Gijsbers explains how to write those phrases here.

I came up with a way to not have to make a message for every single room.

[code]A dungeon is a kind of room.

Throughway relates various rooms to various directions. The verb to abscond (it absconds, they abscond, it absconded, it is absconded, it is absconding) implies the throughway relation.

Occupancy relates various rooms to various directions. The verb to occupy (it occupies, they occupy, it occupied, it is occupied, it is occupying) implies the occupancy relation.

Before an actor going a direction (called way) in a room (called current place):
if current place does not abscond the way:
say “You can’t go that way.” instead;
otherwise:
let further place be the room the way from current place;
if further place is a room:
continue the action;
otherwise:
let reverse be the opposite of the way;
let further place be a random dungeon that absconds reverse;
if further place occupies reverse:
say “[one of]That way seems to lead to a dead end.[or]A cave-in has completely blocked this hallway.[purely at random]”;
now current place occupies the way;
now current place does not abscond the way;
stop the action;
if further place is current place:
say “[one of]That way seems to lead to a dead end.[or]A cave-in has completely blocked this hallway.[purely at random]”;
now current place occupies the way;
now current place does not abscond the way;
stop the action;
now the current place occupies the way;
now the further place occupies reverse;
change the way exit of current place to further place;
change the reverse exit of further place to current place.

Entrance is a dungeon. “You can go north, south, east, and west.” It absconds north, south, east, and west.

Test Room is a dungeon. “You can go north, south, east, or west.” It absconds north, south, east, and west.

etc.[/code]
With a bunch more dungeons added in, this successfully creates a random map from them. It doesn’t always use all the rooms available, which is how I want it for now. With 15 rooms besides the entrance, it still seems rather linear, but maybe with more rooms that each have more exits it wouldn’t seem that way. Of course, they’re still non-Euclidean, but I’ll worry about that later if at all.

It chooses a random message when you find a dead end so that it doesn’t seem artificial (I’ll add more in when I think of them).

I think instead of trying to make rare rooms, I’ll try to make rooms that require circumstances to be met before they can be entered. Of course, rooms with less entrances will inherently be more rare than rooms with more entrances, so I can put more entrances on rooms I want the player to come across in almost every game and vice versa.

You can do this pretty easily. Define rooms as either common or rare. Then instead of saying “let X be a random room”, you use something like “if a random chance of 1 in 10 succeeds: let X be a random rare unvisited room; otherwise: let X be a random common unvisited room.” (With correct indentation, of course.) This way you can make sure that you get a rare room exactly 10% of the time – or whatever other number you would prefer.

I’m working on a randomly generated dungeon game myself, and it’s good to see more efforts in that direction. :slight_smile: (Though my dungeon is completely generated at the start of the game, so it’s a bit different.)

Victor, thanks, I hadn’t thought of that! I love how the solutions to the most complex problems are usually the simplest. :confused:

I hope this isn’t thread necromancy, but I’ve been reworking how the code works and I’ve run into a strange problem of “secret doors”. When I try this code:

[code]Section - Directions

A direction can be standard. North is standard. South is standard. East is standard. West is standard.

Occupancy relates various rooms to various directions. The verb to occupy (it occupies, they occupy, it occupied, it is occupied, it is occupying) implies the occupancy relation.

Section - Dungeons

A dungeon is a kind of room.

After looking:
if the location occupies a direction:
say “You can go [list of directions occupied by the location].”

When play begins:
repeat with C running through dungeons:
construct C.

To construct (X - a dungeon):
let N be the level of X;
repeat with A running through standard directions:
let B be the opposite of A;
if a random chance of 1 in 4 succeeds:
let L be a list of rooms;
repeat with D running through dungeons:
unless D occupies B:
if D is not X:
add D to L;
unless L is empty:
sort L in random order;
let R be entry 1 of L;
change the A exit of X to R;
change the B exit of R to X;
now X occupies A;
now R occupies B;
truncate L to 0 entries.[/code]

…then add in a bunch of dungeons for testing, it constructs the entire map when play begins, so it’s less linear. However, every so often, something like this will happen:

Rock Room
You can go south and east.

Go east.

Stone Room
You can go west.

Go west.

Strange Room

I thought the “unless D occupies B” line should stop this from happening?

I’ve tried this with some say phrases added to narrate how it constructs the dungeon, and it looks like what happened is that it added no exits to First Room, added Sixth Room as a north exit to Third Room, and then added First Room as a south exit to Sixth room.

It looks to me that the problem is that when you’re constructing X, you don’t check to see whether X already occupies the direction. So in the example above, the Sixth Room already occupied south, because it had been added as the north exit to Third Room. If I’m correct, you need to add “if X does not occupy A:” to the beginning of your repeat through standard directions loop. (I commented out the line about the level of the room, since that’s not implemented in your code fragment.)

In addition to what Matt said, your code allows all kinds of weird geometries, where you go up three times and arrive back at the room you began in. Maybe that is what you want – this was very common in old school IF. If you would prefer your dungeon to be laid out along a normal (x,y)-grid, though, you’ll have to do some additional work.

I’ll paste some of the code of my Kerkerkruip Dungeon Generation below: it creates a dungeon on an (x,y,z)-grid. It is long, has few comments, and probably does a lot of stuff you’re not interested in doing (such a supporting complex rules that decide which rooms can be placed where, and checking whether the generated map is “fair” for dungeon crawlers) – but then again, posting it can’t hurt. (In other words: feel no obligation to read through it! But if you do read through and see stuff you’d like to use, feel free to do so. The end of the code especially should have some routines that can be reused if you plan on using a grid-like system.)

[spoiler][code]Book - Creating the Map

Part - Preliminaries

Section - Directions

A direction is either cardinal or not cardinal. A direction is usually not cardinal. North is cardinal. South is cardinal. East is cardinal. West is cardinal. Up is cardinal. Down is cardinal.

Section - Coordinates

A room has a number called the x-coordinate. [north]
A room has a number called the y-coordinate. [east]
A room has a number called the z-coordinate. [up]

Section - Placed and unplaced

A room is either placed or not placed. A room is usually not placed.

Section - Global variables

Considered room is a room that varies. [The room to be placed.]
Original room is a room that varies. [The room from which it is placed.]

Considered-x is a number that varies. [Coordinates of the new location.]
Considered-y is a number that varies.
Considered-z is a number that varies.

Current room score is a number that varies. [Used to check likelihood of placement.]

Additional considered room is a room that varies. [For use with additional placement rules. Please do not take them a step further.]
Additional original room is a room that varies.

Part - Main Routines

Chapter - Resetting and Creating the Map

Section - Resetting the map

The resetting the map rules are a rulebook.

First resetting the map rule (this is the destroy all connections rule):
repeat with place running through rooms:
now place is not placed;
repeat with way running through cardinal directions:
change the way exit of place to nothing;
now the x-coordinate of place is 100;
now the y-coordinate of place is 100;
now the z-coordinate of place is 100.

Resetting the map rule (this is the we start with entrance Hall rule):
now the x-coordinate of Entrance Hall is 0;
now the y-coordinate of Entrance Hall is 0;
now the z-coordinate of Entrance Hall is 0;
now Entrance Hall is placed.

Section - Creating the map and collapsing passages

The creating the map rules are a rulebook.

Creating the map rule (this is the locate and connect all rooms rule):
while less than twelve rooms are placed or less than eight habitable rooms are placed:
let place be a random placed room;
let way be a random cardinal direction;
let x be the x way of place;
let y be the y way of place;
let z be the z way of place;
if the space at x by y by z is free:
let chosen room be a suitable room from place at x by y by z;
place chosen room from place at x by y by z.

Collapse relates rooms to each other. The verb to collapse (he collapses, they collapse, he collapsed, it is collapsed, he is collapsing) implies the collapse relation.

Last creating the map (this is the possibly adding some further connections rule):
repeat with place running through placed connectable rooms:
repeat with further place running through placed connectable rooms:
let way be the direction from place to further place;
if way is not northwest:
if further place is not the room way of place:
if a random chance of 1 in 4 succeeds or place is connection-inviting or further place is connection-inviting: [gives a roughly 1 in 2 chance of connections being made]
if generation info is true, say “* Adding connection between [place] and [further place].[line break][run paragraph on]”;
change the way exit of place to further place;
let reverse be the opposite of way;
change the reverse exit of further place to place;
if a random chance of 1 in 4 succeeds:
now place collapses further place.

Resetting the map rule (this is the reset collapse rule):
repeat with place running through rooms:
while at least one room collapses place:
let place2 be a random room that collapses place;
now place2 does not collapse place.

Chapter - Approving the map

[This routine checks whether the beginning of the map isn’t too narrow: we want to branch out quickly so the player has choices.]

To decide which number is the distance of (place - a room):
decide on the number of moves from place to Entrance Hall.

Distance-1 and distance-2 are numbers that vary.

To approve the map:
now distance-1 is 0;
now distance-2 is 0;
repeat with place running through placed rooms:
if distance of place is 1, increase distance-1 by 1;
if distance of place is 2, increase distance-2 by 1;
if distance-1 plus distance-2 is greater than 4:
now map approved is true;
otherwise:
if generation info is true, say “Map rejected. Regenerating.”.

Section - Additional placement rules

The additional placement rules are a rulebook.

Chapter - Choosing the right room

Section - Building a list of suitable rooms

Table of Suitable Rooms
Candidate Room Score
a room a number
with 200 blank rows

To fill the Table of Suitable Rooms:
blank out the whole of the Table of Suitable Rooms;
repeat with place running through placeable not placed rooms:
[ say place, " ";]
now considered room is place;
consider the placement possible rules;
if rule succeeded:
[say "! ";]
choose a blank row in the Table of Suitable Rooms;
now the Candidate entry is place;
now the Room Score entry is 0.

The placement possible rules are a rulebook.

Placement possible rule (this is the do not use rooms that are too difficult rule):
if the difficulty level of considered room is greater than difficulty:
rule fails.

Last placement possible rule:
rule succeeds.

Section - Scoring the suitable rooms

To score the suitable rooms:
repeat through the Table of Suitable Rooms:
now considered room is the Candidate entry;
now current room score is 0;
consider the placement scoring rules;
now the Room Score entry is current room score.

The placement scoring rules are a rulebook.

Section - Choosing a suitable room

To decide which room is a suitable room from (place - a room) at (x - a number) by (y - a number) by (z - a number):
now original room is place;
now considered-x is x;
now considered-y is y;
now considered-z is z;
fill the Table of Suitable Rooms;
score the suitable rooms;
sort Table of Suitable rooms in random order;
sort Table of Suitable rooms in reverse Room Score order;
[ repeat through Table of Suitable Rooms:
say “[Candidate entry] at [Room score entry] points.”;]
let max be the number of filled rows in Table of Suitable Rooms;
if max is 0:
decide on Entrance Hall;
otherwise:
let pos1 be a random number between 1 and max;
let pos2 be a random number between 1 and max;
let pos3 be a random number between 1 and max;
if pos2 is less than pos1, now pos1 is pos2;
if pos3 is less than pos1, now pos1 is pos3;
[ say “[pos1] of [max]”;]
choose row pos1 in the Table of Suitable rooms;
decide on Candidate entry.

Part - Additional Routines

Chapter - Placing rooms

Section - Placing a room at a specific location

To place (new place - a room) from (original place - a room) at (x - a number) by (y - a number) by (z - a number):
unless new place is Entrance Hall: [which would mean failure]
now x-coordinate of new place is x;
now y-coordinate of new place is y;
now z-coordinate of new place is z;
let way be the direction from original place to new place;
change the way exit of original place to new place;
let reverse be the opposite of way;
change the reverse exit of new place to original place;
now new place is placed;
if generation info is true, say “* [Way] of [original place] ([x-coordinate of original place], [y-coordinate of original place], [z-coordinate of original place]) is [new place] ([x], [y], [z]).[line break][run paragraph on]”;
now additional considered room is new place;
now additional original room is original place;
consider the additional placement rules.

Section - Placing a room next to another room

To place (a - a room) next to (b - a room):
unless the number of rooms surrounding considered-x by considered-y by considered-z is the number of cardinal directions:
while a is not placed:
let way be a random cardinal direction;
let x be the x way of b;
let y be the y way of b;
let z be the z way of b;
if the space at x by y by z is free:
now x-coordinate of a is x;
now y-coordinate of a is y;
now z-coordinate of a is z;
change the way exit of b to a;
let reverse be the opposite of way;
change the reverse exit of a to b;
now a is placed;
if generation info is true, say “* Placed [a] [way] of [b].[line break][run paragraph on]”.

Chapter - Other routines

Section - Absolute distance

[This routine stolen from Fixed Point Maths version 5 by Michael Callaghan.]
To decide which number is the absolute value of (N - a number):
if N is less than 0:
let N be 0 minus N;
decide on N.

To decide which number is the absolute distance between (a - a room) and (b - a room):
let count be 0;
let temp be x-coordinate of a minus x-coordinate of b;
increase count by the absolute value of temp;
let temp be y-coordinate of a minus y-coordinate of b;
increase count by the absolute value of temp;
let temp be z-coordinate of a minus z-coordinate of b;
increase count by the absolute value of temp;
decide on count.

Section - Direction between rooms

To decide which direction is the direction from (a - a room) to (b - a room):
if absolute distance between a and b is not 1:
decide on northwest; [Capture this as a failure!]
otherwise:
if x-coordinate of a minus x-coordinate of b is -1:
decide on north;
if x-coordinate of a minus x-coordinate of b is 1:
decide on south;
if y-coordinate of a minus y-coordinate of b is -1:
decide on east;
if y-coordinate of a minus y-coordinate of b is 1:
decide on west;
if z-coordinate of a minus z-coordinate of b is -1:
decide on up;
if z-coordinate of a minus z-coordinate of b is 1:
decide on down.

Section - Number of surrounding rooms

To decide which number is the number of rooms surrounding (a - a number) by (b - a number) by (c - a number):
let count-1 be 0;
repeat with place running through placed rooms:
let count-2 be 0;
let temp be x-coordinate of place minus a;
increase count-2 by the absolute value of temp;
let temp be y-coordinate of place minus b;
increase count-2 by the absolute value of temp;
let temp be z-coordinate of place minus c;
increase count-2 by the absolute value of temp;
if count-2 is 1:
increase count-1 by 1;
decide on count-1.

Section - Whether a location is free

To decide whether the space at (a - a number) by (b - a number) by (c - a number) is free:
repeat with checked place running through placed rooms:
if x-coordinate of checked place is a:
if y-coordinate of checked place is b:
if z-coordinate of checked place is c:
decide no;
decide yes.

Section - What is the room at a location

To decide what room is the room at (a - a number) by (b - a number) by (c - a number):
repeat with checked place running through placed rooms:
if x-coordinate of checked place is a:
if y-coordinate of checked place is b:
if z-coordinate of checked place is c:
decide on checked place.

Section - The x, y and z way

To decide what number is the x (way - a direction) of (place - a room):
if way is north:
decide on x-coordinate of place + 1;
if way is south:
decide on x-coordinate of place - 1;
otherwise:
decide on x-coordinate of place.

To decide what number is the y (way - a direction) of (place - a room):
if way is east:
decide on y-coordinate of place + 1;
if way is west:
decide on y-coordinate of place - 1;
otherwise:
decide on y-coordinate of place.

To decide what number is the z (way - a direction) of (place - a room):
if way is up:
decide on z-coordinate of place + 1;
if way is down:
decide on z-coordinate of place - 1;
otherwise:
decide on z-coordinate of place.[/code][/spoiler]

Thanks, Matt. I thought to ward against choosing one where the other room occupies the direction, but not the room it’s actually trying to find a connection for.

Yes, I’ve been trying to figure out how to make coordinates so the map makes sense, but every attempt at creating an original code has failed. This will definitely be helpful, thank you!

If you wanted the player to go to a maze-like area but wanted it random, you could define say, half a dozen mazes, assign specific rooms in each one to be in a “random region”, then have the player go to a random room in the random region. Then, not only would the player be sent to a random room, because the maze would be different they would be unable to exactly reproduce the game play.

Not sure if that would just piss players off, but it would add to replay value.