A Procedurally Generated Wilderness (In Text)

Since Ludum Dare, I’ve been fiddling with procedural generation schemes, mostly for things that look like this:

random map

Last night, I posted that image to Twitter, and received this response:

…which was a really good question, actually.

For context, @zkline is blind. Games built in Inform 7 or ChoiceScript are accessible to blind players, but games built in Unity and Twine (and most other engines) are not.

But – I actually have a procedurally generated text wilderness. It’s part of an incomplete, backburnered game called Five Gods Exiled (mentioned last week in “Recognizing Fun Through Elevator Pitches”).

The code doesn’t compile any more, since I built it in late 2010/early 2011, and we’ve had multiple I7 updates since then. But… I thought it might be fun to show you the procedurally generated maps, so I broke it out today and tried to bring it up to date.

Instead, I uncovered a few interesting new ways to trigger internal errors in Inform 7. (Among others, do not comment out 130,000 words of code.) It will take me a bit more time to figure out what unexpected sins I have committed in this brave new world.

Consequently, I don’t have a running copy of Five Gods Exiled. But I’ll walk you through the logic anyway.

0. Comment your code.

Okay, that’s not logic. But it is important. Sigh.

1. Basic geography.

A. Build and connect the map rooms.

Place 20 rooms on a 64 x 64 grid. I used a random walk process, which looks like this:

  • Pick a random direction from the current room.
  • If there is a room in that direction:
    • Build a two-way directional link between that room, and this room;
    • Move into that room.
    • Repeat from the top.
  • Otherwise:
    • Put a new room in that direction.
    • Move into that room.
    • Repeat from the top.

…until all 20 rooms are placed.

Note that these are totally blank rooms: they are capable of having names and descriptions, but they don’t have them yet.

B. Place buildings on the map.

Start with 10 blank doors – wait, no. Inform doesn’t like having doors moved once they’re in play.

The solution: make “entrances” that are not doors, but regular objects. And write code for the entrances to make them behave like doors – you can open them, close them, they have associated rooms, and you can use all the regular commands to travel through an entrance into their associated rooms.

The blank entrances get scattered randomly around the map, no more than one in a room. Then each one gets attached to an associated interior room.

2. Major environmental stuff.

A. Place terrain.

The “terrain” is whatever happens to be underfoot. Terrains are again blank objects. They are capable of having terrain types, which are things like grass, moss, fallen leaves, sand, mud, pebbles, bare rock, and so forth.

Assign a terrain to each exterior room, and give each terrain a terrain type as it’s assigned. Start with a random terrain type, remember the last terrain type assigned, and occasionally switch to a new one, using randomization (Markov chain style) to decide when to switch.

B. Determine temperature and humidity.

These values are pretty basic – humidity includes “arid, humid, or precipitating” and temperature includes “hot, temperate, or cold”.

Based on what terrain got placed, we can derive some things about the temperature and humidity of the area. For example, if there’s slush on the ground, it’s not hot and it’s not arid.

Exclude the impossible temperatures and humidities, and then choose randomly from the remaining available temperatures and humidities.

C. Determine weather.

Again, basic values: the weather can be sunny, cloudy, misty, raining, or snowing. Some of these can be directly derived – if it’s cold and humid, for example, then it’s misty. Otherwise, choose randomly.

D. Add adjectives to the placed terrain.

Each terrain type has a list of possible adjectives. Update the adjective list for terrain types based on the current weather, humidity, and temperature.

Using sand as an example:

  • Always available: fine, coarse, pale, dark-hued, gritty
  • If the humidity is arid: + dry
  • If the humidity is arid and the temperature is hot: + baked
  • If the weather is raining: + rain-soaked, damp
  • If the weather is snowing: + caked, frozen

Repeat through the placed terrain and add an adjective to each piece of terrain, choosing randomly from the list of adjectives currently available to that terrain type.

3. Place barriers and scenery.

Important to note: the room scale and connection scale were intended to be large, from a player perspective. You’re not just walking a few meters off and finding a new room – you’re slogging through the wilderness until you come to a new clearing. (Whenever you move between rooms, there’s messaging to that effect.)

So, what’s a “barrier”? A barrier is the in-game explanation for why the player can’t go in a specific direction, instead of just “you can’t go that way”. It’s an object in the room that blocks a number of directions. And rooms had to be large from a player’s perspective because I didn’t want to link the barriers directionally. (They were causing me enough fun already. In the Dwarf Fortress sense.)

There’s a table of barrier types, including things like forests, brambles, hedges, chasms, cliff faces, and rivers. Each barrier has a minimum size and a maximum size, which indicates how far it can wrap around the room’s compass. Forests can block anywhere from 3 to 7 contiguous directions, for example, while a free-standing pillar blocks only 1 direction.

Each barrier has a specific explanation for why the player can’t go that way. For example, the wall barrier returns “The wall to the [current direction] does not have enough handholds or footholds for you to climb it.”

A. Place barriers.

  • Start in the first external room (called the current room).
  • Turn clockwise until you reach a direction with no connected room.
  • Continue turning clockwise, tracking the number of blocked directions. Stop when you reach a new connected direction.
  • Randomly choose a barrier type that can match that number of blocked directions.
  • Place the barrier.
  • Continue turning clockwise and placing barriers until all blocked directions have associated barriers.
  • Move to the next room and do it again.

B. Describe barriers.

This works basically the same as describing the terrain. So forests can be tangled, thorny, impassable, impenetrable, labyrinthine, gloomy, and so on, while chasms can be shadowy, deep, fathomless, wide, and so on.

C. Place significant props.

The significant props are specific objects (not blank, for once!) such as a barren field, a rune-carved boulder, an empty stream bed, or a headless statue.

Pick a subset of these and move them into randomly-chosen exterior rooms, no more than one to a room.

D. Place and describe decorative props.

The decorative props are things like trees, weed clumps, shrubs, and fallen logs – nothing super-interesting, but enough to add some character. And they have their own adjective lists, of course.

Scatter a bunch of decorative props through the exterior rooms that don’t already have entrances or significant props. Add an appropriate adjective to each decorative prop.

E. Interior rooms… you get the idea

I’ve excised much of the interior room process. If you assume that the process of building interior rooms is much like building the exterior rooms, but without barriers or map connections, then you’ll be close enough.

4. Names and descriptions.

In our exterior rooms, we have directions, entrances, barriers, landmarks, and other miscellany. This is enough information to figure out what each of these rooms actually looks like.

A. Name exterior rooms.

Repeating through the exterior rooms:

  • Determine the most prominent feature of the room, as follows:
    • Is there a building entrance here? Pick that.
    • Is there a significant prop here? Pick that.
    • Are there barriers in the room? Pick the largest barrier.
    • Is there a decorative prop in the room? Pick that.
    • If nothing’s been chosen yet, pick the terrain.
  • Pick a random preposition associated with the landmark.
    • The preposition list behaves like an adjective list, modifying for number of directions and landmark type.
    • General examples: “near”, “not far from”, “beside”.
    • Specific examples: “outside” for an entrance, “surrounded by” for terrain.
  • How many directions can you go from this room?
    • 1 direction: name the room “Dead end [preposition] [landmark]”.
    • 2 directions: name the room “Path [preposition] [landmark]”.
    • 3 directions or more: name the room “[Preposition] [landmark]”.

B. Write exterior room descriptions.

This is where the magic comes in. It’s also where the (authorial) drudgery comes in.

Direct from the code (with minor adjustments for clarity)…

To write exterior room descriptions:
    repeat with temporary locale running through exterior rooms:
        now the current room is the temporary locale;
        determine the current landmark;
        if (the number of barriers in the temporary locale) is 0:
            write an all-directions room description;
        repeat with temporary barrier running through barriers in temporary locale:
            if (the number of directions prevented by the temporary barrier) >= 4:
                write a barrier-dominant room description;
        if (the number of barriers in the temporary locale) is 1:
            write a barrier-dominant room description;
        otherwise if the number of building entrances in the temporary locale is 1:
            write a building-based room description;
        otherwise if the number of significant props in the temporary locale is 1:
            write a prop-based room description;
        otherwise:
            write an undecorated room description;
        write an exterior room odor;
        write an exterior room sound.

Each “write a [type] description” activity connects to a set of templates. Each template is composed of a set of sentences, such as “[the prop-barrier sentence] [the weather sentence] [the terrain sentence]”.

There are 14 different kinds of sentences: weather, terrain, shrine, prop, barrier, weather-terrain, weather-shrine, weather-prop – and so forth. The different template sets prioritize different pieces of information, so (for example) the barrier-dominant room descriptions always start with a barrier sentence. The specific template used is chosen based on what features are present in the room. (Note that “sentence” may actually be more than one sentence, in some cases.)

How does this turn out? Some actual examples:

[Beside a fallen stone bench]
Chill winter sunlight illuminates everything. Blades of pale brown grass grow up around a fallen stone bench. A carved pillar to the northeast bears lonely testament to some greater structure standing here in the far-distant past. A thorny hedge hems your path in, blocking you from going southeast or south.

[Path next to a grey lake]
A vista of rain-soaked moss stretches north and southeast past a grey lake and a crumbling wall. A fallen stone bench looks bedraggled in the rain.

[Dead end close to a vast ocean]
Long waves roll over the surface of the vast ocean and exhaust themselves as curling foam. Here beside the shore, the ocean’s edge blocks travel in all directions except south. Mist twines slowly about the shape of a broken pillar and stretches long tentacles in all directions. A thick layer of oozing slush conceals the ground from view.

It’s not XYZZY-worthy prose, but it works.

So what happens now?

Immodestly, I love this procedural wilderness system. I love the other systems that companion it (the exiled world, which mirrors the map of the wilderness within a city; the orbs that allow you to travel back and forth between the two; the monsters spawned when the two worlds touch; the shadow self that follows you silently from room to room). I was excited about so many parts of this game, and so very disappointed when a good player experience never came together.

Secretly, I still want to make this game – but I want to make it right. I hope that I’ll return here someday, and my perspective will shift like a kaleidescope, and I’ll know exactly how to make it fun, and then it will be one of the best things I’ve ever made.

In the meantime, it’s languishing on my hard drive, and Inform 7 releases come out and the code grows more deprecated and time goes by.

If anyone read this writeup and thought, “Oh, I want to try writing something like that!” – then I’m glad of it, and I wish you the best of luck. (Also, let me know if you need a playtester!)


Thank you to everyone supporting Sibyl Moon through Patreon!
If this post was useful or interesting, please consider becoming a patron.

Bookmark the permalink.

9 Comments

  1. Regarding having doors moved once in play, is this at all related to the elevator problem?

    • I’m sorry, but I need a little more context about the elevator problem.

      • Er, not so much like The Elevator Problem, but just implementing elevators; I have a vague memory of reading something in the manual about how I7 requires certain kinds of not immediately intuitive ways to implement a fully functional, not-purely-decorative elevator. I was reminded of it because intuitively, an elevator door is “moved” each time you go up or down, right?

  2. So, is the code something that would still work in 6G60? Because I still have 6G60 sitting on my computer, and I could maybe run your old code is that helpful.

    …is that a thinly veiled attempt to get you to send me the source code, because I want to do something like this? Yes. Yes it is. (And if someone wants to tell me that I should really get cracking on Tutorial Mode instead, that’s fair. My original efforts broke on the initial shoals of not knowing how to Github.)

    • I have been considering distributing the source, but my first distribution target would be Patreon subscribers. I want to send them good thank-you presents without reducing the usefulness of Sibyl Moon at large, and this seems like a good opportunity. Also, I want to convert it into more of a template first, without all the other game chunks cluttering it up.

      Prompted by your comment, I just checked the website and realized I can still download 6G60. I should have thought of that before! I know it isn’t quite how you intended to help, but that was actually really helpful.

  3. Neat! I’m especially impressed by the dynamic description agglomeration; I’ve looked into doing that sort of thing and it requires a lot of gruntwork to get enough variety in structure that language doesn’t quickly start feeling weirdly repetitive.

    With the tech/algorithm you used (I know virtually no Inform 7), was it reasonable to pull items off of descriptor lists as they were chosen, so you wouldn’t get, eg, a second Grey Lake?

    • Yes. Lists are a data structure in Inform 7, and it’s easy to add things to a list and remove them from a list.

      Rechecking the code – I both did and didn’t do that. Whoops.

      To be more specific, I originally implemented the barrier adjective assignment system in a way that would avoid duplicates. Then I commented out all that code for a refactoring, and failed to reimplement the no-duplicates functionality in the new version. I’ll have to remember that if I do wind up templating this system.

Leave a Reply

Your email address will not be published. Required fields are marked *