In this post I’m going to be focusing on the actions that a player will take with an Inform 7 game. Those actions can be interefered with in various ways to make the game play experience more dynamic. All of this will be done using rules, which I covered in the previous post. In this post, you will see how actions are essentially a descriptive circumstance provided to a rule. When that circumstance applies — meaning, when the action has occurred — then interesting things can be made to happen.
Action Processing
What I’m mostly going to be taking about in this post is going to start talking about how Inform 7 handles action processing. Consider that how a player engages with the game is via a sequence of actions. That means much of what you do as an author of the game is figure out how to respond to these actions. That is the action processing I’m talking about. Inform 7 already provides a lot of default action processing for you, as you’ll see.
Here’s some things to keep in mind as you go through this post:
- An action happens due to a command.
- From Inform’s perspective, any action will either succeed or fail.
- Actions are subject to rules.
- Those rules can determine how (or even whether) an action is carried out.
- Those rules indicate whether the action has succeeded or failed.
Let’s start with the following simple source text:
The Learning Lab is a room.
The Office is a room.
The Office is east of the Learning Lab.
A shredder is in the office.
Test me with "east / take the shredder".
Start the game and run the test me
command and you’ll see a fairly simple scenario: the player can move east into the Office and take the shredder. Not a problem. To Inform, “taking” is the action and “the shredder” is an object passed to that action. To Inform the full action being handled here is, not surprisingly, “taking the shredder.” Notice how the player types “take the shredder” and Inform turns that into taking the shredder.” That past participle phrase — “taking the shredder” — is also a circumstance. It happens to be an action-based one. And if you read the last post, you know that a circumstance can be checked by rules. You also might remember that when you provide a rule in your source text, you start it by indicating the circumstance in which it applies.
So now let’s add a rule that takes place when a specific action occurs. Add the following:
"Learning Inform" by Jeff Nyman The Learning Lab is a room. The Office is a room. The Office is east of the Learning Lab. A shredder is in the office. An instead rule for taking the shredder: say "Instead rule blocks taking the shredder." Test me with "east / take the shredder".
Now replay the game (which should run the test me
command). You should see something like this:
Office
You can see a shredder here.
>[2] take the shredder
Instead rule blocks taking the shredder.
>
If you think back to the previous post, this is just an evolution of what you did there. Instead of using the “when play begins” rulebook or the “every turn” rulebook, I’m using another rulebook called “instead”. This rulebook can be filled with “instead” rules. The “when play begins” rules fired when the game first started. The “every turn” rules fired every turn of the game. Here a rule has been specified that only takes place in the context of a specific action. And even then only if that specific action (“taking”) involves a specific object (“the shredder”).
What the instead rule has done is essentially stop the action in its tracks. Without the rule in place, the player is allowed to take the shredder. With the rule in place, the action of taking is subverted and a message is printed instead. Even though a message was printed, you might wonder why that stops the shredder from being taken. And the answer is: because that’s how Inform works. If you use an “instead” rule, you are telling Inform that “instead of doing this thing the player tried, do something else.”
Instead Rules for Actions
With that understanding in place, change your source text so that it looks like this:
The Learning Lab is a room. The Office is a room. The Office is east of the Learning Lab. A shredder is in the office. A bag of buns is in the Learning Lab. Some lighter fluid is in the Learning Lab. A grill is in the Learning Lab. On the grill are a steak and a hot dog. An ice chest is in the Learning Lab. A bottle of coke is in the ice chest. A bottle of juice is in the ice chest.
All of this should be largely familiar to you if you followed my other posts. Essentially a lot of objects are created, including a container and a supporter. I’m going to use these objects to show what happens when you have different rules in place.
To do that, add the following rules and test statement:
Instead of taking something: say "Instead rule blocks taking something." Instead of taking something which is on the grill: say "Instead rule blocks taking something from the grill." Instead of taking something which is in the ice chest: say "Instead rule blocks taking something from the ice chest." Instead of taking the bag of buns: say "Instead rule blocks taking the bag of buns." Instead of doing something other than taking with the bag of buns: say "Instead rule blocks doing anything with the buns except taking." Test me with "look at bag of buns / take the bag of buns / take the lighter fluid / take the steak / take the hot dog / take all from the ice chest".
Take a moment to type it, then read it. I don’t recommend copy+paste. You need to practice the muscle memory of typing in these commands and then exercise the mental muscle of understanding what you type. Copy+paste is really bad for learning.
Here is the output you see:
>test me (Testing.) >[1] look at bag of buns Instead rule blocks doing anything with the buns except taking. >[2] take the bag of buns Instead rule blocks taking the bag of buns. >[3] take the lighter fluid Instead rule blocks taking something. >[4] take the steak Instead rule blocks taking something from the grill. >[5] take the hot dog Instead rule blocks taking something from the grill. >[6] take all from the ice chest bottle of coke: Instead rule blocks taking something from the ice chest. bottle of juice: Instead rule blocks taking something from the ice chest. >
What you should take from this is that rules are fired in most specific order first. In other words, the rule that is more specific “wins” in a given context and is the rule that will be applied. So notice that a generic rule blocks taking something and that does prevent the lighter fluid from being taken, with the appropriate message. However, a more specific rule blocks taking the bag of buns and that, too, gives an appropriate message.
Make sure you understand that last part. Even though the bag of buns is “something”, the “instead of taking something” rule did not apply when the command take the bag of buns
was given. Instead the more specific rule of “instead of taking the bag of buns” was applied.
Also notice that I do two things with the bag of buns (“look at” and “take”), but two different rules impact what happens. In the previous post, I showed you how to look at how rules are represented in Inform. Let’s do that here. Go to the Index tab and click the “Rules” button. In that panel, underneath Rules governing actions, you’ll see this representation of ‘instead’ rules:
Here you can see how Inform is ordering the rules based on their specificity.
Now let’s add this rule:
Instead of doing something to the bag of buns: say "Instead rule blocks doing something to the bag of buns."
If you run the game again with “test me”, you’ll see that this new rule never fires. Check the output and you’ll see that the specific text from the rule just added never shows up. Think about why this might be the case. Use the Rules Index to look at the situation in terms of where this new rule was situated:
The reason this new rule never fires is because it is essentially “behind” (or “below”, if you prefer) two more specific existing rules, which is “instead of taking the bag of buns” and “instead of doing something other than taking with the bag of buns”. Those two rules, combined, basically cover “taking” and “something other than taking”.
So even though “taking” does constitute “doing something”, Inform stil recognizes that a rule which specifically calls out an action (“taking”) should apply over a more generic rule (“doing something”).
Now remove that last rule you just add and replace it with this one:
Instead of taking something in the Learning Lab: say "Instead rule blocks taking something in the Learning Lab."
Run your test again. You’ll find this new rule overrides every other rule except the “instead of taking the bag of buns” rule. The reason why is that taking the bag of buns is much more specific than taking “something” from the Lab. Once again, to drill the point home, let’s check the Rules Index to see how this rule was placed:
Yep, right at the top, which matches what we are seeing in the output.
Now, remove the last rule you just added and replace it with this one:
Instead of doing something other than taking: say "Instead rule blocks doing something other than taking."
This rule overrides only the actions “take all from the ice chest” but none of the others. That might seem kind of interesting to you. Essentially the “take all from” acts differently from Inform’s perspective than just “take”. If you wanted to prove that out, simply change the test string to read this:
Test me with "look at bag of buns / take the bag of buns / take the lighter fluid / take the steak / take the hot dog / take coke".
In that case, you’ll find that it’s the “instead of taking something which is in the ice chest” rule that fires rather than the “instead of doing something other than taking” rule that you just added. It’s may not obvious to you why this is happening, though. So change your ‘test me’ so that it is back to what it was, with a slight addition at the front:
Test me with "actions / look at bag of buns / take the bag of buns / take the lighter fluid / take the steak / take the hot dog / take all from the ice chest".
The “actions” command is essentially a debugging tool built in to Inform that will show you what happens during action processing. With that in place, run the game again with ‘test me’. I’m going to truncate the output a bit, but here are the essential things to notice:
>test me (Testing.) >[1] actions Actions listing on. >[3] take the bag of buns [taking the bag of buns] Instead rule blocks taking the bag of buns. [taking the bag of buns - failed] ... >[5] take the steak [taking the steak] Instead rule blocks taking something from the grill. [taking the steak - failed] ... >[7] take all from the ice chest [removing the bottle of coke from the ice chest] bottle of coke: Instead rule blocks doing something other than taking. [removing the bottle of coke from the ice chest - failed] [removing the bottle of juice from the ice chest] bottle of juice: Instead rule blocks doing something other than taking. [removing the bottle of juice from the ice chest - failed]
What you can see here is why the rules fired differently than perhaps expected. In the case of taking all from the ice chest I was, in fact, “doing something other than taking” according to the Inform action processing. Specifically, Inform was processing the “removing from” action. Whereas you can see in the other actions that it was a “taking” action being performed.
Take some time to go through the above and really observe what is happening. The examples here were designed with text that displays such that you can tell exactly what rule is taking place in what situation. Time spent now understanding an instrumented example like this will pay off later.
Instead Rules and Clauses
Let’s try another variation on all this. We’re going add some new objects, one new rule, and change the test. Since we experimented a bit above, here’s the full source text with the new stuff:
The Learning Lab is a room. The Office is a room. The Office is east of the Learning Lab. A shredder is in the office. A bag of buns is in the Learning Lab. Some lighter fluid is in the Learning Lab. A grill is in the Learning Lab. On the grill are a steak and a hot dog. An ice chest is in the Learning Lab. A bottle of coke is in the ice chest. A bottle of juice is in the ice chest. Maya is a woman in the Learning Lab. An evidence folder is in the Learning Lab. Instead of taking something: say "Instead rule blocks taking something." Instead of taking something which is on the grill: say "Instead rule blocks taking something from the grill." Instead of taking something which is in the ice chest: say "Instead rule blocks taking something from the ice chest." Instead of taking the bag of buns: say "Instead rule blocks taking the bag of buns." Instead of doing something other than taking with the bag of buns: say "Instead rule blocks doing anything with the buns except taking." Instead of taking the evidence folder in the presence of Maya: say "Instead rule blocks taking something in the presence of a character." Test me with "look at bag of buns / take the bag of buns / take the lighter fluid / take the steak / take the hot dog / get all from the ice chest / take the evidence folder".
Since there’s a lot going on here, let’s just make sure we’re seeing the same output. You should see this:
>test me
(Testing.)
>[1] look at bag of buns
Instead rule blocks doing anything with the buns except taking.
>[2] take the bag of buns
Instead rule blocks taking the bag of buns.
>[3] take the lighter fluid
Instead rule blocks taking something.
>[4] take the steak
Instead rule blocks taking something from the grill.
>[5] take the hot dog
Instead rule blocks taking something from the grill.
>[6] get all from the ice chest
bottle of coke: Instead rule blocks taking something from the ice chest.
bottle of juice: Instead rule blocks taking something from the ice chest.
>[7] take the evidence folder
Instead rule blocks taking something in the presence of a character.
In the previous post you saw how you could conditionalize rules by using “when/while/during” clauses. Here I’m doing something very similar. I’m using an “in the presence of” clause and then specifying a particular object that is a character. Maya was declared to be a “woman” and Inform knows that this means Maya is a type of “person” kind and thus it makes sense to speak of being in her presence. In fact, you might also note that I’ve been conditionalizing rules with the clause “which” — as in, “which is on the grill”.
There is another way to conditionalize, which has to do with how often a particular action is repeated. First, comment out the rule about taking the evidence folder (as shown in the source below) and then add the following two rules and update the test:
[Instead of taking the evidence folder in the presence of Maya: say "Instead rule blocks taking something in the presence of a character."] Instead of taking the evidence folder for the third time: say "Instead rule for taking the evidence folder three times." Instead of examining Maya at least twice: say "Instead rule for examining Maya at least twice." Test me with "look at bag of buns / take the bag of buns / take the lighter fluid / take the steak / take the hot dog / get all from the ice chest / take the evidence folder / again / again / look at Maya / again".
A comment to Inform is any source text surrounded by square brackets, [ ]. This can be handy when you want to keep something available but not have it operative.
By the way, can you guess why I had you comment out the original rule?
You might notice in the ‘test me’ that I’m using the action “again”. This is simply a command that Inform lets you type if you want to repeat the last command you just typed.
Here is the output, starting at the relevant lines:
>[7] take the evidence folder Instead rule blocks taking something. >[8] again Instead rule blocks taking something. >[9] again Instead rule for taking the evidence folder three times. >[10] look at maya You see nothing special about Maya. >[11] again Instead rule for examining Maya at least twice. >
So my existing “instead of taking something” rule fires when I try to take the evidence folder. However, notice that on the third try, my “instead of taking the evidence folder for the third time” rule intervenes. That’s again because the latter rule is more specific than the former.
Incidentally, did you guess why I had you comment out the other rule? Try to put it back in (remove the comment brackets) and see what happens. The relevant output in that case is:
>[7] take the evidence folder Instead rule blocks taking something in the presence of a character. >[8] again Instead rule blocks taking something in the presence of a character. >[9] again Instead rule blocks taking something in the presence of a character. >[10] look at maya You see nothing special about Maya. >[11] again Instead rule for examining Maya at least twice. >
Here both the “instead of taking something” and the “instead of taking the evidence folder for the third time” rules are not fired because the “instead of taking the evidence folder in the presence of Maya” is considered much more specific and thus has precedence.
Finally, do note how the examine rule (“instead of examining Maya at least twice”) only fires if the player attempts to look at (or examine) Maya more than once.
Let’s close off all this rule play with one more variation. Add the following rule:
Instead of waiting in the presence of Maya for two to three turns: say "Instead rule for waiting in the presence of Maya."
Then replace the entire ‘test me’ with the following simpler ‘test me’ in place:
Test me with "wait / wait / wait / wait".
Earlier you saw that you can conditionalize based upon something being done for a certain amount of times. What this last part is showing you is that you can also do this based on the number of turns. The output to this test would be:
>test me (Testing.) >[1] wait Time passes. >[2] wait Instead rule for waiting in the presence of Maya. >[3] wait Instead rule for waiting in the presence of Maya. >[4] wait Time passes. >
So you’ve now seen two ways to conditionalize rules based on repeated activities, one involving the number of times something has been done and another involving the number of turns.
As I said earlier, take some time to go through the above and observe what is happening. With all of the examples provided, I have kept a lot of rules in play at once. Make sure you understand why the output you are getting in each case makes sense in the context of the specificity of certain rules in the text. This is a critical area of Inform development. You will spend much of your time writing rules that deal with potential actions by the player. However, those rules may have different contexts depending on what is going on in the game. So you need to make sure that your rules are worded specifically enough with circumstances such that you can achieve the desired effects.
Instead rules are not the only game in town for actions.
Before and After Rules for Actions
To pratice, let’s “reset” our source text. Make sure yours looks like this:
The Learning Lab is a room. The Office is a room. The Office is east of the Learning Lab. A shredder is in the office. A bag of buns is in the Learning Lab. Some lighter fluid is in the Learning Lab. A grill is in the Learning Lab. On the grill are a steak and a hot dog. An ice chest is in the Learning Lab. A bottle of coke is in the ice chest. A bottle of juice is in the ice chest. Maya is a woman in the Learning Lab. An evidence folder is in the Learning Lab.
Now with that done, put the following rules and ‘test me’ command in:
Before taking the lighter fluid: say "Before rule for taking the lighter fluid." After taking the lighter fluid: say "After rule for taking the lighter fluid. (Overrides normal response.)" Test me with "take the lighter fluid".
Here is the output you should see:
>test me (Testing.) >[1] take the lighter fluid Before rule for taking the lighter fluid. After rule for taking the lighter fluid. (Overrides normal response.) >
Here you can see that along with Instead rulebooks, there are Before rulebooks and After rulebooks. They do pretty much what you would expect given their names. Before rules take place before the actual action whereas After rules take place after it. Now, in my say
text above, I call out “overrides normal response” for a reason. When you blocked taking the bag of buns earlier with an Instead rule, the default or normal response was not generated — which was taking the bag of buns. The action was stopped in its tracks, which is the purpose of an Instead rule. Before rules, on the other hand, do not stop an action at all. They simply tell Inform to do something else before it tries the action. So, in this case, the lighter fluid would be taken.
After rules similarly do not stop the action; in fact, they can’t since they take place after the action has occurred. After rules do, however, stop whatever would have happened after the action, assuming Inform does anything after the action. For example, normally after taking an object in the game, Inform will reply with a simple message of “Taken.” This is a generic After rule that is provided for you by Inform. However, you don’t see “Taken” in the output above. That’s because my After rule overrode the normal response since Inform assumed I was providing a response of my own.
To see this, replace the after rule with this one instead:
After taking the lighter fluid: say "After rule for taking the lighter fluid. (Allows normal response.)"; continue the action.
If you run the ‘test me’ with this rule in place, the output is slightly different:
>test me
(Testing.)
>[1] take the lighter fluid
Before rule for taking the lighter fluid.
After rule for taking the lighter fluid. (Allows normal response.)
Taken.
>
Here the phrase ‘continue the action’ supplied with the rule told Inform to not only do what I wanted to do (print my say phrase) but then continue the action as it normally would have. In this case, “as it normally would have” simply means a message indicating to the player that object was taken.
I should note that when you have rules that have more than one phrase after the colon, you’ll generally want to put them on separate lines to make it easier to read. For example:
After taking the lighter fluid:
say "After rule for taking the lighter fluid. (Allows normal response.)";
continue the action.
Inform Tip! Inform cares about indentation, much like Python does. Indentation is a structural element within Inform source text. Further, that indentation needs to be a tab character. So when you see source text like the above, as you type it in, please make sure you indent with a tab.
Again, ‘continue the action’ in the context of an After really just means that the normal Inform report on the result of the action will be generated. The action itself (“taking”) has already taken place so ‘continue the action’ here is somewhat limited. You might wonder if you can do something this with Instead rules. Certainly. For example, set up your rules so they look like this:
Before taking the lighter fluid: say "Before rule for taking the lighter fluid." Instead of taking the lighter fluid: say "Instead rule blocking taking the lighter fluid. (But not actually blocking.)"; continue the action. After taking the lighter fluid: say "After rule for taking the lighter fluid. (Allows normal response.)"; continue the action.
Here I’m using the ‘continue the action’ with the Instead rule as well and, sure enough, if you run this you’ll get this output:
>[1] take the lighter fluid Before rule for taking the lighter fluid. Instead rule blocking taking the lighter fluid. (But not actually blocking.) After rule for taking the lighter fluid. (Allows normal response.) Taken. >
There, even though I used an Instead rule, which normally stops actions, I have changed that behavior and the action has proceeded on, eventually resulting in the lighter fluid being taken.
In fact, as long as we’re subverting Inform’s intentions here, why not go all the way? Before rules don’t stop the action at all. But let’s make that the case anyway. Change your Before rule to look like this:
Before taking the lighter fluid: say "Before rule for taking the lighter fluid. (But it blocks it.)"; stop the action. Instead of taking the lighter fluid: say "Instead rule blocking taking the lighter fluid. (But not actually blocking.)"; continue the action. After taking the lighter fluid: say "After rule for taking the lighter fluid. (Allows normal response.)"; continue the action.
Now your output is:
>test me (Testing.) >[1] take the lighter fluid Before rule for taking the lighter fluid. (But it blocks it.) >
Here I’ve used the ‘stop the action’ phrase to stop any further processing on this action. That means my Instead and After rules don’t even get a crack at the action.
I said above that as long as we’re subverting Inform’s intentions here, and I use that wording deliberately. Using ‘stop the action’ and ‘continue the action’ does allow you to change how the rules work. But you should think carefully about doing so. If you want to stop an action, then do that in Instead (which is what it is for) rather than in Before. If you want to allow an action to continue without interference, don’t use an Instead rule. If you want to override what happens after the action itself has taken place, then put that in an After rule with the understanding that your goal should be to not have Inform report on what it would normally report on.
In other words, use the rules for their intended purpose.
Yet you might feel that having just three “hooks” into the Inform action processing might be too few. For example, what if you want to change some aspect of how an action is carried out but you don’t want to change the fact that the action takes place? What if you want to check some aspect of whether the action should be allowed and then conditionally decide whether the action should happen or not? What if you want something to happen after the action but also want to change how Inform reports about it?
All of that can be handled by Inform, and I’ll cover that in a different post which will look a different set of rulebooks for handling action processing.
For the Testers and Developers …
Continuing my theme of considering what a test language would be like if it was something like Inform, how do the “instead” rules fit into that? Clearly the actions would be the actions that a test is performing, such as interacting with a GUI or submitting requests to a web service and/or API. Would I ever want to subvert test actions by instead rules? Probably not at the action level — but what about at the expression level? Following along with BDD-type solutions like Cucumber or SpecFlow, you can perhaps imagine certain cases where the test, as typed in, says to do one particular high-level action (“navigate to the landing page”) but behind the scenes that gets translated into more specific low-level steps (“go to some url, type in some login information, verify title of page you landed on”).
The Before and After rules are probably much easier to see for a testing solution since they could quite easily fit in with the idea of setup and teardown methods in many test frameworks. Again, though, consider it also from the test and/or requirement expression level. You could have numerous Before and After rules that apply in given context and will only fire if that context is operative. Along the way Instead rules could fire if other circumstances were to manifest.
Thank you for taking the time to do such a long and informative tutorial. Inform is extremely powerful and I appreciate the methodical and slow approach you are taking to the material to digest it.
Thanks for the comment and encouragement. I definitely plan to do more posts in this category. I’m finding Inform is a fascinating system to learn how to describe well. It’s amazingly fun to play around with.
Thank you for this clear and concise explanation. These posts have laid a better foundation for understanding I7 and building and testing games than my haphazard reading of the manual and recipe book.
Question: How does one work with kinds of things, rather than the things themselves? I added this to the code:
A snack is a kind of thing. An apple is a kind of snack. A kiwi is a snack.
The TEST ME for “take apple” and “take kiwi” produced:
You can’t see any such thing.
Context: I built a vending machine that vends snacks, but now I’m having trouble building rules to block giving of disliked snacks to NPCs.
Looking forward to more posts in this series! Thanks again!
Say you have something like this:
In that case, “take apple” will fail because apple has just been said to be a kind of thing. But it’s not an actual instance of a thing in the room.
The command “take kiwi” will work because a kiwi is a particular instance of a snack that exists in the room.