PunyInform Coding 2: Objects and Actions

PunyInform is a tool for writing text adventures for 8-bit computers and newer computers alike. If you have already installed PunyInform and gone through the PunyInform Coding 1 tutorial, the time has come to learn more about objects and actions. This tutorial goes through the different parts of a simple game where there are two puzzles to be solved, you can score points and you can win the game. I aim to explain everything in this game that wasn’t covered in the previous tutorial.

The game starts with the player outside a house, where they think the will of their uncle Billy may be. They need to get through the locked front door and then through another locked door to get to the room where the will is. That’s the whole game. You can download the game code as well as the compiled game from this folder.

Declaring the constants

A PunyInform game starts with a set of constants to say what the name of the game is, what optional parts of the library you want to use, and more. You typically need to declare all constants before including “globals.h”.

On line 8 we declare that we want to show the current score (and the number of moves) in the statusline. The other option is to show time in the statusline, like “3:00 PM”. Make sure to always declare what kind of statusline you want.

On line 11 we say that we want to use the Simple Door mechanism available in PunyInform. This makes it easier to create doors.

For this game, we’ve decided that we’ve decided that we don’t need the library to care about light and darkness – we’ll just assume there is always light present in all locations. So, we define `OPTIONAL_NO_DARKNESS` in line 12, to exclude the parts of the library that handles light and darkness. This makes the library slightly smaller, and the `light `attribute won’t be defined, since it’s not needed.

Line 15-20 is about scoring. `OPTIONAL_SCORED` means we want to use the `scored` attribute as a simple way to give the player points when they pick up certain objects or visit certain locations. `OPTIONAL_FULL_SCORE` means we want to support the “full score” verb, giving the player a summary of the points. `MAX_SCORE` sets the maximum score. `OBJECT_SCORE` sets the number of points the player gets for each picked-up object that has the `scored` attribute, and `ROOM_SCORE` the number of points for each visited room that has the `scored` attribute. If you don’t declare `OPTIONAL_SCORED` these two constants have no effect.

Then we include “globals.h” and “puny.h” like we always do in a PunyInform game. If we need to declare some global variables of our own or write any entry point routines, we do this between the inclusion of “globals.h” and “puny.h”. For this game, we don’t need to write anything there.

Grammar, verbs and actions

PunyInform comes with a standard set of verbs which most games need, like take, drop, open, lock, put, insert etc. Each verb has a grammar – one or more lines which say how it can be used by the player to form a command leading to an action. Different lines may lead to different actions, like “put coat on shelf” triggers the action `##PutOn`, but “put on coat” triggers the `##Wear` action (actions are (optionally) prefixed with ## to make them stand out in text and code).

In this game, we’d like the player to be able to knock on a door, and PunyInform doesn’t have a verb or an action for that. So we declare the verb ‘knock’. Each grammar line starts with *, adds any prepositions the player has to type, if any, and “noun” to say that the player needs to specify an object. You can also write alternative prepositions, like this: `* 'on'/'at'/'in' noun -> KnockOn`. And you can specify two nouns like this: `* 'on' noun 'with' noun -> KnockOn`. The last grammar line must end with `;` . Writing `KnockOn` after `->` in a grammar line means there is now an action called ##KnockOn and we will have to create a routine called `KnockOnSub` to handle this action, or we get a compilation error. Also note that dictionary words (words that we want to match against words typed by the player) must always be written with single quotes in Inform.

We’d also like the player to be able to look under objects. This is a little different, because PunyInform already has support for the verb ‘look’. We just want to add a line to the grammar of the ‘look’ verb. We do this with `Extend`. For this grammar line, we specify another new action called `##LookUnder`. Of course, we’ll also need to create a routine called LookUnderSub to handle this action.

Now we get to the action routines. An action routine defines what an action does by default. Every actions falls into one of three groups:

• Group 1: Meta-actions that do something to control the game itself, like `##Save` (which saves the current state of the game to a file) and `##Score` (which prints the player’s score).
• Group 2: Actions which change something in the game world or print some important information about it, like `##Take` (which moves an object to the player’s inventory) and `##Inv` which lists the player’s possessions.
• Group 3: Actions which by default don’t do anything but print a message essentially refusing to do it or saying it has no effect, like `##Swim`, `##Push` and `##Yell`.

You can change what group 2 actions and group 3 actions do for certain objects by adding `before` routines to these objects or to the room where the action happens. You can also choose to let any other object present react using a `react_before` routine. If any of these routines return true, it means the default behaviour of the action is cancelled, and the action routine won’t even be called.

For group 2 actions, you can also use an `after` routine in the object affected or the room, or a `react_after` routine in any other object present to do something special and/or print a custom message after the action has happened. If the routine returns true, the action routine won’t print anything.

Most actions you add to a game tend to be group 3 actions. Both of the actions specified here are group 3 actions. This means the action routine should just print a default message and that’s it.

The outside locations

Let’s start defining the game objects. First comes the Lawn location. Nothing new there. Then there’s the FrontPorch location. The only new concept here is that an exit can lead to a door object instead of a location. We’ll cover that in the next section.

Using parse_name

Now to the door mat, and this is where it gets intersting. It may look like we could just have added `name 'door' 'mat',` to the properties, but that would get us in trouble. See, there’s a front door in this location (declared further down), and this door mat. If the player types “open door” or “examine door”, we don’t want the parser to ask “Which one do you mean, the front door or the door mat?”. So, we give the DoorMat object a `parse_name` routine. This is a routine which can read words from the player input using `NextWord()` and return how many words in a row match this object. This specific routine returns 2 (meaning it matched two words) if the player typed “door mat” or, failing that, it returns 1 if the player typed “doormat” or just “mat”. If the player typed anything else, the routine returns false (which equals 0), meaning that nothing matched.

Routines in properties, like this one, return false / 0 by default, while named routines (like the action routines in the last section) return true / 1 by default.

In line 55 the routine starts with `[` . After that we can name up to 15 local variables we want to use in this routine. These are also used as parameters, that is they get assigned values if someone calls the routine with one or more arguments. We choose to create two local variables named w1 and w2. The `;` marks the end of the local variable list. A local variable is a variable that is created when the routine is called and destroyed when the routine returns. It only exists within the routine, and it can’t be accessed in any way from any other routine. Different routines can have local variables with the same name, with no risk of confusion.

In line 56 and 57 we assign values to these two variables. Assignment is done with a single `= `. On the righthand side we call the function `NextWord `and use its return value. The parenthesis mean “call the routine with the name I just gave”. If you want to send arguments to the routine, you put them within the parenthesis with commas between them.

In line 58 we see `==` which is used to check if a value equals another value. We also see the operator && which means AND, so the if-statement checks if the first word is ‘door’ AND the second word is ‘mat’. Additionally, Inform has the operators || meaning OR and ~~ meaning NOT.

In line 59 we see another neat trick in Inform: the keyword `or`. It can be used when comparing one value to multiple other values, and it generates shorter and faster code than doing the comparisons one by one. Line 59 is functionally equivalent to:

`if(w1 == 'doormat' || w1 == 'mat') return 1;`

Hiding the key under the door mat

There’s a key hidden under the door mat. Since the key can’t be seen, we actually don’t put the key in the location just yet, but the player should be able to find it. So, we want the DoorMat object to be special in two ways:

• If the player looks under the mat or pulls the mat aside, they should find the key.
• To avoid exposing the key with one of the standard actions that many players try, like “take all”, we want to stop the player from picking up the mat.

We accomplish this with the `before` routine. First, we react to the `##Take` and `##Remove` actions. Actually just `##Take` would have sufficed here, but `##Take` and `##Remove` are the only two actions you ever need to consider if you want to stop an object from entering the player’s possession, so I usually type out both (`##Remove` is used when an object is removed from inside a container or on a supporter).

Printing a response and returning true

The syntax on line 63 and 68 where we just type a string enclosed by doublequotes is a shorthand for the `print_ret` command, which means “print this, then print a newline character, and return true”. So these four pieces of code are actually equivalent:

Looking under the mat

Then let’s look at the second part of the `before` routine. If the action is `##LookUnder` or `##Pull`, we check if MetalKey has the parent 0 – this means that it hasn’t been brought into the game yet. If this is true, we move the key to the current location (`location` is a global variable holding the object ID of the current location), we increase the score by 10 points, we print a message and return true. If the condition isn’t met, the routine will return false and the action will respond with its default message. Note that whenever we want an if-statement to perform more than one command, we must enclose these commands in { } like we do on line 65-69.

Finally, this object has two attributes: `supporter` and `enterable`. The first means there can be objects on this object. The latter means the player can also be on this object, using a command such as “sit on mat” or “enter mat” to get on.

The key

Now we define the key. Since there is no `->` after `Object` the key won’t be put in the last defined location, but it’s rather hanging in limbo, not in or on anything, when the game starts. The only news here is the `scored` attribute. This means the player will get a certain number of points when they first pick it up. The number of points is the value of `OBJECT_SCORE` , given on line 19.

The front door

Now it’s time to define our first door. We’ll be using PunyInform’s Simple Door mechanism, since this makes the code simpler and most of the time it covers the needs. A door in PunyInform is any object which has the `door` attribute. A door is a gateway between locations. It typically exists in two locations and provides a means for going from the first location to the second or from the second location to the first.

`when_open` and `when_closed` play the same role as `initial` but for doors and containers only. That is, the object gets a paragraph of its own in the room description. If the object is open, `when_open` is used, otherwise `when_closed` is used. In this case, we want the `when_open` text to be different depending on if we’re seeing the door from the outside or the inside of the house, so we use a routine. For `when_closed` we’re fine with just a static string.

Knock knock, who’s there?

Now comes the reason we created the ‘knock’ verb and the `##KnockOn` action earlier: We want to give a proper reply if the player tries to knock on the front door, since that seems a quite sensible thing to do. We do this with a `before` routine. If the action is `##KnockOn` , we print a message and return true, thus stopping the default message from printing.

Floating objects

At any point in time, each object can only be in one location, but PunyInform has a mechanism for moving objects around whenever the player moves, so an object can appear to exist in several locations. This is sometimes referred to as floating objects and is controlled with the `found_in` property. The value of `found_in` should be either a routine which returns true if the object should be in the current location, or a list of locations where it should be (You will notice that a list of values in Inform is also referred to as an array in the documentation).

If we want to use the Simple Door mechanism for a door, that door has to have a `found_in` property, and its value must be an array with exactly two values. In this case, we place the door in FrontPorch and Entrance.

Making the door work

A door has to have a `door_dir` property, to say in which direction a door lies. For a regular door, this should be a single value or a routine, and you usually need to write a routine which returns different values depending on the current location. If you use the Simple Door mechanim, and the door has an array with two values in `found_in` , you can just give an array with two values here. If the player is in the first location in the `found_in` array, the first value is used, otherwise the second value is used. We use parenthesis around the values to avoid a warning from the compiler.

For a regular door, we also have to provide a `door_to` property, saying where the door leads. This too should be a single value or a routine. For a Simple Door, we can just skip it. If the player is in the first location in the `found_in` array, the door leads to the second location in the array and vice versa.

For an object which is lockable, the `with_key` property is used to say which key fits the lock.

Then come the attributes. We use `static` to say that this object is fixed in place and can´t be picked up by the player. We use `door` to say it’s a door. We must add `openable` to say it can be opened and closed and `lockable` to say it can be locked and unlocked. Finally we add `locked` to say the door is locked from the start. And that’s it, we have a complete door!

Here’s the definition of Entrance, the room inside FrontDoor. You can also see that one exit leads to FrontDoor, and one leads to another door called OfficeDoor. This location has the `scored` attribute, which means the player gets a certain number of points when they first come here. The number of points is the value of `ROOM_SCORE` , given on line 20.

There are several interesting objects in this location. We start with the glass box. As you’ll soon see, there are several boxes in this location. It makes sense to allow the player to refer to all of them at once, as in “take the boxes”. To support this, we add the word ‘boxes’ to the `name` property, only we add the plural flag to it, to say it’s the plural form of the name, like so: ‘boxes//p’.

Also, we’re making this box `enterable`, and we want to add a description for when the player is inside the box. We do this with `inside_description`. This can also be used for enterable supporters.

If the player tries to move the box around, we want to tell them it’s real heavy, so we add a before routine which captures the common actions used to move things around.

We proceed to the attributes. This is a `static` object, which means any attempts to take or move the object gets a reply like “It’s fixed in place.” (only we’ve chosen to override all or most of them in the before routine). It’s a `container`, meaning other objects can be inside it. `openable` means it can be opened and closed. `enterable` means the player can enter the object. `transparent` means it’s see-through, which means we can look at the contents even when it’s closed. `transparent` also means that if something gives off light on the outside there’s also light on the inside and vice versa, but this doesn’t matter in this particular game, since we defined `OPTIONAL_NO_DARKNESS` on line 12.

The necklace

Now we add a necklace. It has two arrows ( `->` ) after `Object`, which means it is in the last object defined with a single arrow, in this case the glass box.

The player should be able to break the necklace to free the gems. We do this with a before routine. We’ll need a local variable, and we’ll call it `loc` (short for “location of the necklace”). We capture the `##Attack` action, which is what we get if the player types “break necklace”, “destroy necklace” or “wreck necklace”. First we use the PunyInform routine `ObjectIsUntouchable` to check if the necklace is untouchable by the player. If it is, `ObjectIsUntouchable `prints a message telling the player why they can’t reach the necklace, and we return true to tell PunyInform not to do or print anything further. Note that we use `noun` here, which is always set to the primary object of the current action. When we’re in a before routine (At least one that doesn’t belong to a room), `noun` has the same value as `self`, so we can use them interchangably. As we have now decided that the player can reach the necklace, we perform a sequence of commands:

1. We set `loc` to the current parent of the necklace. This could be the glass box where we found it, but it could also be the player, or it could be the Entrance location etc.
2. We then `remove` the necklace, meaning we remove it from the object tree and put it in the void where the player will never see it.
3. Next we move the three gems that are part of the necklace to the location where the necklace was.
4. We increase the score
5. We print a message and return true, signaling that PunyInform should not print the default message for `##Attack`.

The necklace has a single attribute: `transparent` . This is there to signal that if there are objects in this object, they can be seen by the player. Placing objects within other objects means different things depending on the object type of the parent object:

• For a container (a bag, a box, a car etc), it means the objects in it are in fact inside the container.
• For a supporter (a bed, a table, a carpet etc), it means the objects in it are on the supporter.
• For an animate object (a person, an animal, a robot etc), it means the objects in it are held by the animate object.
• For all other objects, it means the objects are part of the object, like buttons are part of a cash register etc.

The last case is what we’re dealing with here. Look at the next piece of code to see the gems that are part of the necklace.

The gems

We now add the three gems. The three arrows mean they’re in (meaning part of) the necklace which we just defined. All three have the plural word ‘gems’ added to their `name` property, so the player can “take gems”. The emerald needs the indefinite article “an” and so we give it the property `article` and the desired article as a string.

Putting the gems in the necklace like this means they go wherever the necklace goes, the player can examine the gems, but if they try to take one of the gems they get a message that it’s part of the necklace.

The office door lock

Apart from the glass box with the necklace in it, there is also a blue box and a green box here, attached to the wall next to the office door. These are in fact part of the lock mechanism of the office door. If you put a sapphire in the blue box and an emerald in the green box, the door is unlocked. The code to make this happen, however, is not in the boxes but in the door itself.

The code to define the boxes doesn’t hold many surpises. The only thing new here is the `describe` property. This is used when you may want to print a paragraph of text for an object in the room description. If you do, you print a newline, any text you like, another newline and then return true. If you decide you don’t want to print a separate paragraph for this object, you return false. But there’s trick you can do here: Don’t print anything but return true. This will stop “look” from printing anything about the object at all. The boxes are mentioned in the `description` property of the room, so we don’t want the game to show them again.

The office door

The player can place various objects in the blue and green boxes. Whenever the player has inserted something in something else (“put emerald in green box” triggers an `##Insert` action), or picked something up, we want to check if the conditions are met to unlock the door. When we want an object (in this case the office door) to react to an action that happens nearby but where this object may not be directly involved, we can use `react_before` and `react_after`. They work very much like `before` and `after`. We can use `noun` and `second` to check which primary and secondary nouns are used in the action. In this case, we don’t need that though. We check if the following conditions are met:

• The sapphire is in the blue box
• There is exactly one object in the blue box
• The emerald is in the green box
• There is exactly one object in the green box
• The door is locked

If all of these conditions are met, we:

1. Unlock the door
2. Give the player some points
3. Print a message and return true, meaning that PunyInform needn’t print anything

Unlocking the door is done by setting the `locked` attribute to 0 for the OfficeDoor object. An attribute is a bit value – it can be 0 or 1. This is what you can do with an attribute:

• You can check if attribute `locked` is 1 for object X with `if(X has locked)`
• You can check if `locked` is 0 with `if(X hasnt locked)`
• You can set `locked` to 1 with `give X locked`
• You can set `locked` to 0 with `give X ~locked`

The rest of the definition of this object is very similar to the front door. One difference is that while it has the `lockable` attribute, it doesn’t have the `with_key` property. This means the player can tell the door is locked, but there is no key that can unlock it using the “unlock” verb.

The final location

We have come to the final location, the office. We describe a desk and chair, but they’re really not that important, so we’d rather not implement them as objects. Instead we put these words in the `name` property of the location, so the player gets a message saying they don’t need to refer to these objects, if they try to interact with them.

The will has the `initial` property meaning this string will be printed as a paragraph of its own when the player views this room, as long as the object hasn’t moved. If the player takes the will and drops it again, it will just be listed along with any other regular objects in the location.

And then there’s the `description` of course. If the player tries to examine or read the will, the `description` property is consulted. If it’s a routine, it is run. So this is a good place for us to end the game. This is what we do:

1. We set `deadflag` to `GS_WIN` (which is 2, but it’s easier to remember `GS_WIN`) (“GS” stands for Game State by the way, since this variable controls if the game goes on or if it ends). If you set it to `GS_DEAD` instead, the game ends as well, but with a message saying the player has lost.
2. We increase the score.
3. We print the description of the will. (Just writing a string within double-quotes like this means we also print a newline, which is good, and return true, which doesn’t matter – the description property is expected to always print a description and its return value is simply ignored.)

After the action has been completed, PunyInform notices that deadflag is now `GS_WIN `and ends the game.

The Initialise routine

Finally, it’s time to write the mandatory Initialise routine, which is called when the game starts. We can use this routine to move things around, start timers etc, but for this game all we need is to print an introduction to the game.

The finished game

After compiling the game to a Z-code file, you can play it your Z-code interpreter of choice. The above screenshot is from Windows Frotz.