INFORM VARYING STRINGS Sean Barrett (This document is formatted for monospaced fonts, 80 characters display) CONTENTS: Contents General description Usage constraints Functional description and syntax Limitations Sample uses Don't do this 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. A given string can be chosen in one of several ways: 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 substring of a given string can contain one of these effects, and such substrings can actually be nested. USAGE CONSTRAINTS Due to issues with the way the interpreter works and the scope of changes that would be necessary, varying strings can only be used in strings that are immediately printed, that is, strings of the following two kinds: [; print "^He doesn't seem to cotton to you.^"; ]; and [; "You can't go that way."; ]; 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. (This print-only limitation is their only real deviation from normal strings--beyond their "varyingness".) FUNCTIONAL DESCRIPTION AND SYNTAX INDICATING VARYING SUBSTRINGS A varying substring is delimited (marked off) by wrapping it within the characters '{' and '}'. (I am open to discussion of a better choice of characters and for an escaping mechanism to allow games to still display the chosen characters.) 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.}"; ] 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 selection 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.) SELECTION METHOD SPECIFICATION The choice of which selection method (random, constrained random, sequence, or cyclic sequence) to use for selecting alternatives is determined by a special 'selection 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 lot more memory than their brethren dumb strings; however, a varying string only consumes more memory if it actually varies, that is, if it contains a '{' in it. I don't know Inform or the z-machine well enough to know exactly how much more it uses, although if someone wanted to figure it out, there is no doubt a straightforward formula. In general, if you're concerned about space, don't use varying strings. It's possible that somebody more knowledgeable could rewrite varying strings to be more memory efficient, and that moreover such an implementation would be as memory efficient as manually coding the effect in Inform source would be. I imagine that my current implementation is no more than 2x the memory *overhead* of the maximally efficient approach, and for strings of any reasonable size is probably relatively small regardless. Performance Overhead Selection from the n variants uses a chain of 'if's, which has similar performance to Inform's switch() statement. A faster implementation would use array lookups, but then it wouldn't be possible to support nested varying strings. SAMPLE APPLICATIONS 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.^|||||||||||||}"; ]; DON'T DO THIS A word of advice: there are some situations in which it's best to stick with a single, consistent, unvarying response. For example, if the player drops an object, your game should print "Dropped." or "Ok." or some specific message of that sort. It should NOT randomly pick between three or four different variations, because this might mislead the player into thinking that perhaps something different is going on in each of the cases. In other words, consistent responses are an important meta-cue to the player about the degree of simulation vs. emulation in the system. (Examples of simulation in typical IF: light/dark, objects inside objects, travelling from place to place, "have you been here before"-ness.) For an extreme example of what not to do, IMHO, look at the commercial RPG "Betrayal at Krondor". At key moments of simulation--when the player's party is about to be attacked, or after a victory when the player loots corpses--a long bit of narrative text is spewed out that implies the scenario, using several descriptive sentences. While there is aesthetic value to these bits of narrative (at least the first or second time you read them), they create an unfortunate situation for the poor player. Each time such an event occurs, the player reads a few words of text, and then thinks, "Oh, it's another encounter". Moreover, for quite some number of repetitions the player will read (or at least skim) all the way through the text, uncertain about whether perhaps the text varies from previous occasions. THE END