INFORM VARYING STRINGS v1.0 / Sean Barrett Document history: v0.1 99-11-07 v0.2 99-12-29 v1.0 01-09-13 CONTENTS: Contents Brief Learn-By-Cut-and-Paste Examples General description Usage constraints Functional description and syntax Limitations Sample uses Some real-world examples BRIEF LEARN-BY-CUT-AND-PASTE EXAMPLES If you want to read a detailed explanation, skip this section and start reading from "General Description". For those who prefer to just type and experiment and don't want to read a long detailed document and just want to learn from examples, the following fragments of code are somewhat self-explanatory. "The coin comes up {%heads|tails}."; "Bob says, ~{!Uh huh|Yeah|Sure|Ok|Cool}.~"; ! doesn't repeat last print_ret "{What an ugly statue. You can't believe anything could be so ugly.|You take another look, just to be sure. It sure is ugly.|Despite what you might have thought, the statue remains ugly.}"; "Bob clicks the one-digit tally counter and reads off the result to you. ~{&One|Two|Three|Four|Five|Six|Seven|Eight|Nine|Zero}.~"; print "^{!Two large cows|Several rubber ducks|Hoardes of angry cartoon characters} {!march|stomp|stride|charge} {%down {!the hill|the hillside|from the trees|through the trees}|up {!from the river|the hill|the street}} and then, after {!pausing to confer with each other|engaging in a food fight with {!rotten fruit|whipped cream|giant hunks of beef}|licking the streetlight posts|heckling the mime|staring vacuously at the moon}, {!disappear|vanish|slip away} {!over the hill|{%through the trees|amongst the trees}|into the river}.^"; See also "REAL-WORLD EXAMPLES" for more examples, and the section "SIMPLE SAMPLES" for a demonstration of what exactly some simple examples output. GENERAL DESCRIPTION "Varying strings" provide a mechanism by which printed strings can be made to vary with no programming effort on the part of the author. Each string that prints is chosen from several options; the choice can be at random, with constrained randomness, in sequential order repeating the last, or in sequential order wrapping around to the beginning. The technique can be applied to a substring of a printed string, rather than the entire string; more than one region of a given string can independently contain one of these effects, and such substrings can actually be nested. USAGE CONSTRAINTS Varying strings can only be used in strings that are immediately printed, that is, strings of the following kinds: [; "You can't go that way."; ]; and [; print_ret "You can't go that way."; ]; and [; print "^Bob looks around nervously.^"; ]; A string which is stored somewhere and later printed cannot be a varying string; for example ... description "This is an object whose description can't use varying strings.", ... versus ... description [; "This is an object whose description can use varying strings."; ], ... Note that this is the same as the general limitation on putting non-strings inside print statements, e.g. { print "foo ", x, " bar"; } is legal, but { y = "foo ", x, " bar"; } is not--the sequence is printable, but it is not a string. Varying strings are not really strings; they are simply complex print statements, but they LOOK (syntactically) like strings. FUNCTIONAL DESCRIPTION AND SYNTAX INDICATING VARYING SUBSTRINGS A varying substring is delimited (marked off) by wrapping it within the characters '{' and '}'. This simply indicates to the Inform compiler which parts of the string undergo varying-string processing. For example: [; "This {varying string} contains two {delimited} substrings."; ] [; "{The entire string here is a single varying substring.}"; ] Note that in the second example above, there is still an implicit newline after the string, which is not part of the varying substring. The following are malformed varying strings, and will produce syntax errors: [; "an invalid }string} thing"; ] [; "{another invalid string"; ] INDICATING ALTERNATIVES Each varying substring consists of one or more "alternative" strings which might be printed. These are delimited from each other using the character '|'. '|' appears *between* the alternatives, and '{' and '}' appear *around* the alternatives. [; "{One|Two|Three|Four}"; ] [; "This substring has {two|2} varying {substrings|thingies}."; ] It is legal to have an "empty" alternative (or more than one): [; "{One|Two||Four}"; ] ! four alternatives [; "You hear {loud |strange |}noises."; ] ! three alternatives [; "{||||}"; ] ! five alternatives PRINTING BEHAVIOR When printed, a varying substring displays exactly one of its alternatives. A varying substring with only one alternative will always print that substring, and will behave no differently than a non-varying-string--that is, it will print exactly as if the '{' and '}' characters were not present. (See, for example, the first two examples in "indicating varying substrings".) There are four possible variation methods which determine which alternative is printed. The mechanism for specifying a method is discussed in the next section. RANDOM (example, N = 3: 23233313122) one of the N alternatives is chosen at random CONSTRAINED RANDOM (example, N = 3: 21312321323) one of the N alternatives is chosen at random, except that it will never print the same alternative twice in a row; that is, it actually chooses from the (N-1) alternatives discounting the one chosen the last time this substring was printed. SEQUENCE (example, N = 3: 12333333333) each of the N alternatives is printed successively; first the first, then the second, then the third. On print attempts after the Nth, the Nth is printed. CYCLIC SEQUENCE (example, N = 3: 12312312312) each of the N alternatives is printed successively; the first, then the second, then the third. On the N+1'th print of the substring, the first alternative is printed again; on the N+2'th, the second alternative, etc. Note that while the pure "RANDOM" approach can be straightforwardly implemented in regular Inform, the other three approaches are clumsy, as they require additional state--e.g. a variable or attribute. The point of varying strings is to make these other techniques trivial to code, so that authors will feel free to make use of them all the time. (There is a price, of course--memory, including some dynamic memory.) VARIATION METHOD SPECIFICATION The choice of which variation method (random, constrained random, sequence, or cyclic sequence) to use for selecting alternatives is determined by a special 'variation code' character which appears at the beginning of the varying substring, immediately after the '{' character. METHOD CHARACTER EXAMPLE sequence [none] "This is the {first|second|last} variant."; random % "The coin comes up {%heads|tails}."; constrained random ! "I didn't say ~{!foo|bar|baz}~ last time."; cyclic sequence & "I've counted an {&odd|even} number."; (Note to C programmers: the set of characters used were chosen to map some familiar C idioms as mnemonic devices: '|' for 'or', of course, but also: (rand() % n) for random selection, ((x+1)&7) for wrapping power-of-two sequences, and '!' for 'not' as in "not the same as last time".) SIMPLE SAMPLES sequence "This is the {first|second|last} variant."; This is the first variant. This is the second variant. This is the last variant. This is the last variant. This is the last variant. This is the last variant. cyclic sequence "I've counted an {&odd|even} number."; I've counted an odd number. I've counted an even number. I've counted an odd number. I've counted an even number. I've counted an odd number. I've counted an even number. random "The coin comes up {%heads|tails}."; The coin comes up tails. The coin comes up heads. The coin comes up heads. The coin comes up tails. The coin comes up tails. The coin comes up tails. The coin comes up heads. constrained random "I didn't say ~{!foo|bar|baz}~ last time."; I didn't say "bar" last time. I didn't say "foo" last time. I didn't say "bar" last time. I didn't say "baz" last time. I didn't say "foo" last time. I didn't say "bar" last time. NESTING Any alternative of a substring may itself contain varying substrings. If and only if that alternative is printed are its varying substrings processed. Thus, if an alternative is not selected, then its substrings do not update their sequential sequence etc. A silly nesting example: "{&one|{%two|2}|three|four|five|{!six|6|half-dozen}}" This means: Cycle through sequentially the six substrings "one" "{%two|2}" "three" "four" "five" "{!six|6|half-dozen}" Each time the second string is selected, it picks randomly between the substrings "two" or "2". Each time the sixth substring is selected, it picks randomly amongst the three strings "six", "6", and "half-dozen", but never picks the same one of those three as the previous time it printed this substring (which was six iterations before). Nested substrings can become hard to read due to the high density of symbols, and unlike traditional programming languages, it is not possible to introduce extra whitespace to make it easier to read, since whitespace in strings is meaningful. You don't have to make use of nested strings, however, and they become easier to parse visually as you get used to them. Note that because substrings are only evaluated if they're selected, you can't rely on the sorts of counting behaviors demonstrated in the previous set of examples. Consider "I've counted {%an {&odd|even} number of times|{once|twice|a bunch of times}}."; Suppose the random selection between the two main substrings is: "121121221"; then this will display: I've counted an odd number of times. I've counted once. I've counted an even number of times. I've counted an odd number of times. I've counted twice. I've counted an even number of times. I've counted a bunch of times. I've counted a bunch of times. I've counted an odd number of times. LIMITATIONS SERIES PRINTING The following is a valid Inform varying string usage, which contains two varying strings and an embedded function call. [; "I don't {%like|care for} ", (the) compiler_extension, " {%very much|a lot|}."; ]; Given the appropriate object, this might print: I don't like varying strings very much. I don't care for varying strings very much. I don't care for varying strings a lot. I don't care for varying strings very much. I don't like varying strings very much. On the other hand, this is not a valid Inform varying string use: [; "{%I hate ", (the) compiler_extension, "very much.| I hate what you've done.}"; ]; Unfortunately, this is invalid, because varying strings require that each string, that is each block of text contained in double quotes, be a valid varying string. The first string contains a '{' without a matching '}'. I would like to remove this limitation, but it would require a more thorough understanding of the Inform compiler than I possess, and would probably be a lot more work. The existing implementation lies just at the boundary between being a real implementation and being a hack--the scope of the implementation is a hack, but the implementation is reasonably clean. HIDDEN STATE The "state" of the alternative selection is hidden away from your code; the whole point of varying strings is to do all the state handling for you. As a result, though, there is no way for any other code to depend on the state of what was printed. Varying strings must be purely decorative. "The coin comes up {%heads|tails}." is an appropriate varying string for flipping a coin in a game in which flipping a coin has no effect. But if it should have any effect, you will want to explicitly compute a random number to flip the coin. This is true even if the effect is just to change another purely decorative print, for example, if you want to be able to examine the coin and see which way it came up last time it was flipped; varying strings provide no way to link together their results to address this. This severe limitation is mandatory; without this limitation, varying strings would just be yet another programming language, with all the complexity and inconvenience therein. You'd have to provide names for the states; you'd have to distinguish declarations from usages to avoid dumb bugs; you'd want more syntax to allow comparisons and ability to remap from one range to another range, etc. If you want to do this sort of thing, that's what the Inform language proper is there for. Memory Usage Varying strings probably consume a fair bit more memory than their brethren dumb strings; but only if it actually varies (i.e. contains a '{'). For reasonably-sized strings, the extra strings you write probably take up more space than the actual overhead of the code for handling variations. Performance Overhead Selection from the n variants uses a chain of 'if's, which has similar performance to Inform's switch() statement. SAMPLE USES Some subtle variations: An initial sequence that never repeats, followed by a repeating sequence: "{Ready|Get set|Go|{&One|Two|Three}}"; An initial sequence that never repeats, followed by a random choice: "{Ready|Get set|Go|{!Foo|Bar|Baz}}"; Varying strings were implemented because the author wanted it to be easy to give NPCs more varied responses. The author wanted two particular selection methods: sequential and random; while implementing varying strings, the other two were reasonably easy to add anyway. Avoiding repetitive conversations: "{Q says, ~That 'watch' is an exquisitely compact timed explosive, 007. Turn the minute hand to the number of seconds you want, the hour hand to the number of minutes, shake it, push the alarm button, and it's activated. Push the button twice rapidly to deactivate it. Once it's activated, changing the time won't affect the actual delay 'til it goes off.~|Q says, ~Do pay attention, 007. The minute hand is seconds, the hour hand is minutes; shake the watch and push the button to arm it; push the button twice to disarm it.~|{|An exasperated }Q explains {|yet }again how the watch is a timed explosive, that minutes and hours on the watch represent seconds and minutes until it goes off, that shaking it and pushing the button activates it, and that pushing the button twice in a row deactivates it.}"; Random atmospherics (but a rather memory-wasteful approach): each_turn [; print "{!^A bird flies by high overhead, heading south.^|^A dog barks somewhere in the subdivision to the west.^|||||||||||||}"; ]; REAL-WORLD EXAMPLES The following examples are all taken from my rather silly game "Fit For a Queen" which was written for Emily Short's "Walkthrough Comp", and unlike the other examples in this document, have actually been compiled and tested. The description of the "room" you start in: description [; print "{You are playing a computerized ~text adventure~, or work of ~Interative Fiction~, on a computer. You're currently pretty stumped by this game, ~A Storm Brought About By A Moth's Flapping Wing~.^^You type 'LOOK' yet again.^^|}"; if (game has general) "You're finally unstuck, and ready to move on!"; "{You bring up the current room's description. It tells you what the room looks like, what exits there are, and about the various objects (both manipulable and decorative) to be found there|You study the room description{| again| again, but {|still }do not find anything to help you think of a solution}}."; ], The result of turning the smooth duck if it's closed: give self general; "{You attempt to turn the giant duck freshener statue, and it rotates smoothly around one corner, revealing a steep passage descending down into the earth.|You rotate the duck freshener to reveal the secret passage.}"; This random atmospheric makes sure that one of the options ("drops his tray...") will only ever appear once, by providing a nested alternative ("stops to study...") if it's chosen again: if (random(70) < 20) "^Phillipe {!walks lackadaisically to a table.|glares at you as he walks past.|{drops his tray, creating a horrible mess.|stops to study his fingernails.}|gets in a staring contest with one of the robots.|pretends he doesn't notice a robot signalling him.}"; THE END