Procedural Generation of Dinosaurs in Inform 7

On the latest episode of Clash of the Type-Ins, we played my game The Island of Doctor Wooby. The game deals with an island populated by teensy felt dinosaurs, which are a passion of mine.

Since this was an entry in PetJam, the virtual pet game jam, I was trying to make a “text adventure” that didn’t rely on playing your way through a narrative, but where the player’s “goal” was to explore a location and enjoy its inhabitants—and hopefully feel like coming back to explore and enjoy again later. I was thinking a lot about Animal Crossing, which I’ve often loaded up just for purposes of looking at my virtual house and saying hello to my virtual neighbors.

A true virtual pet, which would be waiting for you when you came back (and probably feeling pretty hungry if you’ve been gone long enough), was outside my reach as an Inform 7 programmer. Instead, when you come back to the Island of Doctor Wooby, there are always new dinosaurs to hang out with, because their appearance and behavior are procedurally generated.

The procedural aspects of The Island of Doctor Wooby are fairly robust, and I felt very proud of the game at the time. When we played it on the podcast, though, I found several reasons to be disappointed in my work. There’s a lot of room for improvement.

So let’s look at the relevant parts of the source code, and see what we can learn! If you haven’t played the game, or listened to us playing the game on the podcast, you might want to do that before you read on, just so you know where this is all headed.

Intro to Dinosaur Generation

Volume 3 - Dinosaur Construction

A stuffy is a kind of thing.

Understand "dino" and "dinosaur" and "doll" and "stuffy" as a stuffy.

A stuffy can be blank. A stuffy is usually blank.

Barracks is a room.

Printed name of Barracks is "Non-Space".

26 stuffies are in barracks.

A stuffy has a text called the species name. Understand the species name property as describing a stuffy.

Book 1 - Index of Features

A feature is a kind of thing.

A feature has text called flavor.

A thing can be marked. [only features will be marked. I think.]

This is just definitions of terms to use later. “Stuffy” is the internal name for a dinosaur doll; “features” are the parts of the doll: horns, legs, etc. The “flavor” of a feature will end up in its description—when you examine the Boofasmooshigus, the description will include “with two nubby horns” or something.

“Barracks” is a dummy room. See, the game starts with a certain number of dinosaurs already on the island, but when you find Doctor Wooby’s laboratory/sewing room, you can collect additional dinosaurs as he sews them together. However! By default, Inf0rm 7 can’t really generate objects on the fly. (Using a certain extension, Dynamic Objects, lets you do this, but I didn’t do that.) Instead, at the beginning of the game, a bunch of dinosaurs exist in “blank” form, in the inaccessible Barracks. We can imagine these blank stuffies as eyeless white sauropods—Ooh, or as little eggs!

Imposing Dinosaur Population Limits

When Doctor Wooby finishes sewing a dinosaur, the little egg undergoes the feature-assigning process we’ll see in a bit. Here’s the code that leads up to that process:

Section 4 - Man, Sewing Dinosaurs

Prog is a number that varies. Prog is 0.

Quota is a number that varies.

When play begins:
 randomize quota.

To randomize quota:
 let foo be a random number between 1 and 7;
 now quota is 6 plus foo.

Every turn while the player is in Workshop:
  if prog is less than 50:
    increment Prog;
  if prog is quota:
    randomize quota;
    now prog is 0;
    create a dinosaur;
    now player carries the current test subject;
    say "Wooby snips a thread triumphantly. 'Finished!' He raises the new dinosaur from his desk and hands it to you. 'I call it ['][current test subject].['] Take it out, see how it gets along with the others!'[paragraph break]Then he turns back to his desk and starts working on another dinosaur.";
    let creature be a random blank stuffy in barracks;
    if creature is not a thing:
    say "[paragraph break](Actually, this game just ran out of dinosaurs, so he's gonna be working on that one for a while. To see more dinosaurs, restart the game! Thank you so much for playing!)";
    now prog is 999;
    if a random chance of 3 in 7 succeeds:
    say "[one of]Wooby grimaces at his unfinished dinosaur[or]Wooby clicks his tongue[or]Wooby shakes a cramp out of his hand, then returns to sewing[or]Wooby leans in to examine his handiwork[or]Wooby leans back and closes his eyes. He seems like he's about to yawn[unicode 8212]then he gets back to work[or]Doctor Wooby shifts in his seat[at random]."

A lot of the work being done here just randomizes the “quota” of turns it takes for Wooby to build a new doll. The important part for our purposes starts at “create a dinosaur.”

We’ll see later on exactly how much the line “create a dinosaur” entails, but it picks a blank stuffy out of Barracks, bestows on it the title of “current test subject,” and randomly assigns its appearance and personality. “Now player carries the current test subject” takes that cute little egg out of Barracks and drops it into the player’s inventory, and the following “say…” line describes what’s happening in the fiction.

“Let creature be a random blank stuffy in barracks” bestows the title of “creature” on a random blank dinosaur. Now that we’ve created a dinosaur, we don’t need another little egg for anything in particular. We’re just checking to see if such a thing exists. Inform 7 doesn’t really have a way to directly ask “Do we have any of those objects left?” so we tell it to select a random such object, and then we check to see if that selection actually grabbed anything. “If creature is a thing…” would return as True if there was a blank dinosaur left after the last dino-generation process, and similarly “If creature is not a thing” returns as True if there are no blanks left—the identity of “creature” would be “nothing,” and nothing is not a thing.

That’s a neat trick, but the larger lesson is that, within the paradigms of Inform 7, often the more expedient way to accomplish something (and sometimes the only way) is to start out with everything you could possibly need and then take stuff away, rather than start out with an elegant mimimum and then try to add things. This principal is especially true for purposes of procedural generation, and we’ll see it again later on. (Again, if you use the Dynamic Objects extension, then you can do things differently.)

Anyway, here’s how we build dinosaurs:

The Big Rule

The current test subject is a stuffy that varies.

to create a dinosaur:
  let creature be a random blank stuffy in Barracks;
  if creature is a thing:
    now creature is not blank;
    now current test subject is creature;
    name the current test subject;
    enfeature the current test subject;
    follow the random size rule;
    name the current test subject's features;
    enpersonality the current test subject;
    determine dinosaur stuffing;

Again, we use “if creature is a thing” to make sure we have a cute little felt egg to work our magic on. “Now creature is not blank”—we’re about to put on its features, so we don’t want it to be qualify as un-featured anymore. We bestow the title “current test subject” on it, and then we give it all its qualities via a series of additional rules:

Naming Dinosaurs

This might be the best part. Let’s take this rule a few steps at a time

To name the current test subject:
  let tempname be text;
  let namecomponents be a random number between 1 and 2;
  if a random chance of 1 in 6 succeeds:
    increment namecomponents;
  if namecomponents is 3:
    if a random chance of 2 in 3 succeeds:
      now namecomponents is 2;

Well, “let tempname be text” is necessary but not exciting—that just creates a temporary string of text, which will be zero characters long until we add something to it.

The next part of the rule deals with how long this dinosaur’s name should be. “Let namecomponents be a random number between 1 and 2” means that we have a 50/50 chance of 1 or 2 elements (not counting the name’s suffix). But if we increment that number 1 out of 6 times, then we get… a 41.667% chance of 1 element, a 50% chance of 2 elements, and an 8.333% chance of 3 elements.

The dinosaur names in this game are a lot of fun, until you have to type them out. That’s why the game lets you re-name dinosaurs with more convenient appellations like “Errol.” (We won’t see the renaming code here, because it has nothing to do with procedural generation!) However, the when a name has three elements and then a suffix, something like “Libbyzoolizzamus,” it gets to be not only inconvenient but not especially pleasing to the eye. And 8.333% of names being this long is actually kind of a lot, in practice. So, with the next couple of lines, we make it so 2/3 of names that could have had three elements have two instead, so the proportions are… 41.6% one element; 55.6% two elements; 2.8% three elements.

That “If a random chance of x in y succeeds” is your only way to do weighted probability in Inform 7! You can’t simply say “Option A, 49%; Option B, 49%; Option C, 2%.” You can get these exact percentages if you want, but you’d have to say: “If a random chance of 49 in 100 succeeds, go with Option A; otherwise, if a random chance of 49 in 51 succeeds, go with Option B; otherwise, go with Option C.”

There isn’t really a problem with doing it that way. I guess pulling probabilities out of simpler fractions is just more entertaining to me.

Moving on!

 if namecomponents is 1:
   choose a random row in Table of Long Name Suffixes;
   now tempname is "[suffix entry]";
   if a random chance of 1 in 2 succeeds:
     choose a random row in Table of Long Name Suffixes;
     now tempname is "[suffix entry]";
     choose a random row in Table of Short Name Suffixes;
     now tempname is "[suffix entry]";

We’ll see that the Table of Long Name Suffixes consists of short suffixes like “-don” and “-mus;” the Table of Short Name Suffixes has the longer, cooler suffixes like “-zaptor.” Names with 2 or 3 components are eligible for the big suffixes—even though this has the potential to produce monstrosities like “Cappyslibbawoofycephalus.” I apparently was okay with forcing you to type that once in a while.

But if the name has only one element, and it ends up with a short suffix, for example “Moolus,” we end up with something that doesn’t really look like a dinosaur name. Unacceptable. It has to be at least “Moozaptor.”

 choose a random row in table of name roots;
 now tempname is "[root entry][tempname]";
 decrement namecomponents;
 if namecomponents is greater than 0:
   choose a random row in table of name roots;
   now tempname is "[root entry][tempname]";
   decrement namecomponents;
 if namecomponents is greater than 0:
   choose a random row in table of name roots;
   now tempname is "[root entry][tempname]";
   decrement namecomponents;
 Now the species name of the current test subject is "[tempname]" in title case;
 now the printed name of the current test subject is "[tempname]" in title case.

“Now tempname is ‘[root entry][tempname]'” is the kind of thing you might expect to engender an infinite loop, but it actually works exactly the way you want it to, letting you append text to a text while avoiding bizarre circumlocutions and additional stored strings.

Speaking of loops, I could have put one in here, adding an element to the name, decrementing the number of required elements, and starting over, but with a maximum of three elements I guess it was more convenient to do it this way.

Finally, we assign that tempname string to some texts specifically attached to the current test subject: Its Species Name, which will stick with it the rest of its life, and its Printed Name, which the player can choose to change with a command like >NAME ZOOGOOCERATOPS ROGER.

And now, for your edification, here are the tables of name elements. I actually kinda sorta put my linguistics degree to good use here: Although they may seem like random nonsense, the entries in the Table of Name Roots (almost) all begin with consonants and end with vowels—and I was careful about which consonants I included—ensuring that the resulting names will (almost) always be pronouncable and recognizable as Baby Talk English. A more robust saurian phonotactics would certainly be possible, and you could refine the mechanics and the syllable inventories to generate words from very distinct-looking languages. If you need to create this kind of effect in your own game, you should definitely hire me to do it for you.

Saurian Morphology (not in the linguistic sense)

The dinosaur is then given physical features:

A stuffy has a number called interestingness.

To enfeature the current test subject:
 follow the dinosaur random color rule;
 follow the random expression rule;
 let var1 be a random number between one and two;
 let var2 be a random number between one and two;
 let var3 be a random number between one and two;
 let var4 be a random number between one and three;
 now the interestingness of the current test subject is (var1 + var2 + var3 - var4);
 if the interestingness of the current test subject is 0:
   now the interestingness of the current test subject is 1;
 repeat through Table of Possible Interesting Features:
 now usedness entry is 0;
 give the current test subject a feature;
 disenfeature the current test subject;

The dinosaur random color rule basically puts a random color in “This dinosaur is sewn out of [color] felt.” The dinosaur random expression rule similarly assigns an expression to the dinosaur’s face: Walleyed, distant, uneven, confused, eager, nervous, suspicious.

(While we’re a ways off from talking about dinosaur personality, I can say here that the dinosaurs’ expressions don’t relate in any way to their personalities, but they should.)

The nonsense about var1, var2, etc. generates an random “interestingness” between 0 and 5, but in a bell curve such that the most likely outcomes are 2 and 3. (Notice that you subtract var4! Confusing!) A dinosaur with zero features isn’t extremely desirable, so that outcome gets bumped up to 1.

The table of possible interesting features looks like this:

Table of Possible Interesting Features
a rule             usedness
random body rule   0
random head rule   0
random tail rule   0
random neck rule   0
random teeth rule  0
random tongue rule 0
random back rule   0
random legs rule   0
random arms rule   0
random horns rule  0

And we do need to reset all those zeroes each time we generate a dinosaur. Then we give the current test subject a feature:

To give the current test subject a feature:
  sort Table of Possible Interesting Features in random order;
  sort Table of Possible Interesting Features in usedness order;
  choose row 1 in Table of Possible Interesting Features;
  follow rule entry;
  now usedness entry is 1;
  decrement the interestingness of the current test subject;
  if the interestingness of the current test subject is greater than 0:
    give the current test subject a feature.

Sorting the rows of the table in random order and then in order of usedness (i.e., all the zeroed rows on top) means that row 1 is a random un-used row. This is the song and dance we have to do to pick a new random row on a table. We follow the associated rule (we’ll see that in a bit) and then changed usedness to 1 so we don’t follow it again on this dinosaur.

The last few lines are how we loop through this rule until we’ve assigned as many interesting features as this dinosaur was alotted in the “enfeature the current test subject” rule.

To look at one of these rules, like the “random body rule,” we should look at the general facts about bodies first.

Section - Bodies

A body is a kind of feature. A body is a part of every stuffy.

Bodytype is a kind of value. The bodytypes are bodyboring, spotted, feathery, and patched.

Understand the bodytype property as describing a body.

Understand "spot" and "spots" as spotted.

Understand "feather" and "feathers" and "fringe" as feathery.

Understand "patch" as patched.

a body has bodytype. the bodytype of a body is usually bodyboring.

A bodytype has text called flavor.

The flavor of bodyboring is "utterly uninteresting".

The critical thing here is that every dinosaur has a feature called a body built in from the beginning. We’re not appending the body to the dinosaur—We could if we wanted, but that way of doing things wouldn’t scale as well, pun intended. The different styles of bodies are expressed as a value on each body: spotty, feathery, patched, or boring, all possible values of “bodytype.” The “Understand…” lines allow the player to refer to a dinosaur’s body by e.g. its feathers.

“The bodytype of a body is usually bodyboring.” That means that, by default, a dinosaur’s body is just a body, with nothing to draw your attention. It’s still there, though, and if you examine it, you’ll get a report that “The Grouchodon’s body is utterly uninteresting.”

If the “give the current test subject a feature” rule ends up deciding to give the current test subject an interesting body, we get this rule (which we’ll walk through in parts):

This is the random body rule:
 let the current body be a random body incorporated by the current test subject;
 now the bodytype of the current body is a random bodytype;
 now the flavor of the current body is the flavor of the bodytype of the current body;

We have to pick out “a random body incorporated by the current test subject” because Inform does not know that a dinosaur only has/can only have one body. (We could declare that this is the case, but it wouldn’t save us any time.) Then we assign a random bodytype to the body in question; then we give that body the “flavor” (text that goes in the dinosaur’s description) of that bodytype.

Note that “a random bodytype” does include the possibility of “bodyboring,” so even if this rule does intend to make the dinosaur’s body interesting, it might end up having no effect! This is true of all the possible features! Most dinosaurs are given an interestingness greater than 1, so they have multiple chances to end up with something interesting about them, but there’s still a very low probability of a dinosaur escaping this process with zero interesting features. In the final game, this happens rarely enough that the resulting generic doll is kind of amusing in its own disappointing way, so I didn’t bother going through the extra work of making it an impossibility.

But if the newly assigned bodytype is not bodyboring:

 if the bodytype of the current body is not bodyboring:
   now the current body is marked;
   choose random row in table of colors;
   let colour be text;
   let host be text;
   now host is "[species name of the current test subject]";
   now colour is "[shade entry]";

All the interesting bodytypes (feathers, spots, or patches) have colors of their own. So we pick a random color…

if the bodytype of the current body is spotted:
  now flavor of current body is "[colour] spots";
  now description of current body is "[The host] is covered in [colour] spots.";
  now printed name of current body is "[colour] spots";

…and we make sure it gets into the descriptions of the dinosaur and the dinosaur’s body. (The above business is repeated for the other body styles.)

This same process gets applied to the dinosaur’s tail, teeth, arms, horns, whatever gets randomly selected from the Table of Possible Interesting Features, until the allocated number of features is reached. Then we disenfeature the current test subject:

To disenfeature the current test subject:
  let teetho be a random teeth incorporated by the current test subject;
  if teethtype of teetho is teethnonexistent:
    remove teetho from play;
  let tongueo be a random tongue incorporated by the current test subject;
  if tonguetype of tongueo is tonguenonexistent:
    remove tongueo from play;
  let armo be a random arms incorporated by the current test subject;
  if armstype of armo is armnonexistent:
    remove armo from play;
  let horno be a random horns incorporated by the current test subject;
  if hornstype of horno is nohorns:
    remove horno from play.

Teeth, tongues, arms, and horns are all features that a dinosaur might not end up with in the final analysis. (Other features, like bodies and heads, are not subject to disenfeaturement.) “Teethnonexistent” is a “teethtype” that corresponds to “bodyboring”—either this dinosaur’s teeth didn’t get selected for interestingness, or they rolled the dice and came up with the boring result. (To be honest, sewing little teeth into those dinosaurs’ mouths is usually not worth the effort.)

But each blank dinosaur starts out with teeth and horns, like all the other features, and it’s at this point that we remove those features if we decided the dinosaur didn’t need them. This is what I was alluding to before: For these purposes, it’s easier/more efficient/more elegant to start out with the teeth and then remove them if necessary than it is to add teeth to dinosaurs on a case-by-case basis.

Describing Dinosaurs

We haven’t explored every part of the dinosaur creation rule yet, but at this juncture let’s fast-forward out of the generation phase and into gameplay, when you, the player, decide to >EXAMINE DIZZIGOOBOLOPS.

A stuffy has text called the impression.

The description of a stuffy is usually "[the impression of the noun].".

All this does is let me sidestep the generated description process in case I want to give a bespoke description to any Ünique Dinosaürs.

To say the impression of (beast - a stuffy):
  if the number of marked features incorporated by beast is 0:
  say "[The Beast] is utterly uninteresting[if location is magical]. It looks back at you with a blank expression[end if][one of].[paragraph break]Sometimes you get a boring dinosaur; that's just how the algorithm works. Don't worry, the next one will be cooler[or][stopping]";
  rule succeeds;

Hey, there’s the description of a dinosaur who got through generation with no interesting features! Neat! The rule goes on from there, though:

 let L be a list of texts;
 repeat with factoid running through marked features incorporated by beast:
 if factoid is a body:
   add "[flavor of factoid]" to L;
 if factoid is a head:
   add "[flavor of factoid] head" to L;
 if factoid is a tail:
   add "a [tailtype of factoid] tail" to L;
 if factoid is a neck:
   add "[a flavor of factoid] neck" to L;
 if factoid is a teeth:
   add "a few [teethtype of factoid] teeth" to L;
 if factoid is a tongue:
   add "a [flavor of factoid] tongue" to L;
 if factoid is a back:
   add "[flavor of factoid]" to L;
 if factoid is a legs:
   add "[flavor of factoid]" to L;
 if factoid is a arms:
   add "[flavor of factoid]" to L;
 if factoid is horns:
   add "[flavor of factoid]" to L;

So each interesting feature ends up in a list. Then that list gets put into a description. This is a section of this very long article which you might find actually useful, because this is where I am using Inform’s substitutions to turn procedurally generated facts into a semi-natural-sounding series of sentences:

 sort L in random order;
 let expresso be a random expression incorporated by beast;
 let memo be the flavor of expresso;
 say "[The Beast] is a dinosaur sewn out of [color of beast] felt, about [size of beast in words] inches in size, with [L][if location is magical]. [one of]It looks back at you[or]It regards you[or]It looks around[or]It gazes into space[or]It glances about[or]It fixes you[or]Its head bobs[at random] with [flavor of random expression incorporated by beast] expression[end if]";

The list of features doesn’t really need to be randomized, but examining various dinosaurs in sequence will sound slightly less robotic this way. The “size of beast” will be explored in the next section.

“If location is magical” distinguishes the majority of settings in the game, where felt dinosaurs frolic like living things, from the couple of rooms where they are inanimate objects. In nonmagical rooms, the dinosaurs do not look back at you with any kind of expression, because they are mere dolls.

The variable fictionality of this game is kind of confusing.

Dinosaur Size

The features have all been assigned; next the dinosaur gets a size (in inches):

A stuffy has a number called size.

This is the random size rule:
  let K be a random number between 0 and 3;
  now the size of the current test subject is 4 + K.

That’s it!

Naming Features

“Name the current test subject’s features” just assigns some extra text to each dinosaur’s features to make them easier to refer to (for the game and for the player), and disambiguate-able when necessary. Not interesting for our purposes!

A thing has text called the extended name.

A thing has text called the boring feature name.

Understand the extended name property as describing a feature.

Understand the boring feature name property as describing a feature.

to name the current test subject's features:
 repeat with extremity running through features incorporated by the current test subject:
 now extended name of extremity is "[current test subject] [extremity]";
 now boring feature name of extremity is "[extremity]";

Rule for printing the name of a feature (called extremity) when the asking which do you mean activity is going on:
 say "[extended name of extremity]".

But don’t leave yet! Things are about to get interesting again!

Dinosaur Personalities

This is definitely one of the places where I feel like I dropped the ball. As I said on the podcast:

The dinosaurs have “personalities” but they aren’t distinct enough. I wanted the dinosaurs to be kind of “realistic.” That is, I thought that a dinosaur that did nothing but act clumsy (on the Island of Doctor Wooby, “clumsy” is a personality) would be too obviously artificial: Not relatable or naturalistic, and not distinguishable from the other clumsy dinosaurs.

I aimed to solve this by giving each dinosaur two personality traits. The idea was, first of all, it would take a little longer for you to notice the personalities: Not every “giggly” dinosaur would immediately announce itself as giggly. It would also make dinosaurs distinguishable, in that while this doll may be wimpy and shy, this other doll might be wimpy and lazy.

This doesn’t work. In the final game, the dinosaurs’ identities are basically invisible: Although the personality-specific messages do get triggered, you have to pay very close attention, much closer than I intended, to figure out which dinosaurs have which personalities.

I think I know why this is, but before we get into that, let’s look at the dang code.

First, some definitions:

A personality is a kind of thing.

Wimpy is a personality.

Aggro is a personality.

Giggly is a personality.

Lazy is a personality.

Affectionate is a personality.

Shy is a personality.

Curious is a personality.

Clumsy is a personality.

Describing relates various personalities to various stuffies. The verb to describe means the describing relation.

(The relation between personalities and stuffies does not go in the direction I would expect. I don’t recall why I laid it out this way, but I suspect it has something to do with making related rules easier to write.)

Here’s where each dinosaur gets two personalities:

To enpersonality the current test subject:
  let typeone be a random personality;
  now typeone describes the current test subject;
  let typetwo be a random personality that does not describe the current test subject;
  now typetwo describes the current test subject.

Dinosaur Behavior

Here’s how those personalities manifest in the game:

Section - Personality-based behavior

The current actor is a stuffy that varies.

Definition: a stuffy is available if it is not the current actor.

The other actor is a stuffy that varies.

A personality has a table name called the antic table.

The antic table of wimpy is Table of Wimpy Antics.

The antic table of aggro is Table of Aggro Antics.

The antic table of giggly is Table of Giggly Antics.

The antic table of lazy is table of Lazy antics.

The antic table of affectionate is table of affectionate antics.

The antic table of shy is table of shy antics.

The antic table of curious is table of curious antics.

The antic table of clumsy is table of clumsy antics.

The “current actor” is the dinosaur who gets to execute an antic this turn, and the potential “other actor” is the dinosaur who will be the victim of that antic, if a victim is necessary.

Here’s the big rule that decides what message gets displayed each turn! Let’s figure out what’s wrong with it!

Every turn while the location is magical:
  if a random chance of 2 in 3 succeeds:

Here’s the first problem: We only get a cute message on 2 out of 3 turns. In a typical text adventure, say, a game that takes place during a thunderstorm, you don’t want there to be a “Lightning strikes and it’s scary!” message every single turn. The focus should be on stuff the player is doing, and environmental messages should be surprises, or reminders, or something. You want to err on the side of caution.

In this game, though, the whole point is to watch dinosaurs do stuff. On any turn when you don’t get a message about a dinosaur doing something, it’s as if the dinosaurs have stopped their cavorting, and are pausing en masse to decide what to do next.

At the very least, you should get an update about at least one dinosaur on every turn that there’s a dinosaur present (provided you’re not in one of those nonmagical rooms). A room with no dinosaurs in it should feel very quiet in comparison.

    now current actor is a random stuffy in the location;
    if current actor is not a thing:
      rule succeeds;
    now the other actor is a random available stuffy in the location;

(“Rule succeeds” means “Now you’re done with this rule.” Whenever you see it, it means that everything that comes after won’t be applied this turn.) This part is just deciding who the participants in this antic are, but there’s another problem here: To Inform, “in the location” means free-standing, not supported or contained by anything else. Dinosaurs that you’re carrying in your arms are inelegible to commit dinosaur antics, and so if you’re collecting all the dinosaurs you find and dragging them around with you, you don’t get to enjoy them struggling to escape or nuzzling against your chest or anything. Unacceptable.

    if satiation of current actor is 0:
           follow the starving dinosaur rule;
         rule succeeds;

We haven’t talked about dinosaur satiation here. It’s not really germane to the topic of procedural generation: Each dinosaur has a satiation timer that ticks down until the dinosaur is starving. If it’s starving, and it gets selected for a message, it whines about how hungry it is instead of doing something truly amusing. In practice, you get what feels like a lot of whining from hungry dinosaurs, because all their satiation timers hit zero at roughly the same time, and they never figure out how to feed themselves.

This should be less common (dinosaurs should get hungry more slowly, with a wider variance in their starting satiation) but there should also be an opportunity for a less hungry dinosaur to do something more exciting on the same turn. That is to say: Make this a separate “Every turn…” rule, like the rule that governs dinosaurs whining about being stuffed with too much or not enough stuffing (not shown here).

Moving on:

    if the current actor incorporates a hat:
      if a random chance of 4 in 5 succeeds:
        follow the hat antic rule;
        rule succeeds;

We also haven’t talked about hats! There are hats that you can put on the dinosaurs, and the hats make the dinosaurs do different things. (I made it an 80% chance because by the time you find the hats, you should ideally have seen plenty of the standard cute messages, and the hat behaviors are supposed to add fun variety.) They basically constitute additional even goofier personalities, using the same type of code as antics based on normal personalities, which we are just about to get to.

    let trait be a random personality that describes current actor;
    if trait is shy and other actor is not a thing:
      choose random row in table of happy shy antics;
      say "[antic entry][paragraph break]";
      rule succeeds;

First we have a fun bit of business where Shy dinosaurs get to pull antics from a special table if there are no other dinosaurs around. If you were perceptive enough to detect that this Dagaceratops prefers to be alone, you could take it to a quiet location and get messages like “The Dagaceratops sighs peacefully.”

    sort antic table of trait in random order;
    if other actor is not a thing:
      sort antic table of trait in otherguy order;

Now we’re referring to the regular antic tables. Sorting them like this allows us to get a random antic at the top of the table, and, if there’s no other dinosaurs around, makes sure we get a random antic with only one participant.


table of clumsy antics
antic                                                                         otherguy  hilarious
"[The current actor] falls down!"                                             0         1
"[The current actor] trips over [a random trippable thing in the location]."  0         1
"[The other actor] shrieks as [the current actor] steps on its tail."         1         1

Make sure you scroll to the right to see the columns for “otherguy,” which is 1 if we require another dinosaur and 0 otherwise; and “hilarious,” which is 1 if the action described is hilarious and 0 if it is merely adorable. Apparently all clumsiness is hilarious. We will get to that later.

(Referring to “a random trippable thing in the location” is kind of dangerous. I wanted dinosaurs to be able to trip over stuff, but I didn’t want them to trip over comparatively huge scenery that wouldn’t make sense. So I created the “trippable” quality, which describes only certain scenery items. However. I had to make sure that every room had at least one trippable thing, because otherwise this description wouldn’t grab anything, and the text would read “The Wumbadax trips over nothing!” which would be pretty cute actually. Dangit.)

There’s a table for each personality trait. They all have between three and five entries. Ideally, there would be a longer list of behaviors for each personality: In Animal Crossing, two villagers who are both Cranky can seem like very different people, partly because the pool of conversations you can have with a Cranky villager is so huge.

For the purposes of a project at this scale, I think this number of personality-specific messages is not ideal, but adequate.


    choose row 1 in antic table of trait;
    say "[antic entry][paragraph break]";

Oh, this is just us saying the antic happens. The antic happens!

None of the dinosaurs’ antics are sophisticated enough to change the world state. It’d be fun, if time-consuming, to make aggro dinosaurs scare wimpy dinosaurs into running into other rooms; or they could scare wimpy dinosaurs into jumping into your inventory; or they could rip holes in each other’s seams! There’s all kinds of stuff I could have done. I did include at least one neat interaction. This is the final part of the great big antics rule:

    if hilarious entry is 1:
      let hyena be a random available stuffy described by giggly in the location;
      if hyena is a thing:
        say "[The hyena] busts up laughing at this."

So the hilarious antics of clumsy dinosaurs elicit laughter from giggly dinosaurs. Adorable.

More Nonsense

Speaking of adorable, various messages require dinosaurs to make noises. This is the most trivial example of “procedural generation” in the game but it’s deeply essential:

To say roarnoise:
  say "[one of]rawr[or]raaaar[or]rar[or]graaa[or]raghghgh[or]raar[or]grar[or]hraaa[or]mrar[or]rahaaa[or]grrrr[or]awooooo[or]graaaAAaar[at random]"
To say cutenoise:
  say "[one of]mrow[or]bububu[or]papapa[or]boof[or]hummmm[or]pah[or]bah[or]mmmah[or]guh[or]sfsfsf[or]meep[at random]"

And just for the heck of it, here’s the antic table for a dinosaur wearing the cowboy hat:

Table of Cowboy Antics
antic                                                              otherguy  hilarious
"[The current actor] swaggers around in a bowlegged manner."        0        0
"[The current actor] gives [the other actor] a casual nod."         1        0
"[The current actor] narrows its eyes and whistles."                0        0
"[The current actor] squints meaningfully at [the other actor]."    1        0
"[The current actor] spits."                                        0        0
"[The current actor] surveys the area with a wearied expression."   0        0
"[The current actor] grunts at [the other actor]."                  1        0

When we tested the hats on the podcast, we couldn’t get any such message to trigger. Ater testing it some more, I came to the conclusions above: There should be messages every turn, and cute messages should not be totally overridden by hunger messages.

The source ends with this sad comment, distressingly emblematic of my present feelings that Wooby failed to meet his grand potential:

[Code for the the cop chasing the pirate and the pirate chasing the top hat just did not work.]


I don’t consider The Island of Doctor Wooby to be a failure. A lot of people played it; a lot of people sent me dinosaur descriptions on Twitter, and I’m pretty sure I did drawings of all of them! We all had fun, and that’s what matters.

But I made it for a game jam, with a deadline. Come to think of it, though, I don’t think the deadline made me rush to a release. I think I was eager to put the game out just because I really liked it, and I wanted to let people play it as soon as I could!

So it’s only now, sobered by the march of months, that I am able to see so much room for improvement. A better Wooby would have more environments, more secrets, and more fun verbs to let you interact with these darling dinosaurs. But the core experience is supposed to be dino-watching, and the procedural systems that enable that experience also detract from it in certain ways.

I don’t think I’ll do a re-release of this game, but I think I might re-use some of its systems in a different project, and hopefully improve on them there.

In the meantime, if you feel like returning to the Island of Doctor Wooby, it’s always there for you, and you can tweet a dinosaur description to me any day of the week and I will draw that sucker for you as fast as I can.

Thank you for your time.