Thursday, July 28, 2016

Instead of Instead (Inform 7)

Inform 7 allows an author to accomplish things in many different ways.  It's easy enough to make a dungeon full of rooms and doors and containers and treasures, but if we go much further beyond that, we have to work with creating rules.

All actions in Inform have rulebooks which roughly correspond to phases of time it takes to perform the action.  The major three are

  • CHECK - This is where Inform decides if an action can be accomplished.
  • CARRY OUT - This is where the actual mechanics of the action take place.
  • REPORT - After everything is done, this is where the parser can print a message confirming what has taken place.
Three other rulebooks are built-in:
  • BEFORE
  • AFTER 
  • INSTEAD

BEFORE and AFTER slot where you'd expect, but INSTEAD is probably the most powerful and most mis-used due to that inherent power.  New authors often find the check/carry out/report sequence confusing at first, and will latch onto INSTEAD like Dr. Who's sonic screwdriver to accomplish everything. Why learn a whole handful of rulebooks when you only need one?

The reason is INSTEAD essentially is an override that relinquishes the parsing of a command over to the author. When one tells Inform "Instead of doing this action..." the parser won't bother checking anything and will use the author's specific rule.  Also INSTEAD overrides every other rule including other INSTEAD rules, defaulting to the most specific for a situation.

Suppose all flowers cause wooziness, but certain types have added effects:
Garden is a room.
A flower is a kind of edible thing.  A rose is a kind of flower.  A daisy is a kind of flower.  A lily is a kind of flower.  Understand "flower" as a flower.
There are four roses in garden.  There are six daisies in garden.  There are three lilies in garden. 
Instead of eating a flower: say "It tastes bitter and you feel woozy." 
Instead of eating a rose: say "The rosy scent tickles your nose."
Instead of eating a daisy: say "You feel light-headed."


Garden
You can see four roses, six daisies and three lilies here.
>eat flower
Which do you mean, a rose, a daisy or a lily?
>lily
It tastes bitter and you feel woozy.
>eat rose
The rosy scent tickles your nose.
>eat daisy
You feel light-headed.
>l
Garden
You can see four roses, six daisies and three lilies here.


You'll notice we aren't getting the default message for "flowers" in every case because the more specific Instead rule for roses and daisies override since they are more specific. Also problematic is that even though flowers are declared "edible" they aren't disappearing like standard edible objects because Inform has relinquished control to the author, and our INSTEAD rule doesn't handle the behavior for edible objects. So lets make this change:
Instead of eating a flower:
   say "It tastes bitter and you feel woozy.";
   remove the noun from play.
Garden
You can see four roses, six daisies and three lilies here.
>eat lily
It tastes bitter and you feel woozy.
>look
Garden
You can see four roses, six daisies and two lilies here.
>eat rose
The rosy scent tickles your nose.
>look
Garden
You can see four roses, six daisies and two lilies here.
So our INSTEAD with included eating rules is only working on lilies, which don't have more specific Instead rules affecting them. An enterprising new author usually here will go "No problem, I can write this into every instead rule."  But over the course of even a moderate-sized game this will grow tedious--especially if we want to write other rules that affect flowers, and we'll have to keep up and update every single rule:
Sickness is a number that varies.
Instead of eating a flower:
say "It tastes bitter and makes you feel woozy.";
increase sickness by one;
if sickness is greater than one:
say "You don't feel so good after that meal.";
if sickness is greater than two:
end the story saying "Flowers, while beautiful, have their downsides.  You have been poisoned!"

Instead of eating a rose:
say "It tastes bitter and makes you feel woozy.";
say "The rosy scent tickles your nose.";
increase sickness by one;
if sickness is greater than one:
say "You don't feel so good after that meal.";
if sickness is greater than two:
end the story saying "Flowers, while beautiful, have their downsides.  You have been poisoned!"

Instead of eating a daisy:
say "It tastes bitter and makes you feel woozy.";
say "You feel light headed.";
increase sickness by one;
if sickness is greater than one:
say "You don't feel so good after that meal.";
if sickness is greater than two:
end the story saying "Flowers, while beautiful, have their downsides.  You have been poisoned!"

The author is doing more work than they need to bypassing the parser with INSTEAD. Say we want flowers to eventually poison the player.  Every rule would need to do the work of the parser. While the before/check/carry out/report/after sequence of rulebooks seems daunting, it actually makes life easier:
Garden is a room.
A flower is a kind of edible thing.  A rose is a kind of flower.  A daisy is a kind of flower.  A lily is a kind of flower.  Understand "flower" as a flower. 
Sickness is a number that varies. 
There are four roses in garden.  There are six daisies in garden.  There are three lilies in garden.
After eating a flower: [AFTER will override the normal "You eat the thing. Not bad." report message for edible things]
say "It tastes bitter and makes you feel woozy."; 
First after eating a rose: ["First after" puts this message at the beginning of the After rulebook]
say "The rosy scent tickles your nose.";
continue the action. [So both messages will print as an AFTER will normally stop] 
First after eating a daisy:
say "You feel light-headed.";
continue the action. 
[It's good to name your rules]
Carry out eating a flower (this is the flowers are poisonous rule):
increase sickness by one.
 [An author needn't pack everything into a single rule.]
Carry out eating a flower (this is the consequences of eating flowers rule):
if sickness is greater than one:
say "You don't feel so good after that meal...";
if sickness is greater than two:
end the story saying "Flowers, while beautiful, have their downsides.  You have been poisoned!";
Garden
You can see four roses, six daisies and three lilies here.
>eat lily
(first taking the lily)
It tastes bitter and makes you feel woozy.
>l
Garden
You can see four roses, six daisies and two lilies here.
>eat daisy
(first taking the daisy)
You don't feel so good after that meal...
You feel light-headed.
It tastes bitter and makes you feel woozy.
>look
Garden
You can see four roses, five daisies and two lilies here.
>eat rose
(first taking the rose)
You don't feel so good after that meal...


    *** Flowers, while beautiful, have their downsides.  You have been poisoned! ***
 
INSTEAD rules are very powerful and good for limiting the player from doing foolish things or tying up loose ends of standard responses:
Instead of jumping, say "Out, damned spot!"
Instead of smelling, say "Your sinuses have been acting up, so that sense is currently nonfunctional for the time being." 

And even this type of power can be safely folded into the CHECK rulebook for the specific action:
Check eating a flower (this is the great power equals great responsibility rule): say "You shouldn't be eating flowers. Who knows if they are poisonous?" instead
-----
Thanks to the tireless denizens of the IntFiction.org forums where great advice for programming IF in Inform 7 is best obtained.  I have a website http://hanonondricek.wixsite.com/pyramidif









2 comments:

  1. This is an excellent explanation

    BUT

    every time you say "bypassing the parser" I think it should be "bypassing action-processing" or something like that. By the time all this stuff is happening, the parser has done the work of converting a command into an action. It's the general behavior of the action that's getting short-circuited.

    There's basically two things that I've found "Instead" rules useful for. One is redirecting actions completely--things like "Instead of pushing the button, try switching the kettle on." Another is when I have to do something pretty complicated that involves printing text and carrying out effects, and they can't easily be broken up into different rules (for instance, if the action changes the printed name of the noun and you need to refer to the old name). But looking at my code most of the "Instead" rules are just things I coded in a hurry.

    ReplyDelete
  2. Thanks for the correction (and sorry I missed this reply for so long), your advice is very sound. Pure INSTEAD rules do have a place but it is very easy for beginners (myself included) to overuse them and end up doing more coding than is required when the action-processing rules are there to help authors along.

    ReplyDelete