Inform 7 as narrative simulator

By Allison Parrish

This tutorial presents some strategies for making “narrative simulations” in Inform 7. By “narrative simulation” I mean a system for generating events that unfold over time in a simulated world. I start by showing a how non-player characters can be programmed to perform Inform 7 actions, and then I show how these actions can be encouraged on every turn. Along the way, I discuss some Inform 7 basics, such as descriptions, lists, and loops. Finally, I show how an Inform 7 game can be made to progress without any input from the player at all, and how non-player characters can be scripted to pursue particular goals. Section 7 from the Inform 7 Recipe Book covers similar territory.

The tutorial is specifically aimed at people who have followed my Inform 7 Concepts and Strategies tutorial, but should be accessible to any Inform 7 programmer at a beginning level.

Non-player actors

You can easily introduce non-player characters into your story. Inform 7 has a kind of thing, a person that has sub-kinds man, woman and animal. However, unless you associate some kind of activity with this character, they will be effectively inert, like any other set decoration.

Non-player characters can be programmed to attempt actions (built-in actions like taking, going, and eating, but also actions that you define yourself) with the try phrase. In the example below, I create a custom action (“purring”), and then override the built-in kissing action so that the player’s kiss will provoke the cat to perform this action.

"Smooch that kitty" by Allison Parrish

A cat is a kind of animal.

Purring is an action applying to nothing.

Report something purring:
	say "[the actor] purrs."

Instead of kissing a cat:
	say "You give [the noun] a smooch.";
	try the noun purring.

The Study is a room. Walnut is a cat in the study.

Aside from the try phrase, there’s another new bit of syntax in this example. I’ve written report something purring, rather than simply report purring. The former indicates that this report rule should apply to any actor; the latter applies only to the player. Inside of a report something rule, you can use the actor to refer to the thing that actually did the action. Also note that I didn’t bother to define a new player command with the Understand… assertion, since the player will never invoke this action.

Arguably, I could have made this example even more simple by simply adding a say phrase in the instead of kissing a cat rule. There’s no real reason here to go to all the trouble of defining a new action. However, a benefit of defining actions (rather than stuffing everything into say) is that we can use all of Inform’s existing machinery surrounding actions to our advantage.

In the following example, Walnut likes smooches and purrs on receipt of one. Muffins, on the other hand, will leave the room indignantly when kissed.

"Smooch those kitties" by Allison Parrish

A cat is a kind of animal. A cat can be either affectionate or aloof. Cats are usually aloof.

Purring is an action applying to nothing.

Report something purring:
	say "[the actor] purrs."

Instead of kissing a cat:
	say "You give [the noun] a smooch.";
	if the noun is affectionate:
		try the noun purring;
	otherwise:
		try the noun going south.

The Study is a room.

Walnut is a cat in the study. Walnut is affectionate.

Muffins is a cat in the study. Muffins is aloof.

The Kitchen is south of the Study.

Note that in this example, Inform still follows all of the regular rules for going, including the message from the going action’s report rule. Neat!

Exercise: Modify the example above so that kissing aloof cats will cause them to leave the room in any available direction (i.e., if you kiss Muffins in the Kitchen, he should move back to the Study).

Doing something on every turn

Every turn rules. Test this by typing wait.

"Bored" by Allison Parrish

The Study is a room.

Every turn:
	say "[one of]Ho-hum[or]Sigh[or]Hmm[or]Kinda bored[or]Bleah[at random]."

We can use an every turn rule to encourage actors to do things using try:

"The Purrs of the Kitties" by Allison Parrish

A cat is a kind of animal.

Purring is an action applying to nothing.

Report something purring:
	say "[the actor] purrs."

The Study is a room. Walnut is a cat in the study. Muffins is a cat in the study.

Every turn:
	try Walnut purring;
	try Muffins purring.

Querying the world model

We may want to program the same every turn behavior for more than one object. One way to do this is as above: just type the name of every object. Another way is to query the world model and get a list of objects that match some description, then iterate over those objects in a loop. (Yes, Inform 7 has lists and loops!)

First, we’ll use the let phrase, which creates a variable with the given name, and assigns to it the value after the word be. The variable name needs to be a name that hasn’t been used elsewhere in the source code, and the variable itself is local to its current block (i.e., the rule). The words the list of indicates that this value should be a list, and the list itself will be populated by objects matching the description that follows. A very simple description that results in a list is using the word all followed by a kind, as in the example below:

"The Kitchen Sink" by Allison Parrish

The Kitchen is a room. The sink is in the Kitchen.

Performing object vision is an action applying to nothing. Understand "object vision" as performing object vision.

Carry out performing object vision:
	let L be the list of all objects;
	say L.

When you type object vision, the Carry out rule prints literally every object, including Inform’s built-in objects.

Getting specific with descriptions

We can, however, be more specific than this, limiting our query with relations and/or adjectives in the description:

"A List of Cats" by Allison Parrish

Performing catventory is an action applying to nothing. Understand "catventory" as performing catventory.

A cat is a kind of animal. A cat can be either affectionate or aloof. Cats are usually aloof.

The Study is a room. The player is in the Study.

The Kitchen is south of the Study. 

Muffins and Dimple are affectionate cats in the Study. Walnut, Giorgio and Bucket are aloof cats in the Study. 

Hamburger is an affectionate cat in the Kitchen. Zelda and Big Butt are aloof cats in the Kitchen.

Carry out performing catventory:
	let L be the list of affectionate cats;
	say "Affectionate cats: [L].";
	let F be the list of aloof cats;
	say "Aloof cats: [F]."

Note that the query gives us everything that matches, not just things that are in the player’s “scope” (i.e., visible and in the current room). We can be even more specific by specifying relations, e.g., the in relation for the desired location:

"A List of Cats" by Allison Parrish

Performing catventory is an action applying to nothing. Understand "catventory" as performing catventory.

A cat is a kind of animal. A cat can be either affectionate or aloof. Cats are usually aloof.

The Study is a room. The player is in the Study.

The Kitchen is south of the Study. 

Muffins and Dimple are affectionate cats in the Study. Walnut, Giorgio and Bucket are aloof cats in the Study. Hamburger is an affectionate cat in the Kitchen. Zelda and Big Butt are aloof cats in the Kitchen. 

Carry out performing catventory:
	let L be the list of affectionate cats in the Study;
	say "Affectionate cats: [L].";
	let F be the list of aloof cats in the Study;
	say "Aloof cats: [F]."

Exercise: Use the visible adjective in the descriptions above to list only the cats that are in the player’s current room and also visible to the player (i.e., not in a container).

Getting a thing at random

You can use a similar syntax to query the world model for a single random object meeting your description. Just put the word random before your description. In the following example, there are three cats in the study. On each turn, a random cat is entreatied to purr:

"A Random Cat" by Allison Parrish

A cat is a kind of animal.

Purring is an action applying to nothing.

Report something purring:
	say "[the actor] purrs."

The Study is a room. 

Muffins, Dimple and Hamburger are cats in the Study.

Every turn:
	let C be a random cat in the Study;
	try C purring.

Moving in a random direction

Eventually we might want our characters to move with a purpose, but a good start for simulating movement is to get characters to move around at random. In Inform, this means randomly moving between rooms. There are two descriptions that can help us with this. The text rooms adjacent to R evaluates to a list of rooms that are connected to a given room R; the full description a random room adjacent to R returns one of those rooms at random. The easiest way to move in the direction of that room is the description the best route from R to S, where R and S are both rooms.

Here’s an example of both of these features in action! We use the phrase the location of Muffins to get the room that Muffins is currently in. On every turn, Muffins moves to a room adjacent to that location.

This example also makes use of the fact that names of variables in Inform can be any valid string, including spaces, as long as that string has not been previously used to name something else in the program. I’m using the name “the next room” to store the random adjacent room, and “the next direction” to store the direction that Muffins will move to.

"Muffins leaves the room" by Allison Parrish

The Living Room is a room. The Kitchen is south of the Living Room. The Foyer is east of the Living room. The Dining Room is west of the Living Room. The Veranda is north of the Living Room. The player is in the Living Room.

A cat is a kind of animal. Muffins is a cat in the Living Room.

Every turn:
	let the next room be a random room adjacent to the location of Muffins;
	let the next direction be the best route from the location of Muffins to the next room;
	try Muffins going the next direction.

Looping over lists

In the examples above, I’ve used the fact that lists are “sayable values” that can be incorporated into strings as substitutions. (See Writing with Inform section 5.5 for more details about how this text can be formatted.) This is fine for just displaying what’s in the list, but if we actually want to do something with the list, we need to be able to address each of the list’s items. An easy way to do this is with Inform’s repeat with … running through syntax.

In the following example, I create a comment command that iterates through all of the objects in the player’s current location and briefly riffs on them. (Note that the description makes use of a which clause to further narrow down the list of matching objects.)

"Commenting on things" by Allison Parrish

The Kitchen is a room. The blender, the refrigerator, the cheesewheel, the sink, and the oven are things in the Kitchen.

Commenting on stuff is an action applying to nothing. Understand "comment" as commenting on stuff.

Carry out commenting on stuff:
	let L be the list of things which are not the player in the location of the player;
	repeat with X running through L:
		say "[one of]Oh, [a X]. Cool[or]I spy [a X][or]Of course there'd be [a X][at random]."

Okay! Now we’re in a position to put some stuff together, including custom actions, every turn rules, and querying the world model. In the following example, I write an every turn rule that loops over cats matching a particular property, and then have them perform an action. Test this by typing wait and let the vocalizations begin:

"The Vocalizations of Cats" by Allison Parrish

A cat is a kind of animal. A cat can be either affectionate or aloof. Cats are usually aloof.

Purring is an action applying to nothing.

Report something purring:
	say "[the actor] purrs."

Hissing is an action applying to nothing.

Report something hissing:
	say "[the actor] hisses."

The Study is a room. 

Muffins and Dimple are affectionate cats in the Study.

Walnut, Giorgio and Bucket are aloof cats in the Study. 

Every turn:
	let L be the list of affectionate cats in the location of the player;
	repeat with X running through L:
		try X purring;
	let F be the list of aloof cats in the location of the player;
	repeat with X running through F:
		try X hissing.

Again, note also that the variable names in the above phrases (e.g., L, X, F etc.) can be any valid Inform 7 name, as long as that name hasn’t been used previously in the source code. Picking a name carefully can help the loop read more naturalistically. For example:

"Commenting on things" by Allison Parrish

The Kitchen is a room. The blender, the refrigerator, the cheesewheel, the sink, and the oven are things in the Kitchen.

Commenting on stuff is an action applying to nothing. Understand "comment" as commenting on stuff.

Carry out commenting on stuff:
	let the comment-worthy items be the list of things which are not the player in the location of the player;
	repeat with current item running through the comment-worthy items:
		say "[one of]Oh, [a current item]. Cool[or]I spy [a current item][or]Of course there'd be [a current item][at random]."

An adaptation of Teens Wander Around A House

We have almost enough machinery in place to adapt Darius Kazemi’s classic of procedural narrative, Teens Wander Around A House. In this novel, a number of characters are placed in a simulated house. At each timestep, the characters move to random locations adjacent to their current locations. If two or more characters find themselves in the same location, an event ensues involving them. Here’s a typical passage from Teens Wander Around A House:

Vivianna encountered Philomena in the foyer. “Where did you get that phone, Philomena?”

Philomena ignored her.

The living room held two items of interest to Kiah: the coffee table and Darby. Kiah played nervously with her purse in a successful bid to avoid talking to Darby.

Nobody was in the front yard, so Gale found herself uncharacteristically at ease. Gale started to climb the tree, then thought better of it.

As a first approximation, we’ll program this in Inform by first creating a map of a house and placing teens within it; then we’ll use an every turn rule to move them from one room to the next.

"Teens Wander: First Approximation" by Allison Parrish

The Foyer is a room. The Living Room is north of the Foyer. The Front Yard is south of the Foyer. The Library is west of the Foyer.

The Dining Room is east of the Living Room. The Kitchen is east of the Dining Room. The Back Yard is north of the Dining Room.

A teen is a kind of person. Philomena, Dita, Vivianna, Darby, Kiah and Gale are teens in the Front Yard.

The player is in the Foyer.

Every turn:
	let the current teen be a random teen;
	let their next room be a random room adjacent to the location of the current teen;
	let their direction be the best route from the location of the current teen to their next room;
	try the current teen going their direction.

Test this game by typing wait a few times. You’ll witness various teens coming and going.

This is okay as far as it goes, but it’s missing a few fundamental features of Teens Wander Around A House. Here are a few of them:

  • The game requires player interaction (via the wait command) rather than autonomously generating a text.
  • The narration includes only events that are visibile to the player (i.e., when teens move in and out of rooms that the player isn’t in, we don’t see any evidence of that movement). This is different from the novel, which has an omniscient point of view.
  • The narration itself is pretty bland: we’re only seeing Inform’s default reporting text.
  • We haven’t programmed the interactions between the teens yet.

Let’s take a look at this issues in order.

An interactive fiction that plays itself

Important for this to have an end condition; otherwise it will repeat indefinitely.

"Parse-imonious" by Allison Parrish

The Study is a room.

The parse command rule is not listed in any rulebook.

The generate action rule is not listed in any rulebook.

Every turn:
	say "howdy";
	if turn count is 5:
		end the story;

Including things in player scope

Normally interactive fiction is narrated from the perspective of something called the “player,” which is a person in the story world who can perceive and interact with things in a particular location. (Normally this happens in the second person present tense, a peculiarity of interactive fiction when compared to other forms of narrative, though Inform includes some machinery to conduct narration in other persons and tenses.) In Inform, “scope” is the term that describes all of the things that Inform considers perceptible to the player. The Inform Recipe sums up the default definition of scope like this: “the scope for someone consists of everything in the same place as them, unless it is dark.”

We may want our narrative simulation to take perspective into account, and only narrate events perceptible to a single character, or a collection of characters; these are common modes in narrative fiction. However, Teens Wander Around A House is narrated in an omniscient fashion, in which the movements, location, feelings, and speech of any of the characters are available to the narrator.

The Recipe Book’s section on the deciding the scope of the player rule has some tips for manipulating scope in general. But we want to change scope entirely, making anything in the game visible to the player, so we can write some code to narrate these events. The following example shows an easy way to do this: just go through all of the rooms and add them to scope.

"I can hear the cats purring as one" by Allison Parrish

The Living Room is a room. The Kitchen is south of the Living Room.

A cat is a kind of animal. Muffins is a cat in the Living Room. Walnut is a cat
in the Kitchen.

Purring is an action applying to nothing.

Report something purring:
	say "In [the location of the actor], [the actor] purrs."

Every turn:
	repeat with the furrball running through list of all cats:
		try the furrball purring.

[comment the following out if you want to see the default behavior!]

After deciding the scope of the player:
	repeat with X running through the list of all rooms:
		place X in scope.

In a report rule, the description The location of the actor gives the name of the room that the relevant actor is currently in. I’ve used it here to give the narration a bit of context; without this part, Inform would simply report that “Walnut purrs” or “Muffins purrs.”

Narration strategies

We can do this in one of two ways: either by redefining the report rules for every relevant action, or having a single after rule that covers any action that happens, overriding any report rules.

"The Kitty Smoochers" by Allison Parrish

A cat is a kind of animal. A cat can be either affectionate or aloof. Cats are usually aloof.

Hissing is an action applying to nothing.

Purring is an action applying to nothing.

The block kissing rule is not listed in the check kissing rulebook.

[we put the "carrying out" of the kiss in the report rule, because report happens after the original action has already taken place. otherwise the events would be narrated out of order!]
Report someone kissing:
	say "In [the location of the actor], [the actor] kisses [the noun].";
	if the noun is affectionate:
		try the noun purring;
	otherwise:
		try the noun hissing;

Report something purring:
	say "[the actor] purrs."

Report something hissing:
	say "[the actor] hisses."

The Study is a room. The Kitchen is south of the Study.

Walnut is a cat in the study. Walnut is affectionate.

Muffins is a cat in the kitchen. Muffins is aloof.

Chauncey is a person in the study. Belinda is a person in the kitchen.

Every turn:
	try Chauncey kissing Walnut;
	try Belinda kissing Muffins.

After deciding the scope of the player:
	repeat with X running through the list of all rooms:
		place X in scope.

The other way

"The Kitty Smoochers with one after rule" by Allison Parrish

A cat is a kind of animal. A cat can be either affectionate or aloof. Cats are usually aloof.

Hissing is an action applying to nothing.

Purring is an action applying to nothing.

Report someone kissing a cat:
	if the noun is affectionate:
		try the noun purring;
	otherwise:
		try the noun hissing.

The block kissing rule is not listed in the check kissing rulebook.

After someone doing something:
	let X be the action name part of the current action;
	if X is the action name part of kissing the noun:
		say "In [the location of the actor], [the actor] kisses [the noun].";
	otherwise if X is the action name part of hissing:
		say "In [the location of the actor], [the actor] hisses.";
	otherwise if X is the action name part of purring:
		say "In [the location of the actor], [the actor] purrs.";
	continue the action.

The Study is a room. The Kitchen is south of the Study.

Walnut is a cat in the study. Walnut is affectionate.

Muffins is a cat in the kitchen. Muffins is aloof.

Chauncey is a person in the study. Belinda is a person in the kitchen.

Every turn:
	try Chauncey kissing Walnut;
	try Belinda kissing Muffins.

After deciding the scope of the player:
	repeat with X running through the list of all rooms:
		place X in scope.

Modeling interactions

"The Smoochers of Several Kitties" by Allison Parrish

A cat is a kind of animal. A cat can be either affectionate or aloof. Cats are usually aloof.

Hissing is an action applying to nothing.

Purring is an action applying to nothing.

Report someone kissing a cat:
	if the noun is affectionate:
		try the noun purring;
	otherwise:
		try the noun hissing.

The block kissing rule is not listed in the check kissing rulebook.

After someone doing something:
	[the "action name part" of an action is the action's name; actions that apply to something need "the noun" after them]
	let X be the action name part of the current action;
	if X is the action name part of kissing the noun:
		say "In [the location of the actor], [the actor] kisses [the noun].";
	otherwise if X is the action name part of hissing:
		say "In [the location of the actor], [the actor] hisses.";
	otherwise if X is the action name part of purring:
		say "In [the location of the actor], [the actor] purrs.";
	otherwise if X is the action name part of going the noun:
		say "[The actor] heads [noun], arriving in [the location of the actor].";
	continue the action. [allows other after/report rules to run]

The Study is a room. The Kitchen is south of the Study.

Muffins and Dimple are affectionate cats in the Study. Walnut, Giorgio and Bucket are aloof cats in the Study. 

Hamburger is an affectionate cat in the Kitchen. Zelda and Big Butt are aloof cats in the Kitchen.

Chauncey is a person in the study. Belinda is a person in the kitchen.

Every turn:
	repeat with the kisser running through the list of all people who are not cats:
		repeat with the kitty running through the list of cats in the location of the kisser:
			try the kisser kissing the kitty;
	repeat with the kitty running through the list of all cats:
		let their next room be a random room adjacent to the location of the kitty;
		let their direction be the best route from the location of the kitty to their next room;
		["silently" suppresses Inform's built-in report rules for built-in actions. without this, we'd still see the event narrated from the player's perspective.]
		silently try the kitty going their direction.

After deciding the scope of the player:
	repeat with X running through the list of all rooms:
		place X in scope.

Putting it all together

"Teens Wander: Take two" by Allison Parrish

The Foyer is a room. The Living Room is north of the Foyer. The Front Yard is south of the Foyer. The Library is west of the Foyer.

The Dining Room is east of the Living Room. The Kitchen is east of the Dining Room. The Back Yard is north of the Dining Room.

A teen is a kind of person. Philomena, Dita, Vivianna, Darby, Kiah and Gale are teens in the Front Yard.

The player is in the Foyer.

Every turn:
	let the current teen be a random teen;
	let their next room be a random room adjacent to the location of the current teen;
	let their direction be the best route from the location of the current teen to their next room;
	try the current teen going their direction.

After someone going a direction:
	let teens here be the list of teens who are not the actor in the location of the actor;
	if the number of entries in teens here is greater than 0:
		let the other teen be entry 1 of the teens here;
		say "[The actor] encounters [the other teen] in [the location of the actor].";
	otherwise:
		say "[The actor] finds herself alone in [the location of the actor]."

Every turn:
	if turn count is 25:
		end the story;

[narration machinery follows]

[read no commands; generate no player actions]
The parse command rule is not listed in any rulebook. The generate action rule is not listed in any rulebook.

After deciding the scope of the player:
	repeat with X running through the list of all rooms:
		place X in scope.

More TK!