Rewarding the Effort
|
If the player has gone to all this trouble to reach the top of the tree, perhaps he or she deserves some sort of reward for it. One form of reward is to add some points to the player's score. This can be done very simply through the statement:
|
addToScore(points, 'reason for awarding points');
|
|
|
addToScore(1, 'reaching the top of the tree.');
|
|
It would be possible to code all this in the connector we used on the chair object, either by overriding the connector's actionDobjTravelVia method, or, more simply (and sneakily) by inserting the relevant code into its showTravelDesc method. But this is not the best solution, since we may later want to provide alternative means by which Heidi could reach the top of the tree (find a ladder perhaps, or constructing a person-lifting kite), and it would be cumbersome if we had to add the same point scoring method to each connector and still devise some means of preventing the point from being awarded more than once.
Since what we want to do is to check for the valiant Heidi arriving at a particular location (the top of the tree) regardless of how she achieved this feat, the best solution is to make use of the enteringRoom method of the topOfTree room. The library code already keeps track of which rooms have been visited by setting their seen property, so we can use this to ensure that the point is awarded only the first time Heidi reaches the top of the tree. The revised topOfTree room then looks like this:
|
"You cling precariously to the trunk, next to a firm, narrow branch."
down = clearing
enteringRoom(traveler)
{
if(!traveler.hasSeen(self) && traveler == gPlayerChar)
addToScore(1, 'reaching the top of the tree. ');
}
;
|
|
Before turning over the page to see how this guide does it, you could have a go at implementing these extra objects yourself. Remember that the branch will need to be a Supporter so you can put things on it, and the nest a Container so you can put things in it. Remember too that you'll need to make sure that Heidi can't pick up the branch - after all it's part of the tree and fixed in place (if in doubt, look at the pedestal in the 'goldskull' game). Then put something interesting in the nest, and see if you can get your revised game to compile and run.
|
+ branch : Surface, Fixture 'branch' 'branch'
"The branch looks too narrow to walk, crawl or climb along, but firm
enough to support a reasonable weight. "
;
++ nest : Container 'bird\'s nest' 'bird\'s nest'
"It's carefully woven from twigs and moss. "
;
+++ ring : Thing 'platinum diamond ring' 'diamond ring'
"The ring comprises a sparkling solitaire diamond set in platinum. It
looks like it could be intended as an engagement ring. "
;
Note the use of the + notation to nest (no pun intended) each item in the preceding one. We make the branch a Surface so that we can put things on it and a Fixture so that it's fixed in place; this illustrates how the same object may inherit from more than one superclass. The nest is made a Container so we can put something in it. Internally there's not a lot of difference; the location property of ring is set to nest, and the location property of nest to branch. The difference lies in the way the library code describes the situation (in or on) and the type of commands it will respond to (put in or put on), as you'll find if you add the code and play with the new version of the game.
You'll probably also find that the discovery of the ring seems rather bland and bathetic, since as soon as Heidi arrives at the top of the tree the game announces "On the branch is a bird's nest (which contains a diamond ring)." It would be more interesting, and hence would seem a bit more rewarding, if she had to work a little to find that ring. Besides, it would not be that unrealistic to suppose that the ring would at first be hidden among the twigs and moss that make up the nest. The first step towards making things more interesting, then, is to remove the +++ from in front of the definition of the ring (so that it starts life outside the game world altogether with a location of nil) and then code the nest to respond to a search or look in command. This code must first check that we haven't already found the ring, and then, if we haven't, it should move the ring into the nest and report the find. While we're at it we might as well award the player a point for the discovery. The appropriate code looks like this:
|
"It's carefully woven from twigs and moss. "
dobjFor(LookIn)
{
action()
{
if(ring.moved)
{
"You find nothing else of interest in the nest. ";
exit;
}
ring.moveInto(self);
"A closer examination of the nest reveals a diamond ring inside! ";
addToScore(1, 'finding the ring');
}
}
;
There are five points to note about this code:
(1) moved is a property defined by the library; it starts at nil (i.e. not true) and is set to true as soon as the object is moved from its initial location, which is what ring.moveInto(self) does;
(2) to move the ring we use its moveInto method, we do not change its location property directly by writing something like ring.location = self (part of the reason for this is that the library maintains lists of what's contained by what; the moveInto method takes care of updating the appropriate lists when an object is moved, while setting the location property would not);
(3) self simply refers to the object in which it occurs (in this case the nest);
(4) exit terminates the action straightaway, so that code following an exit statement is not executed; the exit statement may also be used to veto an action at the check stage, as we shall see shortly;
(5) the library definition of Thing already defines dobjFor(Search) asDobjFor(LookIn), so that the two verbs behave the same way; this is a potential trap for the unwary since an author might code the behaviour for Search and then try to create the synonym by adding dobjFor(LookIn) asDobjFor(Search), which will cause a stack overflow run time error as LookIn and Search frantically chase each other's tails (each being remapped to the other) ad infinitum, or at least ad terminum acervi (to the limit of the stack).
An alternative way of achieving the same effect would be to leave the +++ in front of the definition of ring, and add PresentLater to the front of its class list, at the same time changing ring.moveInto(self) to ring.makePresent() in the definition of nest. Where the PresentLater mix-in class is used, the game initialization makes a note of the object's location, then moves it into nil (i.e. out of the game world); a call to makePresent() then restores the object to its initial location. Yet another way we could achieve much the same effect would be by making the ring of class Hidden, but we shall illustrate that on another object shortly.
Whichever method is employed, the revision to the nest object makes things slightly more interesting, but searching the nest isn't that much of a challenge. It would be rather more interesting if in order to search the nest we first had to hold it, and, furthermore, if the nest was just out of reach so we first had to find some way of bringing it nearer (we've already indicated in the description of the branch that Heidi can't go crawling along the branch towards the nest). The obvious tool for the job would be some sort of stick, and the obvious place to find such a stick might be among twigs and branches that had fallen to the ground at the bottom of the tree.
The simplest way of making it necessary to hold the nest before searching it would be to add
|
preCond = [objHeld]
|
|
check()
|
{
|
if(!isHeldBy(gActor))
|
{
|
what's inside. ";
exit;
|
}
|
}
|
|
|
{
check()
{
if(!moved && !stick.isIn(gActor))
{
"The nest is too far away for you to reach. ";
exit;
}
}
action()
{
if(!moved)
"Using the stick you manage to pull the nest near enough to take,
which you promptly do. ";
inherited;
}
}
|
This is fine, but it might not occur to the player that the nest can be taken simply because Heidi is holding the stick; the player may suppose some more complex command involving both the stick and the nest needs to be used. Out of the standard verbs defined in the library the most plausible candidate would be move nest with stick, so we'll code this command to act in exactly the same way. This introduces a new complication, defining special behaviour for a verb involving two objects. We'll begin by defining some code on the intended direct object of this command, which is still the nest:
|
{
verify()
{
if(isHeldBy(gActor))
illogicalAlready('{You/he} {is} already holding it. ');
}
check()
{
if(gIobj != stick)
{
"{You/he} can't move the nest with that. ";
exit;
}
}
}
The effect of this code is to rule that it is illogical to attempt to move the nest with anything while the nest is being held, and not to allow the nest to be moved with anything other than the stick (gIobj in the check routine refers to the indirect object of the command, which in virtually every case means the second object referred to in a two-object command). We leave it up to the stick to deal with the rest, which it'll do by passing responsibility back to the nest, thus:-
|
{
verify() {}
check() {}
action()
{
if(gDobj==nest && !nest.moved)
replaceAction(Take, nest);
}
}
Note since that this forms part of our definition of the stick, which is to be the indirect object of the MoveWith command, we now define iobjFor(MoveWith) instead of dobjFor(MoveWith) We supply empty check and verify routines to allow the stick to be used as an indirect object of MoveWith (which the library would otherwise prevent). The action routine only does something special if the direct object (gDobj) is the nest and the nest hasn't already been moved. If either of those conditions fails to be met, the command will result in the default MoveWith action of the direct object, which is a report to the effect that moving it didn't achieve anything. If, however, the direct object is the nest and it hasn't been moved yet we want the result to be the same as if we had issued the command take nest while holding the stick. We achieve this with the replaceAction macro. This does just what it says it does and stops processing the current command, replacing it with the action routine of the new command. Had we wished to execute another command and then continue with the existing command we would have used nestedAction instead.
Although the base of the tree is a good place to find the stick, it's probably better not to make it too obvious; if the stick is just lying there in plain sight the player will take it automatically, which will make getting hold of the nest virtually a non-puzzle. To make things a bit harder we'll try to disguise the presence of the stick by burying it in a pile of useless twigs, so that the player has to do some work (albeit minimal) to find them. While we're at it we'll change the description of the sycamore tree so that it refers to the pile of twigs. Again, this is something you might like to try yourself before turning over the page to see how this guide does it. After changing the description of the tree, you'll need to add one object to represent the pile of twigs, and then another for the stick object, which should remain hidden until Heidi examines or searches the pile of twigs. To hide the stick you could use one of the techniques discussed in relation to hiding the ring in the nest, or you could make the stick a Hidden object and call its discovered() method at the appropriate moment.
|
|
"Standing proud in the middle of the clearing, the stout
tree looks like it should be easy to climb. A small pile of loose
twigs has gathered at its base. "
dobjFor(Climb) remapTo(TravelVia, gPlayerChar.location.up)
;
+ Decoration 'loose small pile/twigs' 'pile of twigs'
"There are several small twigs here, most of them small, insubstantial,
and frankly of no use to anyone bigger than a blue-tit <<stick.moved ?
nil : '; but there is also one fairly long, substantial stick among
them'>>. <<stick.discover>>"
dobjFor(Search) asDobjFor(Examine)
;
+ stick : Hidden 'long substantial stick' 'long stick'
"It's about two feet long, a quarter of an inch in diameter, and
reasonably straight. "
iobjFor(MoveWith)
{
verify() {}
check() {}
action()
{
if(gDobj==nest && !nest.moved)
replaceAction(Take, nest);
}
}
;
We could have handled the stick in a similar manner to the ring, by moving it into the clearing when we wanted it to appear, but this seems a good opportunity to introduce the Hidden class, which does much what it says. A Hidden item is one that is physically present but does not reveal its presence until its discover method is called. By making stick of class Hidden instead of class Thing, we can control when we want it to appear. In this case we want it to appear when the player character examines the pile of twigs, so we make an embedded call to stick.discover in the description of the twigs, using the <<>> syntax. The fact that this method will be called every time the twigs are examined doesn't matter, since once the method has been called once, the subsequent calls will have no effect. There are a couple of other refinements we need to think about, however. First, the description of the pile of twigs should only refer to the stick amongst them until the stick has been moved; we achieve this through another embedded expression that displays nothing if the stick has been moved but describes the stick if it hasn't. Secondly, the player might reasonably try to search the pile of sticks as well as examine them, so we add a line to the definition of the anonymous sticks object to remap search to examine.
Note how we have defined the vocabWords property of the Decoration object representing the pile of twigs: we have defined it as 'loose small pile/twigs'. Although you can't normally refer to an object by two or more of its nouns, there is an exception in the case of a name like 'x of y', where both x and y should be specified as nouns. Our pile of twigs will now respond to examine twigs or x small pile or search pile of loose twigs and other such combinations.
Let's just add one final refinement. Normally if you drop an object, it lands in the room where you are, as you would expect. But if you were to drop something from the top of a tree you'd expect it to fall to the ground below rather than hover around in the air still conveniently in reach. It would be good if we could model this in our game, and it turns out to be fairly straightforward. First, we need to change the class of topOfTree to FloorlessRoom, which means that any object dropped or thrown from this location won't land here. Then we need to override topOfTree's bottomRoom property to define where something dropped from here will land. In this case we want bottomRoom to be clearing. Now anything dropped (or thrown) while Heidi is at the top of the tree will fall to the clearing, and the game will display a suitable message to show that the object is falling out of sight. The definition of topOfTree thus becomes;
|
"You cling precariously to the trunk, next to a firm, narrow
branch. "
down = clearing
enteringRoom(traveler)
{
if(!traveler.hasSeen(self) && traveler == gPlayerChar)
addToScore(1, 'reaching the top of the tree. ');
}
bottomRoom = clearing
;
|
|
Getting Started in TADS 3
[Main]
[Previous] [Next]