Inform 7 Home Page / Documentation
§20.8. Replacements
Suppose V is a text which varies - perhaps a property of something, or a variable defined everywhere, or a temporary "let"-named value. How do we change its contents? The easiest way is simply to assign text to it. Thus:
let V be "It is now [the time of the day in words]."
And, for instance,
let V be "[V]!"
adds an exclamation mark at the end of V.
Otherwise, it is more useful (also a little faster) to modify V by changing its characters, words and so on. Thus:
replace character number (number) in (text) with (text)
This phrase acts on the named text by placing the given text in place of the Nth character, counting from 1. Example:
let V be "mope";
replace character number 3 in V with "lecul";
say V;
says "molecule".
replace word number (number) in (text) with (text)
This phrase acts on the named text by placing the given text in place of the Nth word, counting from 1, and dividing words at spacing or punctuation. Example:
let V be "Does the well run dry?";
replace word number 3 in V with "jogger";
say V;
says "Does the jogger run dry?".
replace punctuated word number (number) in (text) with (text)
This phrase acts on the named text by placing the given text in place of the Nth word, counting from 1, and dividing words at spacing, counting punctuation runs as words in their own right. Example:
let V be "Frankly, yes, I agree.";
replace punctuated word number 2 in V with ":";
say V;
says "Frankly: yes, I agree.".
replace unpunctuated word number (number) in (text) with (text)
This phrase acts on the named text by placing the given text in place of the Nth word, counting from 1, and dividing words at spacing, counting punctuation as part of a word just as if it were lettering. Example:
let V be "Frankly, yes, I agree.";
replace unpunctuated word number 2 in V with "of course";
say V;
says "Frankly, of course I agree.".
replace line number (number) in (text) with (text)
This phrase acts on the named text by placing the given text in place of the Nth line, counting from 1. Lines are divided by paragraph or line breaks.
replace paragraph number (number) in (text) with (text)
This phrase acts on the named text by placing the given text in place of the Nth paragraph, counting from 1.
Last, but not least, we can replace text wherever it occurs:
replace the text (text) in (text) with (text)
This phrase acts on the named text by searching and replacing, as many non-overlapping times as possible. Example:
replace the text "a" in V with "z"
changes every lower-case "a" to "z": the same thing done with the "case insensitively" option would change each "a" or "A" to "z".
All very well for letters, but it can be unfortunate to try
replace the text "Bob" in V with "Robert"
if V happens to contain, say "The Olympic Bobsleigh Team": it would become "The Olympic Robertsleigh Team". What we want, of course, is for Bob to become Robert only when it's a whole word. We can get that with:
replace the word (text) in (text) with (text)
This phrase acts on the named text by searching and replacing, as many non-overlapping times as possible, where the search text must occur as a whole word. Example:
replace the word "Bob" in V with "Robert"
changes "Bob got on the Bobsleigh" to "Robert got on the Bobsleigh".
replace the punctuated word (text) in (text) with (text)
This phrase acts on the named text by searching and replacing, as many non-overlapping times as possible, where the search text must occur as a whole word or run of punctuation.
But these are all just special cases of the grand-daddy of all replacement phrases:
replace the regular expression (text) in (text) with (text)
This phrase acts on the named text by matching the regular expression and replacing anything which fits it, as many non-overlapping times as possible. Example:
replace the regular expression "\d+" in V with "..."
changes "The Battle of Waterloo, 1815, rivalled Trafalgar, 1805" to "The Battle of Waterloo, ..., rivalled Trafalgar, ...". The "case insensitively" causes lower and upper case letters to be treated as if the same letter.
When replacing a regular expression, the replacement text also has a few special meanings (though, thankfully, many fewer than for the expression itself). Once again "\n" and "\t" can be used for line break and tab characters, and "\\" must be used for an actual backslash. But, very usefully, "\1" to "\9" expand as the contents of groups numbered 1 to 9, and "\0" to the exact text matched. So:
replace the regular expression "\d+" in V with "roughly \0"
adds the word "roughly" in front of any run of digits in V, because \0 becomes in turn whichever run of digits matched. And
replace the regular expression "(\w+) (.*)" in V with "\2, \1"
performs the transformation "Frank Booth" to "Booth, Frank".
Finally, prefixing the number by "l" or "u" forces the text it represents into lower or upper case, respectively. For instance:
replace the regular expression "\b(\w)(\w*)" in X with "\u1\l2";
changes the casing of X to "title casing", where each individual word is capitalised. (This is a little slow on large texts, since so many matches and replacements are made: it's more efficient to use the official phrases for changing case.)
In this example, we want the names of rooms to be asterisked out if the player wanders around without the benefit of a candle. We can do this by treating the room names as text, then replacing every letter:
"Blackout"
Tiny Room is a dark room. Absurdly Long-Named Room is a dark room. It is west of Tiny Room.
The Candle Factory is north of Tiny Room. It contains a beeswax candle. The beeswax candle is lit.
Rule for printing the name of a dark room:
let N be "[location]";
replace the regular expression "\w" in N with "*";
say "[N]".
Test me with "w / look / e / n / get candle / s / w".
Notice that the hyphen in the Absurdly Long-Named Room does not get replaced. We could replace even that, if we liked, with
replace the regular expression "\S" in N with "*";
which would catch every character that is not a space.
|
ExampleFido
A dog the player can name and un-name at will.
|
|
Suppose we'd like to have a dog which the player is allowed to name himself. We'd like to deal correctly with both
>name the dog fido
and
>name the dog "fido"
so we'll also need to strip quotation marks out of the command. We can do this as follows:
"Fido"
The Back Yard is a room.
A dog is an animal in Back Yard. The dog has some text called the nickname. The nickname of the dog is "nothing". Understand the nickname property as describing the dog.
Rule for printing the name of the dog when the nickname of the dog is not "nothing":
say "[nickname of the dog]"
Naming it with is an action applying to one thing and one topic. Understand "name [something] [text]" as naming it with. Check naming it with: say "You can't name that."
Instead of naming the dog with "nothing":
now the nickname of the dog is "nothing";
now the dog is improper-named;
say "You revoke your choice of dog-name."
Instead of naming the dog with something:
let N be "[the topic understood]";
replace the text "'" in N with "";
now the nickname of the dog is "[N]";
now the dog is proper-named;
say "The dog is now known as [nickname of the dog]."
Test me with "name the dog Fido / name the dog Lawrence / look / x lawrence / name Lawrence nothing / look / x lawrence".
For the sake of argument, suppose we want to parrot back all the player's commands in pig Latin:
"Igpay Atinlay"
Armfay is a room.
After reading a command:
let N be "[the player's command]";
replace the regular expression "\b(<aeiou>+)(\w*)" in N with "\1\2ay";
replace the regular expression "\b(<bcdfghjklmnpqrstvwxz>+)(\w*)" in N with "\2\1ay";
say "[N][paragraph break]";
reject the player's command.
Test me with "nix on the stupid".
Suppose we have an unhappily mutated fish that the player can refer to by any of a number of species names, or any word followed by -fish. We want to reject these commands, but preserve a memory of what the player last tried to call the thing:
"Mr. Burns' Repast"
Wharf is a room.
There is an unknown fish in the Wharf. The unknown fish has some a text called the supposed name. The description of the unknown fish is "The victim of heavy mutagens, this thing is not really recognizable as any species you know.".
Fish variety is a kind of value. The fish varieties are salmon, albacore, mackerel.
Rule for printing the name of the unknown fish:
if the supposed name of the unknown fish is "", say the printed name of the unknown fish;
otherwise say the supposed name of the unknown fish.
After reading a command:
if the unknown fish is visible and player's command matches the regular expression "\b\w+fish":
let N be "[the player's command]";
replace the regular expression ".*(?=\b\w+fish)" in N with "";
now N is "[N](?)";
now the supposed name of the unknown fish is N;
respond with doubt;
reject the player's command;
otherwise if the unknown fish is visible and the player's command includes "[fish variety]":
now supposed name of the fish is "[fish variety understood](?)";
respond with doubt;
reject the player's command.
To respond with doubt:
say "You're not [italic type]sure[roman type] you're seeing any such thing."
Test me with "get swordfish / look / touch monkfish / look / listen to tunafish / x fish / x salmon / look".
|
ExampleNorthstar
Making Inform understand ASK JOSH TO TAKE INVENTORY as JOSH, TAKE INVENTORY. This requires us to use a regular expression on the player's command, replacing some of the content.
|
|
Most of the time, Inform understands commands to other characters when they take the form "JOSH, TAKE INVENTORY" or "JOAN, WEAR THE ARMOR". But novice players might also try commands of the form ASK JOSH TO TAKE INVENTORY or ORDER JOAN TO WEAR THE ARMOR.
The easiest way to make Inform understand such commands is to meddle directly with the player's command, changing it into the format that the game will understand, as here:
"Northstar"
The Northstar Cafe is a room. "The Northstar is crammed with its usual brunch crowd, and you were lucky to get a table at all. You are now awaiting the arrival of your ricotta pancakes."
Josh is a man in The Northstar Cafe. "Josh is on his way past your table." The description of Josh is "He is a waiter here, but you also know him socially, so he tends to be more chatty than the other waiters." A persuasion rule: persuasion succeeds.
After reading a command:
let N be "[the player's command]";
replace the regular expression "\b(ask|tell|order) (.+?) to (.+)" in N with "\2, \3";
change the text of the player's command to N.
Test me with "ask Josh to take inventory / tell Josh to take inventory / order Josh to take inventory".
Note that we have to copy N back explicitly to replace the player's command.
Novice players of interactive fiction, unfamiliar with its conventions, will often try to add extra phrases to a command that the game cannot properly parse: HIT DOOR WITH FIST, for instance, instead of HIT DOOR.
While we can deal with some of these instances by expanding our range of actions, at some point it becomes impossible to account for all the possible prepositional phrases that the player might want to tack on. So what do we do if we want to handle those appended bits of text sensibly?
We could go through and remove any piece of text containing "with ..." from the end of a player's command; the problem with that is that it overzealously lops off the ends of valid commands like UNLOCK DOOR WITH KEY, as well. So clearly we don't want to do this as part of the "After reading a command..." stage.
A better time to cut off the offending text is right before issuing a parser error. At that point, Inform has already determined that it definitely cannot parse the instruction as given, so we know that there's something wrong with it.
The next problem, though, is that after we've edited the player's text we want to feed the corrected version back to Inform and try once more to interpret it.
This is where we have a valid reason to write a new "rule for reading a command". We will tell Inform that when we have just corrected the player's input to something new, it should not ask for a new command (by printing a prompt and waiting for another line of input); it should instead paste our stored corrected command back into "the player's command" and proceed as though that new text had just been typed.
Thanks to John Clemens for the specifics of the implementation.
"Cave-troll" by JDC
Section 1 - The Mechanism
The last command is a text that varies.
The parser error flag is a truth state that varies. The parser error flag is false.
Rule for printing a parser error when the latest parser error is the only understood as far as error and the player's command matches the text "with":
now the last command is the player's command;
now the parser error flag is true;
let n be "[the player's command]";
replace the regular expression ".* with (.*)" in n with "with \1";
say "(ignoring the unnecessary words '[n]')[line break]";
replace the regular expression "with .*" in the last command with "".
Rule for reading a command when the parser error flag is true:
now the parser error flag is false;
change the text of the player's command to the last command.
Section 2 - The Scenario
The Cave is a room.
The troll is a man in the cave.
The player carries a sword.
The chest is a locked lockable container in the cave.
Test me with "attack troll with sword / unlock chest with sword / attack troll as a test".
A caveat about using this method in a larger game: "parser error flag" will not automatically control the behavior of any rules we might have written for Before reading a command... or After reading a command..., so they may now fire at inappropriate times. It is a good idea to check for parser error flag in those rules as well.