From: Glenn Hutchings Date: Sat, 16 Apr 2016 20:14:06 +0000 (+0100) Subject: Add chapter 8. X-Git-Url: https://jxself.org/git/?a=commitdiff_plain;h=21a2902d175911ef3650ed613b580ffdc7e58754;p=ibg.git Add chapter 8. --- diff --git a/chapters/08.rst b/chapters/08.rst new file mode 100644 index 0000000..cf3f53a --- /dev/null +++ b/chapters/08.rst @@ -0,0 +1,592 @@ +============================ + William Tell: in his prime +============================ + +.. highlight:: inform6 + +.. epigraph:: + + | *O was an oyster girl, and went about town;* + | *P was a parson, and wore a black gown.* + +.. only:: html + + .. image:: /images/picO.png + :align: left + +.. raw:: latex + + \dropcap{o} + +ur game's action nears its climax in the town's central square. In this +chapter we define the square's constituent rooms and deal with Wilhelm's +approach to the hat on the pole -- does he salute it, or does he remain +proudly defiant? + +The south side of the square +============================ + +The town square, notionally one enormous open space, is represented by +three rooms. Here's the south side:: + + Room south_square "South side of the square" + with description + "The narrow street to the south has opened onto the town square, + and resumes at the far side of this cobbled meeting place. + To continue along the street towards your destination -- + Johansson's tannery -- you must walk north across the square, + in the middle of which you see Gessler's hat set on that + loathsome pole. If you go on, there's no way you can avoid + passing it. Imperial soldiers jostle rudely through the throng, + pushing, kicking and swearing loudly.", + n_to mid_square, + s_to below_square; + + Prop "hat on a pole" + with name 'hat' 'pole', + before [; + default: + print_ret "You're too far away at the moment."; + ], + found_in south_square north_square; + + Prop "Gessler's soldiers" + with name 'soldier' 'soldiers' 'guard' 'guards', + description "They're uncouth, violent men, not from around here.", + before [; + FireAt: + print_ret "You're outnumbered many times."; + Talk: + print_ret "Such scum are beneath your contempt."; + ], + found_in south_square mid_square north_square marketplace, + has animate pluralname proper; + +It's all pretty standard stuff: just a ``Room`` and two ``Prop``\s. The +"real" pole object is located in the ``mid_square`` room, which means that +players can't EXAMINE it from this room (technically, it's "not in scope"). +However, since we're pretending that Wilhelm can see the whole of the +square from where he's standing, we need to provide a dummy hat on a pole, +``found_in`` both this room and the north side of the square, even if it's +"too far away" for a detailed description. + +In fact, it's "too far away" for anything. We've replaced the standard +``before`` action for the ``Prop`` class (which permits ``Examine``, but +rejects other actions with "You don't need to worry about...") with one +rejecting *all* actions. Since Wilhelm's hatred of the vogt's activities +is central to our plot, a message saying "You don't need to worry about the +hat" would be unacceptably misleading. + +The obnoxious soldiers are also implemented very sketchily; they need to be +there, but they don't do much. Their most interesting characteristic is +probably that they trap two actions -- ``FireAt`` and ``Talk`` -- which are +*not* part of the library, but instead new actions that we've defined +specially for this game. We'll talk about those actions in "Verbs, verbs, +verbs" on page 111, at which time the role of this ``before`` property will +make more sense. + +The middle of the square +======================== + +The activities here are pivotal to the game's plot. Wilhelm has arrived +from the south side of the square, and now encounters the pole with the hat +on top. He can do three things: + +#. Return south. That's allowed, but all it does is waste a little time -- + there's nothing else to usefully do south of here. + +#. Salute the pole, and then proceed to the north. That's allowed, though + it rather subverts the folk story. + +#. Attempt to proceed northwards without saluting the pole. Twice, a + soldier will prevent this, and issue a verbal warning. On the third + attempt, patience runs out, and Wilhelm is hauled off to perform his + party piece. + +So, there are two actions that we need to look out for: ``Salute`` (trapped +by the pole), and ``Go`` (which can be trapped by the room itself). ``Go`` +is a standard library action. ``Salute`` is one that we've devised; let's +deal with it first. Here's a first cut of the room:: + + Room mid_square "Middle of the square" + with description + "There is less of a crush in the middle of the square; most + people prefer to keep as far away as possible from the pole + which towers here, topped with that absurd ceremonial hat. A + group of soldiers stands nearby, watching everyone who passes.", + n_to north_square, + s_to south_square; + +and the pole:: + + Furniture pole "hat on a pole" mid_square + with name 'wooden' 'pole' 'pine' 'hat' 'black' 'red' 'brim' 'feathers', + description + "The pole, the trunk of a small pine some few inches in diameter, + stands about nine or ten feet high. Set carefully on top is + Gessler's ludicrous black and red leather hat, with a widely + curving brim and a cluster of dyed goose feathers.", + has_been_saluted false, + before [; + FireAt: + print_ret "Tempting, but you're not looking for trouble."; + Salute: + self.has_been_saluted = true; + print_ret "You salute the hat on the pole. ^^ + ~Why, thank you, sir,~ sneers the soldier."; + ], + has scenery; + +The room will need some more work in a minute, but the pole object is +complete (note that we've simplified matters slightly by making one object +represent both the pole and the hat which it supports). It mentions a +property which we've not met before: ``has_been_saluted``. What a +remarkable coincidence: the library provides a property with a name that's +exactly right for our game; surely not? + +No, of course not. ``has_been_saluted`` isn't a standard library property; +it's one that we've just invented. Notice how easily we did it -- we +simply included the line:: + + has_been_saluted false, + +in the object definition and voilà, we've added our own home-made property, +and initialised it to ``false``. To switch the state of the property, we +can simply write:: + + pole.has_been_saluted = true; + pole.has_been_saluted = false; + +or just (within the pole object):: + + self.has_been_saluted = true; + self.has_been_saluted = false; + +We could also test, if necessary, how the property currently fares:: + + if (pole.has_been_saluted == true) ... + +and that is exactly what we'll be doing in a minute to check whether +Wilhelm has saluted the pole, and choose between different outcomes. + +Notice that we use ``==`` (that's two equals signs) to test for "is equal +to"; don't confuse this usage with ``=`` (a single equals sign) which +assigns a value to a variable. Compare these examples: + +.. list-table:: + :header-rows: 1 + :widths: 1 1 + + * - Correct + - Incorrect + + * - ``score = 10;`` + - ``score == 10;`` + + * - assigns the value 10 to ``score`` + - does nothing; ``score`` is unchanged + + * - ``if (score == 10) ...`` + - ``if (score = 10) ...`` + + * - executes the next statement only if the value of ``score`` is 10 + - assigns 10 to ``score``, then always executes the next statement -- + because ``score = 10`` evaluates to 10, which is treated as + ``true``, so the test is always ``true`` + +Defining a new property variable which, instead of applying to every object +in the game (as do the standard library properties), is specific only to a +class of objects or even -- as here -- to a single object, is a common and +powerful technique. In this game, we need a ``true/false`` variable to +show whether Wilhelm has saluted the pole or not: the clearest way is to +create one as part of the pole. So, when the pole object traps the Salute +action, we do two things: use a ``self.has_been_saluted = true`` statement +to record the fact, and then use a ``print_ret`` statement to tell players +that the salute was "gratefully" received. + +.. note:: + + Creating new property variables like this -- at the drop of a hat, as it + were -- is the recommended approach, but it isn't the only possibility. + We briefly mention some alternative approaches in "Reading other + people's code" on page 181. + +Back to the ``mid_square`` room. We've said that we need to detect Wilhelm +trying to leave this room, which we can do by trapping the ``Go`` action in +a ``before`` property. Let's sketch the coding we'll need:: + + before [; + Go: + if (noun == s_obj) { Wilhelm is trying to move south } + if (noun == n_obj) { Wilhelm is trying to move north } + ]; + +We can easily trap the ``Go`` action, but which direction is he moving? +Well, it turns out that the interpreter turns a command of GO SOUTH (or +just SOUTH) into an action of ``Go`` applied to an object ``s_obj``. This +object is defined by the library; so why isn't it called just "``south``"? +Well, because we already have another kind of south, the property ``s_to`` +used to say what lies in a southerly direction when defining a room. To +avoid confusing them, ``s_to`` means "south to" and ``s_obj`` means "south +when the player types it as the object of a verb". + +The identity of the object which is the target of the current action is +stored in the ``noun`` variable, so we can write the statement ``if (noun +== s_obj)`` to test whether the contents of the ``noun`` variable are equal +to the ID of the ``s_obj`` object -- and, if so, Wilhelm is trying to move +south. Another similar statement tests whether he's trying to move north, +and that's all that we're interested in; we can let other movements take +care of themselves. + +The words :samp:`{Wilhelm is trying to move south}` aren't part of our +game; they're just a temporary reminder that, if we need to execute any +statements in this situation, here's the place to put them. Actually, +that's the simpler case; it's when :samp:`{Wilhelm is trying to move +north}` that the fun starts. We need to behave in one of two ways, +depending on whether or not he's saluted the pole. But we *know* when he's +done that; the pole's ``has_been_saluted`` property tells us. So we can +expand our sketch like this:: + + before [; + Go: + if (noun == s_obj) { Wilhelm is trying to move south [1] } + if (noun == n_obj) { Wilhelm is trying to move north... + if (pole.has_been_saluted == true) + { ...and he's saluted the pole [2] } + else { ...but he hasn't saluted the pole [3] } + } + ]; + +Here we have one ``if`` statement nested inside another. And there's more: +the inner ``if`` has an ``else`` clause, meaning that we can execute one +statement block when the test ``if (pole.has_been_saluted == true)`` is +true, and an alternative block when the test isn't true. Read that again +carefully, checking how the braces ``{...}`` pair up; it's quite complex, +and you need to understand what's going on. One important point to +remember is that, unless you insert braces to change this, an ``else`` +clause always pairs with the most recent ``if``. Compare these two +examples:: + + if (condition1) { + if (condition2) { here when condition1 is true and condition2 is true } + else { here when condition1 is true and condition2 is false } + } + + if (condition1) { + if (condition2) { here when condition1 is true and condition2 is true } + } + else { here when condition1 is false } + +In the first example, the ``else`` pairs with the most recent :samp:`if +({condition2})`, whereas in the second example the revised positioning of +the braces causes the ``else`` to pair with the earlier :samp:`if +({condition1})`. + +.. note:: + + We've used indentation as a visual guide to how the ``if`` and ``else`` + are related. Be careful, though; the compiler matches an ``else`` to + its ``if`` purely on the basis of logical grouping, regardless of how + you've laid out the code. + +Back to the before property. You should be able to see that the cases +marked ``[1]``, ``[2]`` and ``[3]`` correspond to the three possible +courses of action we listed at the start of this section. Let's write the +code for those, one at a time. + +.. rubric:: Case 1: Returning south + +First, :samp:`{Wilhelm is trying to move south}`; not very much to this:: + + warnings_count 0, ! for counting the soldier's warnings + before [; + Go: + if (noun == s_obj) { + self.warnings_count = 0; + pole.has_been_saluted = false; + } + if (noun == n_obj) { + if (pole.has_been_saluted == true) + { moving north...and he's saluted the pole } + else { moving north...but he hasn't saluted the pole } + } + ]; + +Wilhelm might wander into the middle of the square, take one look at the +pole and promptly return south. Or, he might make one or two (but not +three) attempts to move north first, and then head south. *Or*, he might +be really perverse, salute the pole and only then head south. In all of +these cases, we take him back to square one, as though he'd received no +soldier's warnings (irrespective of how many he'd actually had) and as +though the pole had not been saluted (irrespective of whether it was or +not). In effect, we're pretending that the soldier has such a short +memory, he'll completely forget Wilhelm if our hero should move away from +the pole. + +To do all this, we've added a new property and two statements. The +property is ``warnings_count``, and its value will count how many times +Wilhelm has tried to go north without saluting the pole: 0 initially, 1 +after his first warning, 2 after his second warning, 3 when the soldier's +patience finally runs out. The property ``warnings_count`` isn't a +standard library property; like the pole's ``has_been_saluted`` property, +it's one that we've created to meet a specific need. + +Our first statement is ``self.warnings_count = 0``, which resets the value +of the ``warnings_count`` property of the current object -- the +``mid_square`` room -- to 0. The second statement is +``pole.has_been_saluted = false``, which signifies that the pole has not be +saluted. That's it: the soldier's memory is erased, and Wilhelm's actions +are forgotten. + +.. rubric:: Case 2: Moving north after saluting + +:samp:`{Wilhelm is moving north...and he's saluted the pole}`; another easy +one:: + + warnings_count 0, ! for counting the soldier's warnings + before [; + Go: + if (noun == s_obj) { + self.warnings_count = 0; + pole.has_been_saluted = false; + } + if (noun == n_obj) { + if (pole.has_been_saluted == true) { + print "^~Be sure to have a nice day.~^"; + return false; + } + else { moving north...but he hasn't saluted the pole } + } + ]; + +All that we need do is print a sarcastic goodbye from the soldier, and then +``return false``. You'll remember that doing so tells the interpreter to +continue handling the action, which in this case is an attempt to move +north. Since this is a permitted connection, Wilhelm thus ends up in the +``north_square`` room, defined shortly. + +.. rubric:: Case 3: Moving north before saluting + +So that just leaves the final case: :samp:`{moving north...but he hasn't +saluted the pole}`. This one has more to it than the others, because we +need the "three strikes and you're out" coding. Let's sketch a little +more:: + + warnings_count 0, ! for counting the soldier's warnings + before [; + Go: + if (noun == s_obj) { + self.warnings_count = 0; + pole.has_been_saluted = false; + } + if (noun == n_obj) { + if (pole.has_been_saluted == true) { + print "^~Be sure to have a nice day.~^"; + return false; + } + else { + self.warnings_count = self.warnings_count + 1; + switch (self.warnings_count) { + 1: First attempt at moving north + 2: Second attempt at moving north + default: Final attempt at moving north + } + } + } + ]; + +First of all, we need to count how many times he's tried to move north. +``self.warnings_count`` is the variable containing his current tally, so we +add 1 to whatever value it contains: ``self.warnings_count = +self.warnings_count + 1``. Then, determined by the value of the variable, +we must decide what action to take: first attempt, second attempt, or final +confrontation. We could have used three separate ``if`` statements:: + + if (self.warnings_count == 1) { First attempt at moving north } + if (self.warnings_count == 2) { Second attempt at moving north } + if (self.warnings_count == 3) { Final attempt at moving north } + +or a couple of nested ``if`` statements:: + + if (self.warnings_count == 1) { First attempt at moving north } + else { + if (self.warnings_count == 2) { Second attempt at moving north } + else { Final attempt at moving north } + } + +but for a series of tests all involving the same variable, a ``switch`` +statement is usually a clearer way of achieving the same effect. The +generic syntax for a ``switch`` statement is:: + + switch (expression) { + value1: whatever happens when the expression evaluates to value1 + value2: whatever happens when the expression evaluates to value2 + ... + valueN: whatever happens when the expression evaluates to valueN + default: whatever happens when the expression evaluates to something else + } + +This means that, according to the current value of an expression, we can +get different outcomes. Remember that the :samp:`{expression}` may be a +``Global`` or local variable, an object's property, one of the variables +defined in the library, or any other expression capable of having more than +one value. You could write ``switch (x)`` if ``x`` is a defined variable, +or even, for instance, ``switch (x+y)`` if both ``x`` and ``y`` are defined +variables. Those :samp:`{whatever happens when...}` are collections of +statements which implement the desired effect for a particular value of the +switched variable. + +Although a switch statement :samp:`switch ({expression}) { ... }` needs +that one pair of braces, it doesn't need braces around each of the +individual "cases", no matter how many statements each of them includes. +As it happens, case 1 and case 2 contain only a single ``print_ret`` +statement each, so we'll move swiftly past them to the third, more +interesting, case -- when ``self.warnings_count`` is 3. Again, we could +have written this:: + + switch (self.warnings_count) { + 1: First attempt at moving north + 2: Second attempt at moving north + 3: Final attempt at moving north + } + +but using the word ``default`` -- meaning "any value not already catered +for" -- is better design practice; it's less likely to produce misleading +results if for some unforeseen reason the value of ``self.warnings_count`` +isn't the 1, 2 or 3 you'd anticipated. Here's the remainder of the code +(with some of the printed text omitted):: + + self.warnings_count = self.warnings_count + 1; + switch (self.warnings_count) { + 1: print_ret "..."; + 2: print_ret "..."; + default: + print "^~OK, "; + style underline; print "Herr"; style roman; + print " Tell, now you're in real trouble. I asked you + ... + old lime tree growing in the marketplace.^"; + move apple to son; + PlayerTo(marketplace); + return true; + } + +The first part is really just displaying a lot of text, made slightly +messier because we're adding emphasis to the word "Herr" by using +underlining (which actually comes out as *italic type* on most +interpreters). Then, we make sure that Walter has the apple (just in case +we didn't give it to him earlier in the game), relocate to the final room +using ``PlayerTo(marketplace)``, and finally ``return true`` to tell the +interpreter that we've handled this part of the ``Go`` action ourselves. +And so, at long last, here's the complete code for the ``mid_square``, the +most complicated object in the whole game:: + + Room mid_square "Middle of the square" + with description + "There is less of a crush in the middle of the square; most + people prefer to keep as far away as possible from the pole + which towers here, topped with that absurd ceremonial hat. A + group of soldiers stands nearby, watching everyone who passes.", + n_to north_square, + s_to south_square, + warnings_count 0, ! for counting the soldier's warnings + before [; + Go: + if (noun == s_obj) { + self.warnings_count = 0; + pole.has_been_saluted = false; + } + if (noun == n_obj) { + if (pole.has_been_saluted == true) { + print "^~Be sure to have a nice day.~^"; + return false; + } ! end of (pole has_been_saluted) + else { + self.warnings_count = self.warnings_count + 1; + switch (self.warnings_count) { + 1: print_ret "A soldier bars your way. ^^ + ~Oi, you, lofty; forgot yer manners, didn't you? + How's about a nice salute for the vogt's hat?~"; + 2: print_ret "^~I know you, Tell, yer a troublemaker, + ain't you? Well, we don't want no bovver here, + so just be a good boy and salute the friggin' + hat. Do it now: I ain't gonna ask you again...~"; + default: + print "^~OK, "; + style underline; print "Herr"; style roman; + print " Tell, now you're in real trouble. I asked you + nice, but you was too proud and too stupid. I + think it's time that the vogt had a little word + with you.~ + ^^ + And with that the soldiers seize you and Walter + and, while the sergeant hurries off to fetch + Gessler, the rest drag you roughly towards the + old lime tree growing in the marketplace.^"; + move apple to son; + PlayerTo(marketplace); + return true; + } ! end of switch + } ! end of (pole has_NOT_been_saluted) + } ! end of (noun == n_obj) + ]; + +The north side of the square +============================ + +The only way to get here is by saluting the pole and then moving north; not +very likely, but good game design is about predicting the unpredictable. :: + + Room north_square "North side of the square" + with description + "A narrow street leads north from the cobbled square. In its + centre, a little way south, you catch a last glimpse of the pole + and hat.", + n_to [; + deadflag = 3; + print_ret "With Walter at your side, you leave the square by the + north street, heading for Johansson's tannery."; + ], + s_to "You hardly feel like going through all that again."; + +There's one new feature in this room: the value of the ``n_to`` property is +a routine, which the interpreter runs when Wilhelm tries to exit the square +northwards. All that the routine does is set the value of the library +variable ``deadflag`` to 3, print a confirmation message, and ``return +true``, thus ending the action. + +At this point, the interpreter notices that ``deadflag`` is no longer zero, +and terminates the game. In fact, the interpreter checks ``deadflag`` at +the end of every turn; these are the values that it's expecting to find: + +* 0 -- this is the normal state; the game continues. +* 1 -- the game is over. The interpreter displays "You have died". +* 2 -- the game is over. The interpreter displays "You have won". +* any other value -- the game is over, but there aren't any appropriate + messages built into the library. Instead, the interpreter looks for an + **entry point** routine called ``DeathMessage`` -- which we must provide + -- where we can define our own tailored "end messages". + +In this game, we never set ``deadflag`` to 1, but we do use values of 2 +and 3. So we'd better define a ``DeathMessage`` routine to tell players +what they've done:: + + [ DeathMessage; print "You have screwed up a favourite folk story"; ]; + +Our game has only one customised ending, so the simple ``DeathMessage`` +routine we've written is sufficient for our purposes. Were you to conceive +multiple endings for a game, you could specify suitable messages by +checking for the current value of the ``deadflag`` variable:: + + [ DeathMessage; + if (deadflag == 3) print "You leave Scarlett O'Hara for good"; + if (deadflag == 4) print "You crush Scarlett with a passionate embrace"; + if (deadflag == 5) print "You've managed to divorce Scarlett"; + ... + ]; + +Of course, you must assign the appropriate value to ``deadflag`` at the +point when the game arrives at each of those possible endings. + +We've nearly finished. In the concluding chapter of this game, we'll talk +about the fateful shooting of the arrow. diff --git a/images/picO.png b/images/picO.png new file mode 100644 index 0000000..7384620 Binary files /dev/null and b/images/picO.png differ