Full Adv3Lite Version of Trollbridge

Trollbridge is a simple demo game in which the protagonist needs to get a troll out of the way so that he can cross the bridge that leads to his village.

The version given below uses the full version of the adv3Lite library, i.e. adv3Lite with all (or at least, most of) the optional modules. If you compare it with the minimal version you'll see that it uses a substantially wider range of classes to define simulation objects, at the gain of having to define fewer properties on those objects. This version is almost functionally identical to the other, however, except that it includes scoring, a status-line exit lister, an ABOUT menu, and a more sophisticated way of presenting the hints.



Trollbridge Source Code (Full adv3Lite)

#charset "us-ascii"

#include <tads.h>
#include "advlite.h"

versionInfo: GameID
    IFID = '23781944-8bbf-437f-9c67-4b1fafc1ed35'
    name = 'Trollbridge2'
    byline = 'by Eric Eve'
    htmlByline = 'by <a href="mailto:eric.eve@somewhere.com">Eric Eve</a>'
    version = '1'
    authorEmail = 'Eric Eve <eric.eve@somewhere.com>'
    desc = 'A version of the Trollbridge game using more of the adv3Lite
        library, as would probably more normally be the case.'
    htmlDesc = 'A version of the Trollbridge game using more of the adv3Lite
        library, as would probably more normally be the case.'
    
    showAbout()
    {
        aboutMenu.display();
        "Done. ";
    }
;


gameMain: GameMainDef
    /* Define the initial player character; this is compulsory */
    initialPlayerChar = me
    
    showIntro()
    {
        "All you want to do is return home after a hard day's work in the
        fields, but just as you arrive at the bridge over the river that runs by
        your village, an unexpected obstacle presents itself...\b";
    }
;

/* 
 *   We don't define or use the aHdir function in this version of the game,
 *   since the included exitList module will effectively do the job for us.
 */

/* The starting location; this can be called anything you like */

startroom: Room 'Road by Bridge'
    "The road from the south comes to an end at the edge of a
    bridge leading north across the river, whose bank continues to
    east and west. "
    
    north = bridge
    south = road
    east = eastBank
    west = westBank
;

/* 
 *   The player character object. This doesn't have to be called me, but me is a
 *   convenient name. If you change it to something else, remember to change
 *   gameMain.initialPlayerChar accordingly.
 */

+ me: Thing 'you' 
    "You're a gangling young lad of eighteen. "
    isFixed = true       
    person = 2  // change to 1 for a first-person game
    contType = Carrier    
;

/*  
 *   We use the Enterable class for the bridge so that an attempt to ENTER it.
 *   will automatically be translated into an attempt to travel via the
 *   object defined on its connector property.
 */
+ Enterable 'bridge; big stone'
    "The big stone bridge spans the river. "
    
    connector = bridge 
    
    vocabLikelihood = 10
    
    dobjFor(Cross) asDobjFor(Enter)
    dobjFor(Board) asDobjFor(Enter)
;

/* The custom River class is defined below */
+ River
;

bridge: Room 'Bridge'
    "From the centre of the bridge you can carry on north home to your village
    or back south down the road through the fields. Meanwhile the river rushes
    rapidly under the bridge, carrying the waters downstream to the east. "
    
    south = startroom
    
    /* 
     *   In adv3Lite we can attach a method directly to a directional exit
     *   property; the method is then executed when the player character
     *   attempts to move in the relevant direction. Here the effect is to
     *   produce a winning ending to the game.
     */
    north()
    {
        "You carry on across the bridge and head on to the village, back to
        home, hearth, and a good hearty meal. ";
        
        /* 
         *   Calling awardPointsOnce(); on an Achievement (defined below) causes
         *   the points to be awarded, but only the first time the method is
         *   called.
         */
        homeAchievement.awardPointsOnce();
        
        /* We use the finishGameMsg() function to end the game. */
        finishGameMsg(ftVictory, [finishOptionUndo, finishOptionScore,
            finishOptionFullScore]);
    }
;

/* 
 *   We make the river a Decoration so that attempting to do anything with it
 *   except examining it is met with the response defined in its
 *   notImportantMsg.
 */
+ Decoration 'river; rushing; water'
    "The river is in full flood, carrying the water down from recent heavy
    rainfall in the highlands off to the west. "
        
    notImportantMsg = 'The river is too far beneath you. '
;


eastBank: Room 'River Bank (East)'
    "This section of riverbank is in danger of becoming flooded by the river
    pouring in full torrent from under the bridge to the east. Further south the
    field is quite dry, however. "
    
    west = startroom
    south = eastField
;

/* The River class is defined below. */
+ River
;

/* The bottle is now a Container, which means that things can be put in it. */
+ bottle: Container 'green bottle; (wine)'
    "It looks like it may be a wine bottle, but there's no label on it; perhaps
    the label washed off in the water. "
    
    smellDesc = "The bottle smells strongly of vinegar. "
        
    /* isFloating is a custom property we define just for this object */
    isFloating = true
    
    initSpecialDesc = "A brown bottle floats in the river just out of reach,
        apparently caught by the reeds, perhaps after being thrown into the 
        river. "
    
    dobjFor(Take)
    {
        /* 
         *   In adv3Lite a check() routine need only display some text to
         *   prevent the action from going ahead.
         */
        check()
        {
            if(isFloating)
                "The bottle is just out of reach. ";
        }
        
    }
    
    dobjFor(MoveWith)
    {
        action()
        {
            if(isFloating)
            {
                "You manage to snag the bottle with {the iobj} and bring it
                ashore. ";
                isFloating = nil;
                moved = true;
                bottleAchievement.awardPointsOnce();
            }
            else
                inherited;
        }
    }
    
    contentsListed = !isFloating
;


++ vinegar: Thing 'some wine; murky red ;vinegar liquid'
    "It's a kind of murky red colour. "
    isPourable = true
    
    
    dobjFor(PourInto)
    {
        action()
        {
            "You pour the vinegar into {the iobj}. ";
            moveInto(gIobj);
        }
    }
    
    smellDesc = "The wine smells sour; it must be turning to vinegar. "
    tasteDesc = "It tastes very sour, more like vinegar than wine. "
    cannotDrinkMsg = 'The wine is undrinkable; it\'s more or less vinegar now. '
    
    /* 
     *   If isProminentSmell is true, then the smellDesc of this object will be
     *   displayed in response to an intransitive SMELL command (as well as
     *   SMELL VINEGAR).
     */
    isProminentSmell = !isIn(bottle)
;

+ Decoration 'reeds;submerged;plants;them'
    "The reeds are more than half submerged by the river. "
;

westBank: Room 'River Bank (West)'
    "The riverbank, which continues to the east is particularly thick with
    plants at this point. The way north is blocked by the river running rapidly
    by, but to the south lies an open field. "
    east = startroom
    south = westField
;

+ River
;

/* By making the plants a Fixture we ensure that they can't be taken. */
+ plants: Fixture 'plants;green;reeds;them'
    "Most of them are just reeds<<if monkshood.isIn(nil)>>, but every now and
    again you fancy you detect a hint of purple among them<<end>>. "
        
    /*  
     *   Objects listed in the hiddenIn property will be discovered in response
     *   to a SEARCH or LOOK IN command.
     */
    hiddenIn = [monkshood]
    
    /*   
     *   If autoTakeOnFindHidden were true then SEARCHing the plants would
     *   result in the contents of its hiddenIn list being automatically taken
     *   by the player character. By making it nil we instead move the items in
     *   the hiddenIn list to the location of the plants.
     */
    autoTakeOnFindHidden = nil
    
    lookInMsg()
    {
        if(monkshood.isIn(location))
            return 'As you\'ve already discovered, there\'s some monkshood
                growing among the reeds. ';
        else
            return 'Now you\'ve taken the monkshood there\'s not much there but
                reeds. ';
    }
;

+ purple: Decoration 'hint of purple'
    "It looks like there may be something purple there. "
        
    notImportantMsg = 'You\'ll need to take a closer look first. '
;


monkshood: Thing 'some monkshood; purple; flowers aconitum'
    "You recognize the purple leaves as Monkshood, a plant that can be deadly
    poisonous in any quantity, especially if made up into a tincture. "
    
    dobjFor(Take)
    {
        check()
        {
            if(gloves.wornBy != gActor)
                "You know better than to pick monkshood with your bare hands; it
                would be too easy for the poison to seep into your skin. ";
        }
    }
    
    dobjFor(Pick) asDobjFor(Take)
    
    actionMoveInto(cont)
    {
        inherited(cont);
        monksAchievement.awardPointsOnce();
    }
    
    initSpecialDesc = "Some purple monkshood grows among the plants. "
;

road: Room 'Road'
    "This long road runs north -- south through cornfields to east and west. "
    north = startroom
    
    south { "That would take you too far in the wrong direction; there has to 
        be a solution closer to hand. "; }
    
    east = eastField
    
    west = westField
    
;

eastField: Room 'East Field'
    "The field has been recently harvested, leaving only the stubble of corn
    behind. A small hut stands off to one side; to the west is the road, while
    the river is a little distance to the north. "
    
    
    west = road
    north = eastBank
    in = hutDoorOutside
;

/* 
 *   Making the hut an Enterable means that any attempt to enter the hut will be
 *   translated into an attempt to travel via the object defined on its
 *   connector property, in this case the hut's outside door.
 */
+ Enterable 'small hut; wooden (tool); shed toolshed'
    "The small wooden hut is nothing special; it's probably little more than a
    toolshed for the people working this field. "
   
    connector = hutDoorOutside
       
    /* 
     *   The remapIn property is a quick way of redirecting a whole set of
     *   actions to an associated container object, but it works just as well
     *   with a door object. This definition will redirect commands like OPEN,
     *   CLOSE, LOCK, UNLOCK and ENTER from the hut to the door.
     */
    remapIn = hutDoorOutside
;

/* 
 *   Any door must be of the Door class, and must be linked to the object
 *   representing the other side of the door through its otherSide property.
 */
++ hutDoorOutside: Door '(hut) door'
    otherSide = hutDoorInside
    keyList = [ironKey]
;


+ rock: Thing 'large rock; irregular dark grey gray of[prep]; lump'
    "It's a large irregular dark grey lump of rock. "
    
    dobjFor(Take)
    {
        check()
        {
            if(isStuck)
                "You can't quite lift it; it seems to be stuck to the ground,
                probably by the dried mud encrusted around it. ";             
        }
    }
    
    dobjFor(LookUnder) { check() { checkDobjTake();}  }
    
    
    initSpecialDesc = "A large rock lies on the ground a short distance from the
        hut. "
    
    /* 
     *   Anything in the hiddenUnder property will be discovered and moved into
     *   the location of the rock when the player character either takes or
     *   looks under the rock.
     */
    hiddenUnder = [ironKey]
    
    /*   
     *   By default we can't pour things on other things, but if allowPourOntoMe
     *   is true then we can pour something that's pourable onto this Thing.
     */
    allowPourOntoMe = true
    
    /*   Here, isStuck is a custom property */
    isStuck = true
;

/* 
 *   Again we use the Fixture class to ensure the mud can't actually be taken.
 *   This is equivalent to defining isFixed = true on the object. Either way an
 *   attempt to take it will be met with the response definined in the
 *   cannotTakeMsg property.
 */
+ driedMud: Fixture 'dried mud; encrusted hard solid'
    "The mud encrusted round the rock looks like it's dried as hard as cement. "
        
    feelDesc = "The mud feels very hard indeed. "
    
    cannotTakeMsg = 'It\'s dried solid, making it too hard to move. '
    
    /* 
     *   This short form of remap (the only legal use of remap in adv3Lite)
     *   remaps POUR X ON MUD to POUR X ON ROCK.
     */
    iobjFor(PourOnto) { remap = rock }
;

+ Decoration 'stubble; harvested; corn'
    "Only the stubble is left. "    
    
    notImportantMsg = 'What\'s left of the corn now that it\'s been harvested
        isn\'t worth bothering with. '

;

/* Anything that is to act as a key must be of the Key class. */
ironKey: Key 'iron key'
    moveInto(cont)
    {
        inherited(cont);
        keyAchievement.awardPointsOnce();
    }
;

hut: Room 'Hut'
    "This small hut looks like it's used as a convenient storage place for
    whoever works this field. A single wooden shelf runs along one wall
    opposite the door. "
    out = hutDoorInside
;

+ hutDoorInside: Door 'door'
    otherSide = hutDoorOutside    
    keyList = [ironKey]
;

/* 
 *   Making something a Surface means that objects can be placed on top of it.
 *   Making it a Fixture prevents it from being taken. We can use multiple
 *   inheritance as here to define an object that belongs to both classes. We
 *   could have got the same effect by defining isFixed = true and contType = On
 *   on a Thing, but here we're illustrating the use of the additional classes
 *   defined in the full version of adv3Lite.
 */
+ Surface, Fixture 'shelf; plain wooden'    
;


/* 
 *   Making the gloves of class Wearable allows them to be worn. This is
 *   equivalent to defining isWearable = true.
 */
++ gloves: Wearable 'gloves; worn leather of[prep]; pair; them it'
    "They're just a pair of worn leather gloves. "    
;

++ shears: Thing 'pair of shears; sharp sharpened;;it them'
    "They look like they've been recently sharpened. "
    canCutWithMe = true
    canMoveWithMe = true
;

westField: Room 'West Field'
    "The field to the west of the road has yet to be harvested; the high
    standing corn waves gently in the evening sunshine all around. Off to the
    north the field slopes down to the riverbank, while the main road lies just
    to the east. A chicken coop squats in a corner of the field. "
    east = road
    north = westBank
;

/* 
 *   Making the coop a Container means that things can be put in it; making it a
 *   Fixture ensures that it can't be taken.
 */
+ coop: Container, Fixture 'chicken coop;; wire'
    "The chicken coop is made mainly of wire. It looks as if it has recently
    been repaired, perhaps after a fox managed to get in. "
    
    /* 
     *   A Container is open by default, but we want this one to start out
     *   closed, so that the player character can't immediately get at the dead
     *   hen.
     */
    isOpen = nil
    
    /*  
     *   We make the coop transparent so that the player character can see that
     *   there's a dead hen in it even when it's closed.
     */
    isTransparent = true
    
    cannotOpenMsg = 'Whoever carried out the repairs seems to have been a bit
        over-zealous; there\'s now no obvious way to open the coop. '
    
    isCuttable = true
    dobjFor(CutWith)
    {
        check()
        {
            if(isOpen)
                "You've already cut your way into the coop; there's no need to
                cut it any further. ";
        }
        
        action()
        {
            isOpen = true;
            "You cut an opening in the coop with {the iobj}. ";
            
        }
    }
    
;

++ hen: Thing 'dead hen;; carcass'
    "It's dead; it looks like a fox must have got it. "
    specialDesc = "A hen lies on the floor of the coop. "
    
    isPoisoned = nil
    
    moveInto(cont)
    {
        inherited(cont);
        henAchievement.awardPointsOnce();
    }
;

/* 
 *   We make the bucket a Container so that things can be put in it. This is
 *   equivalent to defining contType = In and isOpen = true (plain Containers,
 *   unlike OpenableContainers, start out open by default).
 */
+ bucket: Container 'bucket; plain old wooden'
    "It's just a plain old wooden bucket. "
    
    isFillable = true
    
    afterAction()
    {
        if(vinegar.isIn(self) && monkshood.isIn(self) && hen.isIn(self) &&
           !hen.isPoisoned)
        {
            "The noxious tincture of monskshood in vinegar starts seeping into
            the carcass of the hen. ";
            
            hen.isPoisoned = true;
        }
    }
    
    allowPourIntoMe = true
    canMoveWithMe = true
    
    initSpecialDesc = "A bucket lies on the ground by the side of the chicken
        coop. "
;

+ Decoration 'some standing corn; ripe'
    "It's ripe and ready to be harvested. "
    
    notImportantMsg = 'The corn is best left to the harvesters. '
;

/* 
 *   Water is rather an odd sort of object to simulate. Here we define it with
 *   isFixed = true so that it can't be picked up but isListed = true so that
 *   it's listed when present (normally making isFixed true would make isListed
 *   nil, i.e. false).
 */
water: Thing 'some water'
    "It's reasonably clear, without too much silt from the river. "
    isFixed = true
    isListed = true
    cannotTakeMsg = 'The water runs through your fingers. '
    
    isPourable = true
    
    dobjFor(Pour)
    {
        action()
        {
            askForIobj(PourOnto);
        }
    }
       
    dobjFor(PourOnto)
    {
        action()
        {
            "You pour the water onto {the iobj}. ";
            if(gIobj is in (rock, driedMud) && rock.isStuck)
            {
                rock.isStuck = nil;
                "This appears to loosen some of the dried mud holding the rock.
                ";
            }
            moveInto(nil);
            driedMud.moveInto(nil);
        }
    }
    
    dobjFor(Drink)
    {
        action()
        {
            "You scoop up a few handfuls of the water to slake your thirst. ";
        }
    }
    
    tasteDesc = "It doesn't taste too bad. "
    isDrinkable = true
    
;

/* 
 *   Define our custom River class. Objects of this class can't be taken or
 *   entered but they can be used at as the indirect objects of a FillWith
 *   command (e.g. FILL BUCKET WITH WATER or FILL BUCKET FROM RIVER).
 */
class River: Fixture 'river;;water current'
    "The river is in full flood, carrying the water down from recent heavy
    rainfall in the highlands off to the west. Even a strong swimmer would be
    in danger of being carried away by that current, and you're not any kind
    of swimmer. The river is in any case far too deep to ford. "
    isFixed = true
    
    cannotTakeMsg = 'The water runs through your fingers. '
    cannotEnterMsg = 'You never learned how to swim. '
    
    iobjFor(FillWith)
    {
        verify()
        {
            if(water.isIn(bucket))
                illogicalNow('The bucket is already full of water. ');            
        }
        
        action()
        {
            "{I} fill{s/ed} {the dobj} with water from the river. ";
            water.moveInto(gDobj);
        }
    }
     
    dobjFor(Cross) asDobjFor(Enter)
;


/*  
 *   The troll is a fairly simple NPC, but in this version we'll implement him
 *   using the adv3Lite Actor class etc.
 */
troll: Actor 'troll; big ugly;; him' @startroom
    "Like all full-grown trolls, this one is very big and very ugly. "
    
    
    allowAttack = true
    
    dobjFor(Attack)
    {
        check()
        {
            "The troll is almost twice your size and four times your strength;
            in any attempt at combat you'd be sure to come off second best. ";
        }
    }    
    
    iobjFor(ThrowAt)
    {
        action()
        {
            gDobj.actionMoveInto(location);
            "The troll swats {the dobj} angrily aside and snarls at you. ";
        }
    }
;

/* 
 *   An ActorState represents one of a number of states an Actor can be in. The
 *   troll is a simple NPC with only one state, but a more complex NPC would
 *   have several, reflecting its changing behaviour and appearance at different
 *   times. By also making this ActorState a StopEventList we can have it
 *   display each of the messages in its eventList property in turn (repeating
 *   the final one ad infinitum); this provides a neater method of achieving
 *   what was done with the afterAction() method and switch statement in the
 *   advLiter version.
 */
+ trollBridgeState: ActorState, StopEventList
    isInitState = true
    
    specialDesc = "A big ugly troll guards the bridge. "
    
    beforeTravel(traveler, connector)
    {
        if(traveler == gPlayerChar && connector == bridge)
        {
            "The trolls snarls and blocks your path. ";
            exit;
        }
    }
    
    eventList = [
        
        'The troll finishes gnawing on the bones of some defunct animal ---
        a hen perhaps --- and tosses them contemptuously into the river. ',
        
        'The troll lets out a loud belch. ',
        
        'The troll looks around for something else to eat. ',
        
        'The troll starts staring at you with a worryingly hungry look. ',
        
        'The troll continues to stare hungrily at you as if he\'s pondering how
        tasty you might be. '
    ]
;

/* 
 *   A GiveShowTopic defines the actor's response to being given or shown
 *   something, in this case a hen. The AltTopic that's defined immediately
 *   after defines the alternative response that's used when its isActive
 *   property is true (i.e. what happens when we give the hen to the troll when
 *   the hen is poisoned). In adv3Liter we had to define special handling for
 *   the GiveTo action on the troll, with an additional method on the hen.
 */
++ GiveShowTopic @hen
    topicResponse()
    {
        "The troll snatches the hen from your grasp and starts chewing on it.
        He finishes it off in a few mouthfuls, then his hunger apparently
        provoked rather than sated, he makes a grab for you and starts tearing
        your limbs off to start chewing on them. Mercifully you quickly lose
        consciousness. ";
        
        finishGameMsg(ftDeath, [finishOptionUndo, finishOptionScore,
            finishOptionFullScore]);
    }
;

+++ AltTopic
    topicResponse()
    {
        "The troll grabs the hen from you and stuffs it into his mouth. Almost
        immediately he clutches at his stomach and groans in agony, his cries
        crescendoing to shrieks as he leans over the parapet to empty the
        contents of his stomach into the river. Either because he leans too far,
        or because the poison has already done its work, he falls over the
        parapet into the river and is rapidly carried off by the current,
        disappearing out of sight as he's pulled under the water. ";
        
        hen.moveInto(nil);
        troll.moveInto(nil);
        trollAchievement.awardPointsOnce();
    }
    isActive = hen.isPoisoned
;

/* The troll's response to being offered anything other than the hen. */
++ DefaultGiveShowTopic
    topicResponse()
    {
        "The troll takes one look at {the dobj} and casts {him dobj}
        contemptuously aside. ";
        gDobj.moveInto(location);
    }
    
;

/* The troll's response to all other conversational commands. */
++ DefaultAnyTopic
    "The troll merely grunts threateningly; trolls never were great
    conversationalists. "
;


/* Define a custom FillWith action */
DefineTIAction(FillWith)
;

/* Define the grammar for the FillWith action */
VerbRule(FillWith)
    'fill' singleDobj ('with' | 'from') singleIobj
    : VerbProduction
    action = FillWith
    verbPhrase = 'fill/filling (what) (with what)'
    missinqQ = 'what do you want to fill; what do you want to fill it with'
;

/* 
 *   In the adv3Liter version we handled PICK MONKSOOD and CROSS BRIDGE via a
 *   StringPreParser, which is a less than fully adequate way of doing it. Here
 *   we do it properly by defining custom PICK and CROSS actions.
 */
DefineTAction(Pick)
;

DefineTAction(Cross)
;

VerbRule(Pick)
    'pick' singleDobj
    : VerbProduction
    action = Pick
    verbPhrase = 'pick/picking (what)'
    missingQ = 'What do you want to pick'
;

VerbRule(Cross)
    'cross' singleDobj
    : VerbProduction
    action = Cross
    verbPhrase = 'cross/crossing (what)'
    missingQ = 'What do you want to cross'
;


/* 
 *   Modifications to the base Thing class to define default handling for our
 *   custom FillWith, Pick and Cross actions.
 */
modify Thing
    dobjFor(FillWith)
    {
        preCond = [objHeld]
        verify() 
        { 
            if(!isFillable)
                illogical(cannotFillMsg); 
            
            if(gIobj == self)
                illogicalSelf(cannotFillWithSelfMsg);
        }
    }
    
    iobjFor(FillWith)
    {
        preCond = [touchObj]
        verify()         
        {             
            illogical(cannotFillWithMsg);             
        }
    }
    
    isFillable = nil
    
    cannotFillMsg = '{The subj dobj} {is}n\'t something that can be filled. '
    cannotFillWithMsg = '{I} {can\'t} fill anything with {that iobj}. ' 
    cannotFillWithSelfMsg = '{I} {can\'t} fill {the dobj} with {himself dobj}. '
    
    dobjFor(Pick)
    {
        preCond = [touchObj]
        verify() { illogical(cannotPickMsg); }
    }
    
    dobjFor(Cross)
    {
        preCond = [touchObj]
        verify() { illogical(cannotCrossMsg); }
    }
    
    cannotPickMsg = '{I} {can\'t} pick {that dobj}. '
    cannotCrossMsg = '{I} {can\'t} cross {that dobj}. '
;

/* 
 *   A Doer is an object that can intercept a command and make it do something
 *   different from the normal action handling.
 */

Doer 'put bucket in River'
    execAction(c)
    {
        doInstead(FillWith, bucket, gIobj);
    }
;

/* 
 *   Instead of using a Doer we can now use the built-in hint system to set up
 *   the same simple series of hints
 */

hintMenu: TopHintMenu 'Hints';

+ Goal 'How do I get past the troll?'
    [
        'The troll is hungry; this is one troll you should feed, preferably with
        something that won\'t do him any good. ',
        
        'You\'ve seen the troll polish off a dead hen, and it shouldn\'t be too
        hard to find another one nearby; but you\'ll have to poison it somehow.
        ',
        
        'If you search along the river bank you should find the means of
        poisoning the hen -- a poisonous plant that can be made into a tincture
        with the aid of some sour wine. ',
        
        'In order to pick the poisonous plant and get into the hen coop you need
        some items that are in the hut. The key is hidden not far from the hut,
        but you need to loosen something to get at it. How might you dispose of
        the dried mud? ',
        
        'Fill the bucket with water from the river and pour it on the rock (or
        the mud).',
        
        'You can use either the bucket or the shears to move the bottle close
        enough inshore to take. ',
        
        'Use the bucket to mix your tincture: put the monkshood in the bucket,
        pour the wine/vinegar in and then put the hen in. The hen will then be a
        meal fit for a troll! '        
    ]
    
    goalState = OpenGoal
;

/* 
 *   We'll also add some scoring; each Achievement object marks something the
 *   player has to do to obtain the associated points. The points are awarded by
 *   calling the awardPointsOnce() method on the Achievement object.
 */

keyAchievement: Achievement +1 "finding the key to the hut";
bottleAchievement: Achievement +1 "getting the bottle";
trollAchievement: Achievement +1 "poisoning the troll";   
henAchievement: Achievement +1 "obtaining the dead hen";
monksAchievement: Achievement +1 "picking the monkshood";
homeAchievement: Achievement +1 "getting safely back home";


/* 
 *   Finally we'll create a menu for use with the ABOUT command. In addition to
 *   displaying general instructions on playing IF and our hints menu, it'll
 *   offer our custom MenuLongTopicItem giving a brief description of the game.
 */
aboutMenu: MenuItem
    contents = [topInstructionsMenu, hintMenu]
;

+ MenuLongTopicItem 'About Trollbridge 2'
    'Trollbridge 2 is a demonstration of adv3Lite using more of the core library
    than the basic Trollbridge game and making use of a few more features.\b
    The immediate difference you\'ll see is the status line exit lister. This
    version of the game also keeps score, and in this version you can use the GO
    TO command (e.g. GO TO BRIDGE) to navigate round the map to rooms you\'ve
    already visited. '
;