Crossing the Stream
As the next step to making things more complicated for Heidi, we'll put the key in a field on the far side of a stream. First we need to add two extra locations to accommodate the stream:
|
"The path through the trees from the southeast comes to an end on
the banks of a stream. Across the stream to the west you can see
an open meadow. "
southeast = fireClearing
west = streamWade
;
streamWade : RoomConnector
room1 = pathByStream
room2 = meadow
;
meadow : OutdoorRoom 'Large Meadow'
"This large, open meadow stretches almost as far as you can see
to north, west, and south, but is bordered by a fast-flowing stream
to the east. "
east = streamWade
;
|
Next we'll move the small brass key to the meadow and tweak its properties a little.
|
"It's a small brass key, with a faded tag you can no longer read. "
initSpecialDesc = "A small brass object lies in the grass. "
remoteInitSpecialDesc(actor)
{
"There is a momentary glint of something brassy as
the sun reflects off something lying in the meadow across the stream. ";
}
dobjFor(Take)
{
action()
{
if(!moved)
addToScore(1, 'retrieving the key');
inherited;
name = 'small brass key';
}
}
;
|
Now comes the clever stuff. In order to make objects in room A visible from room B we need to join the two locations together with a DistanceConnector; which is particular kind of SenseConnector (which we met before in connection with the cottage window); a SenseConnector can exist in two or more locations since it is a subclass of MultiLoc (more of which anon). A DistanceConnector has a library template that makes it exceedingly easy to define; all we need to add is:
|
|
|
"The stream is not terribly deep at this point, though it's flowing
quite rapidly towards the south. "
locationList = [pathByStream, meadow]
dobjFor(Cross)
{
verify() {}
check() {}
action()
{
replaceAction(TravelVia, streamWade);
}
}
;
|
Part of the value of defining a separate streamWade object now becomes apparent; it makes the coding of the action method of dobjFor(Cross) exceedingly simple. Instead of having to test for which side of the stream we're on to decide which side we need to end up on when we cross the stream, we simply TravelVia streamWade and leave streamWade to sort it all out. But as we'll see shortly, that's only part of the story.
|
First, we need to define both CrossAction and its associated grammar. A couple of library macros hide most of the complication of all this, and all we need write is:
|
VerbRule(Cross)
'cross' singleDobj
: CrossAction
verbPhrase = 'cross/crossing (what)'
;
|
The name of the VerbRule (here Cross) can be anything we like, so long as it's unique among the VerbRule names in our game. It doesn't actually need to match the name of our action, it's just (a) a convenient way of ensuring a unique VerbRule name and (b) an obvious way of making it clear what the VerbRule is for. After naming the VerbRule we next need to define its grammar, i.e. the phrase that the player must enter to invoke this command. This will normally consist of a fixed element, such as the name of the verb, in this case 'cross', followed by a placeholder for the noun or nouns that the player wants the command to apply to. For a TAction this placeholder can either be singleDobj (meaning that only one direct object is allowed) or dobjList (meaning that the command can be applied to several direct objects at once, as in take the red ball, the long stick, and the bent banana).
It would make no sense to cross several objects at once, so we definitely want singleDobj rather than dobjList here. We could, if we wanted, have defined more synonyms for the verb, e.g. ('cross' | 'ford') singleDobj, but once more I'll leave that as an exercise for the interested reader. The point to note is that if we do want to define alternative phrasings, we use a vertical bar (|) to separate the alternatives, and brackets to group them. The brackets would be necessary in the foregoing example, since without them we'd have 'cross' | 'ford' singleDobj, which would mean 'cross' or 'ford something', rather than 'cross something' or 'ford something', as we'd actually want.
After the definition of the grammar for the command comes a colon followed by the name of the action class, which is the name we gave the action plus the word 'Action' appended, hence CrossAction. If you think this looks rather like declaring our VerbRule (strictly speaking, our grammar definition) to be of class CrossAction, then you're right; but again this isn't an issue that need concern us here, beyond noting that the DefineTAction(Cross) macro in fact defines a new class called CrossAction as a subclass of TAction.
We then have to define a verbPhrase so that the parser can construct certain messages, such as '(first crossing the stream)' or 'What do you want to cross?' if it needs to. The correct format for a verb phrase for a TAction should be reasonably clear from the example shown: first the infinitive (without 'to') followed by the present participle with a slash (oblique) in between (hence 'cross/crossing'). Then a placeholder for a direct object, enclosed in brackets (hence '(what)'). Note that this placeholder may be used by the parser to construct a question about a missing direct object ('What do you want to cross?'), so for verbs that were more likely to be applied to people (e.g. 'thank') you'd want to use '(who)' or, even more correctly, '(whom)' rather than '(what)'.
One more step we have to take is to define what happens when cross is used with any noun other than the stream, which we can do by modifying the definition of the Thing class:
modify Thing dobjFor(Cross) { verify() { illogical('{The dobj/he} {is} not something you can cross. ' ); } } ;
Note here how we've begun the illogical response with '{The dobj/he} {is}'
rather than '{The dobj/he} is'
. By putting 'is' in curly braces we ensure that it will always agree in number with the name of the direct object (which is what, of course, '{The dobj/he}'
expands to). This ensures that if the direct object were, say, some flowers growing on the river bank, then cross flowers will respond with 'The flowers are not something you can cross' rather than the incorrect 'The flowers is not something you can cross'.
If you now compile and run the game it should all work, though getting across the stream doesn't seem to be much of a puzzle. We can make it more of one if Heidi has to wear a pair of old boots before she can cross. To start with we'll leave the boots lying by the side of the stream. Then all we have to do is to modify the
streamWade object so that it only allows anyone to pass when they're wearing the boots.Before looking at the solution below, you may like to try to work out how to do all this yourself. The only new thing about the boots is that we need to make them of class Wearable, so Heidi can put them on. The trick is then to work out how to prevent Heidi from crossing the stream unless she is wearing the boots. You should be able to work it out by analogy from the way we prevented Heidi from climbing the tree unless she's standing on the chair.
|
|
@pathByStream
"They look ancient, battered, and scuffed, but probably still waterproof. "
;
|
|
room1 = pathByStream
room2 = meadow
canTravelerPass(traveler) { return boots.isWornBy(traveler); }
explainTravelBarrier(traveler)
{
"Your shoes aren't waterproof. If you wade across you'll get your feet
wet and probably catch your death of cold. ";
}
;
|
Getting Started in TADS 3
[Main]
[Previous] [Next]