Implementing infinite object dispensers *with object counts* in TADS3/adv3

This is one of those things where I’ve have existing code for the individual bits, but tying them all together is proving unexpectedly complicated (TADS3, everybody).

Infinite(-ish) Dispensers

First, here’s some code for an infinite resource dispenser, involving two classes: ResourceFactory for the dispensers; and Resource for the things dispensed. It’s kinda like the stock Dispenser class, but instances are created as needed.

The gimmick I’m using is to hide an instance of the Resource in each ResourceFactory and use it to remap >TAKE [resource] to >TAKE [resource] FROM [resource factory] if there are no other takeable instances of the resource in scope (but will default to taking a resource instance on the floor if there is one).

Preliminary nonsense

First a couple library messages:

modify playerActionMessages
        // Default success message.
        okayTakeResource = '{You/He} take{s} {an dobj/her} from
                {the iobj/him}. '

        okayTakeResourceCount(n) {
                return('{You/He} take{s} <<spellInt(n)>>
                        <<((n == 1) ? gDobj.name : gDobj.pluralName)>>
                        from {the iobj/him}. ');
        }
        
        // Factory doesn't accept anything.
        cantPutAnythingInFactory = '{You/He} can\'t put anything in
                {the iobj/him}. '

        // Factory accepts returns, but dobj isn't its resource.
        cantPutInNonResource = '{You/He} can\'t put {that dobj/him}
                in {the iobj/him}. '
;

The second message there, the one with the count, is mostly aspirational at the moment, which we’ll see later.

Next we’ll declare a few utility macros. This is based on the isType.h file, which is available from a tiny git repo. The only bit we really care about is the isType macro itself, which is:

#define isType(obj, cls) ((obj != nil) && (dataType(obj) == TypeObject) && obj.ofKind(cls))

That is, it evaluates to boolean true if the first arg is non-nil and is an instance of the class given in the second arg. We use that to define our resource-specific checks:

#define isResource(obj) isType(obj, Resource)
#define isResourceFactory(obj) isType(obj, ResourceFactory)
#define isInFactory(obj) (isResource(obj) && isResourceFactory(obj.location) \
                && (obj.ofKind(obj.location.resourceClass)))

The first two are straightforward. The third is used to test if the arg a) is a non-nil instance of the Resource class, and b) is currently in a ResourceFactory instance that dispenses resources of its type.

The Resource class

We now define the Resource class:

class Resource: Thing
        // Tweak vocabulary likelihood so we don't show up in disambiguation
        // prompts if we're still in the factory.
        vocabLikelihood = (isInFactory(self) ? -30 : 0)

        isEquivalent = true

        // The factory handles things for >TAKE [resource] FROM [factory],
        // but we also have to handle >TAKE [resource] by itself when
        // there's no other instance in scope besides the one in the
        // factory.
        dobjFor(Take) {
                verify() {
                        if(isInFactory(self))
                                logicalRank(50, 'factory');
                        inherited();
                }
                action() {
                        if(isInFactory(self))
                                replaceAction(TakeFrom, self, self.location);
                        inherited();
                }
        }
;

We use the isInFactory macro to twiddle the vocabLikelihood of Resource instances. This is so an instance in a factory won’t show up in a disambig prompt, but if there are no other instances in scope trying to >TAKE the resource will grab one from the factory (instead of requiring the player to specify >TAKE [resource] FROM [factory].

The latter behavior handled by the dobjFor(TAKE) stanza.

The ResourceFactory class

Finally we define the ResourceFactory class:

class ResourceFactory: Container
        // Class of resource we dispense.  Should be overidden by instances.
        resourceClass = Resource

        // We don't list our contents because we always contain a phantom
        // resource.
        contentsListed = nil
        contentsListedInExamine = nil

        // Boolean.  If true we can put resource instances back in the factory.
        resourceReturnable = nil

        // Actual resource dispensing logic.
        iobjFor(TakeFrom) {
                action() {
                        local obj;

                        obj = createResource();
                        obj.moveInto(gActor);
                        defaultReport(&okayTakeResource);
                        exit;
                }
        }

        // Handle returns.
        iobjFor(PutIn) {
                verify() {
                        // If we don't accept returls, fail.
                        if(!resourceReturnable)
                                illogical(&cantPutAnythingInFactory);

                        // We accept returns but the dobj isn't our
                        // resource.
                        if(!isResource(gDobj) || !gDobj.ofKind(resourceClass))
                                illogical(&cantPutInNonResource);
                }

                // To do a return we do the default stuff--display message,
                // remove object from actor's inventory--and then we move
                // the object into nil;  we don't try to cache instances,
                // just dump and create a new one if we have to.
                action() {
                        inherited();
                        gDobj.moveInto(nil);
                }
        }

        // Resource creation method.  By default we just create an instance
        // of our resource class, but subclasses can fancy this up if
        // needed.
        createResource() {
                return(resourceClass.createInstance());
        }

        initializeThing() {
                inherited();
                initializeFactory();
        }

        // Factory initialization.  We create an instance of our resource
        // and add it to ourselves.  This is what saves us from
        // >TAKE [resource] failing with "You see no [resource] here. ".
        initializeFactory() {
                if(contains({ x: x.ofKind(resourceClass) }))
                        return;
                createResource().moveInto(self);
        }
;

The class’ initializeThing() insures that there’s a “phantom” Resource instance in the factory to provide a match in scope for the resource’s vocabulary. We prevent the factory from listing its contents so it’s mostly invisble.

The additional logic is just the iobjFor(TakeFrom) and iobjFor(PutIn) stanzas, which handle creating new resource instances and disposing of them, respectively. The last behavior being optional and controlled by the resourceReturnable flag.

A working(-ish) example

Putting this all together into a complete example “game”:

#include <adv3.h>
#include <en_us.h>

#include "adv3Utils.h"

versionInfo: GameID;
gameMain: GameMainDef initialPlayerChar = me;

class Pebble: Resource '(small) (round) pebble*pebbles' 'pebble'
        "A small, round pebble. "
;

startRoom: Room 'Void'
        "This is a formless void containing a bucket of infinite pebbles. "
;
+me: Person;
+bucket: Fixture, ResourceFactory '(infinite) (pebbles) bucket' 'bucket'
        "It contains...let's see...looks like an infinite number of
        pebbles. "
        resourceClass = Pebble
        resourceReturnable = true
;

This gets us a plausible infinite pebble dispenser dispenser:

Void
This is a formless void containing a bucket of infinite pebbles.

>i
You are empty-handed.

>take pebble
You take a pebble from the bucket.

>take pebble
You take a pebble from the bucket.

>i
You are carrying two pebbles.

>drop pebble
You drop the pebble.

>l
Void
This is a formless void containing a bucket of infinite pebbles.

You see a pebble here.

>take pebble
You take the pebble.

>l
Void
This is a formless void containing a bucket of infinite pebbles.

>i
You are carrying two pebbles.

We can take as many pebbles as we want, and deference is given to other in-scope pebbles over the “phantom” pebble in the bucket.

However…

Trying again with counts

The problem comes when we try to take more than one pebble:

>restart
Do you really want to start over? (Y is affirmative) > y

Void
This is a formless void containing a bucket of infinite pebbles.

>take 3 pebbles
You don't see that many pebbles here.

…or, alternately…

>restart
Do you really want to start over? (Y is affirmative) > y

Void
This is a formless void containing a bucket of infinite pebbles.

>take pebble; take pebble; take pebble
You take a pebble from the bucket.

You take a pebble from the bucket.

You take a pebble from the bucket.

>take 3 pebbles
the pebble in the bucket: You take a pebble from the bucket.

your pebble: You are already carrying the pebble.

your pebble: You are already carrying the pebble.

>i
You are carrying four pebbles.

I’ve previously implemented workarounds for actions with counts when there aren’t enough simulation objects to satisfy noun resolution. For example, >DRAW 5 CARDS from a deck of cards, where the individual cards aren’t simulation objects.

This approach works fine if the noun/vocabulary being matched is only used for “virtual”/non-simulation objects. The problem here is that we have a mix of “real” and “virtual” objects.

Here’s what that looks like, although like I said I do not have this working:

Horrible mess that doesn’t actually work, don’t copy this

First create a couple actions with counts that shadow the stock >TAKE and >TAKE [dobj] FROM [iobj] actions:

DefineTAction(TakeCount) countAction = true;
VerbRule(TakeCount)
        ( 'take' | 'pick' 'up' | 'get' ) singleNumber dobjList
        | 'pick' singleNumber dobjList 'up'
        : TakeCountAction
        verbPhrase = 'take/taking (what)'
;

DefineTIAction(TakeCountFrom) countAction = true;
VerbRule(TakeCountFrom)
        ( 'take' | 'get' ) singleNumber dobjList
                ( 'from' | 'out' 'of' | 'off' | 'off' 'of') singleIobj
        | 'remove' singleNumber dobjList 'from' singleIobj
        : TakeCountFromAction
        verbPhrase = 'take/taking (what) (from what)'
;

Whenever these guys are used, the singleNumber in the productions will mean that gAction will have a non-nil numMatch property, and calling getval() on it will return the numeric value.

We also put a countAction flag on both of these actions. This could be handled more cleanly, but this is just to make writing checks easier.

Anyway, now we modify the resource and factory classes:

modify Resource
        vocabLikelihood() {
                if(!isInFactory(self)) return(0);
                if(gAction && gAction.countAction) return(-100);
                return(0);
        }

        dobjFor(TakeCount) {
                verify() {
                        if(!isInFactory(self))
                                nonObvious;
                }
                action() {
                        if(!gAction.numMatch) return;
                        self.location.resourceCount = gAction.numMatch.getval();
                        replaceAction(TakeCountFrom, self, self.location);
                }
        }

        dobjFor(TakeCountFrom) {
                verify() { dangerous; }
        }
;

modify ResourceFactory
        resourceCount = nil

        iobjFor(TakeCountFrom) {
                verify() {}
                action() {
                        local i, obj;

                        if(!resourceCount) return;
                        for(i = 0; i < resourceCount; i++) {
                                obj = createResource();
                                obj.moveInto(gActor);
                        }
                        defaultReport(&okayTakeResourceCount, resourceCount);
                        resourceCount = nil;
                }
        }
;

With the same gameworld as before, that gets us:

Void
This is a formless void containing a bucket of infinite pebbles.

>take 3 pebbles
You take three pebbles from the bucket.

Hurray! But try it again…

>undo
Taking back one turn: "take 3 pebbles".

Void
This is a formless void containing a bucket of infinite pebbles.

>take 3 pebbles
You take three pebbles from the bucket.

>take 3 pebbles
your pebble:
your pebble:
your pebble:
the pebble in the bucket: You take three pebbles from the bucket.

Ooof.

Thanks for coming to my TED Talk

That’s where I’m at. So…is there an obvious/clever way to handle this I’m missing?

I was half hoping typing all this up would rubber duck debug a solution for me but so far nope.

3 Likes

Hm.

This feels like a kludge, but I can kinda get this specific example working by tweaking the scope via the new actions’ objInScope() methods:

DefineTAction(TakeCount)
        countAction = true
        objInScope(obj) { return(isInFactory(obj)); }
;
VerbRule(TakeCount)
        ( 'take' | 'pick' 'up' | 'get' ) singleNumber dobjList
        | 'pick' singleNumber dobjList 'up'
        : TakeCountAction
        verbPhrase = 'take/taking (what)'
;

DefineTIAction(TakeCountFrom)
        countAction = true
        objInScope(obj) { return(isInFactory(obj)); }
;
VerbRule(TakeCountFrom)
        ( 'take' | 'get' ) singleNumber dobjList
                ( 'from' | 'out' 'of' | 'off' | 'off' 'of') singleIobj
        | 'remove' singleNumber dobjList 'from' singleIobj
        : TakeCountFromAction
        verbPhrase = 'take/taking (what) (from what)'
;

And a unrelated tweak to ResourceFactory.iobjFor(TakeCountFrom) that should’ve been in the original (to handle >TAKE [resource] FROM [factory] properly in addition to just >TAKE [resource]):

        getResourceCount() {
                return(resourceCount ? resourceCount :
                        ((gAction && gAction.numMatch)
                                ? gAction.numMatch.getval()
                                : 0));
        }

        iobjFor(TakeCountFrom) {
                verify() {}
                action() {
                        local i, obj;

                        resourceCount = getResourceCount();

                        if(!resourceCount) return;
                        for(i = 0; i < resourceCount; i++) {
                                obj = createResource();
                                obj.moveInto(gActor);
                        }
                        defaultReport(&okayTakeResourceCount, resourceCount);
                        resourceCount = nil;
                }
        }

(The original version relied on having resourceCount set by the remap from TakeCount).

Anyway, this gets us partially there:

Void
This is a formless void containing a bucket of infinite pebbles.

>take 3 pebbles
You take three pebbles from the bucket.

>take 3 pebbles
You take three pebbles from the bucket.

But this costs us the weirdness of now >TAKE with a count will always prefer to take from the factory if the dobj is a resource, even if there are enough resources elsewhere in scope:

>restart    
Do you really want to start over? (Y is affirmative) > y

Void
This is a formless void containing a bucket of infinite pebbles.

>take 3 pebbles
You take three pebbles from the bucket.

>drop all
pebble: Dropped.
pebble: Dropped.
pebble: Dropped.

>take 3 pebbles
You take three pebbles from the bucket.

>take pebble
You take the pebble.

>l
Void
This is a formless void containing a bucket of infinite pebbles.

You see two pebbles here.

That makes >TAKE behave differently if it is used with or without a count. Which seems undesirable.

1 Like

Is there some way to tweak a ResolveInfo’s quant_ that I’m missing.

The comments in adv3 source mention using filterResolveList() to do so, but objects’ filterResolveList() methods aren’t called for this kind of failure.

I can try to sleaze my way into fixing this by dumping logic into ResolveInfo.quant_ directly (that is, making it a method instead of a property), but then whatever handles the action ends up having to re-parse the command phrase because the action will behave as if the quantity is one. That is, declaring quant_ on the resolve info to be large enough to satisfy the action’s required number of objects will result in the action not having any of the properties set that normally pass quantity, like gAction.numMatch.

This is one of those things that seems so basic that there must be something obvious I’m missing.

2 Likes

I fiddled with ≈infinite/dynamic dispensers and taking numeric quantities as separate issues for separate scenarios, but they didn’t quite overlap with what you’re doing, so I don’t think my old code would be any help.

2 Likes

Spent some time tracking this back through the parser.

The basic behavior I’m trying to intercept/override is in QuantifiedPluralProd.resolveNouns(). Near the end, after almost everything else, it goes through the resolved noun list (an array of ResolveInfo instances) and sums up their quant_ properties and compares that to the number in the command. If the sum is less than the number, it calls insufficientQuantity() on the result, which throws a parser exception and stops parsing there.

The thought I’m currently working on is declaring a qualifiedPluralNounPhrase production that shadows the existing anyNum one:

grammar qualifiedPluralNounPhrase(resourceNum):
        ('any' | ) numberPhrase->quant_ indetPluralNounPhrase->np_
        | ('any' | ) numberPhrase->quant_ 'of' explicitDetPluralNounPhrase->np_
        : ResourcePluralProd
;

The ResourcePluralProd class is a subclass of QuantifiedPluralProd with a custom resolveMainPhrase() method (which is what resolveNouns() uses to get its starting list of resolved nouns, in the form of an array of ResolveInfo instances):

        resolveMainPhrase(resolver, results) {
                local action, lst, n;

                // Get the "normal" basic noun resolution results.
                lst = inherited(resolver, results);

                // If we got nothing, bail.
                if(isNilOrEmpty(lst))
                        return(lst);

                // If we only need 1 thing, skip the fancy logic.
                n = (quant_ ? quant_.getval() : 1);
                if(n <= 1)
                        return(lst);

                // Make sure the action is one we do the renumbering for.
                action = resolver.getAction();
                if(!action || !action.tweakResourceCount)
                        return(lst);

                lst = lst.subset({ o: isInFactory(o.obj_) });

                // Tweak the quantity of the in-factory matches.
                lst.forEach(function(o) {
                        o.quant_ = (quant_ ? quant_.getval() : o.quant_);
                });

                return(lst);
        }
;

This uses a new property on Action, tweakResourceCount. By default nil, if true then the code above will:

  • truncate the resolved noun list to only include the “phantom” in-factory objects, and then
  • tweak the ResolveInfo for matching in-factory objects so their quantity matches whatever is required by the command

The tweaks to Action are:

modify Action tweakResourceCount = nil;
modify TakeAction tweakResourceCount = true;
modify TakeFromAction tweakResourceCount = true;

There’s some later tapdancing that needs to be done in the dobjFor() stanzas to properly handle action replacement (for when, for example, >TAKE 3 PEBBLES is replaced with >TAKE 3 PEBBLES FROM BUCKET, because the quantity information in the original action isn’t passed to the replacement action).

And this gets us some of the way there:

Void
This is a formless void containing a bucket of infinite pebbles.

You see three rocks here.

>take 3 pebbles
You take three pebbles from the bucket.

>take 3 pebbles
You take three pebbles from the bucket.

>put 3 pebbles in bucket
pebble: Done.
pebble: Done.
pebble: Done.

That is:

  • the first action doesn’t fail with an insufficient quantity parser exception, which it would in stock adv3
  • the second action doesn’t print a bunch of object announcements about the other pebbles in scope (like the ones in the player’s inventory) which it would without the list pruning in ResourcePluralProd.resolveMainPhrase()
  • the third action doesn’t attempt to do anything special and only uses the default scope logic, because PutInAction.tweakResourceCount isn’t true

The problem is that as currently written it will always preempt a >TAKE action involving a resource. That is, if you drop the pebbles and then >TAKE 3 PEBBLES it will always handle that as taking three pebbles from the bucket instead of grabbing the ones off the ground (but a non-count >TAKE PEBBLE will work correctly).

I think I could munge something together for that, but now I’m fretting over handling constructions like >TAKE ALL BUT 3 PEBBLES, which will not be processed correctly because of the quantity of matching pebbles will be incorrect.

2 Likes

The fun never ends with this stuff…

I briefly thought I would have something to add here, but then realized my “infinite dispenser” actually works on custom Verb BuyAction so massaging an overlap with Take syntax was not necessary.

So no, not helpful is what I’m saying.

2 Likes

Yeah, in actual practice I think most of my use cases will end up being simpler than the general case because they’ll be using new actions, like >HARVEST or whatever. Or will be in situations where there won’t be any fancy quantity-tweaking needed because there will only be a single resource.

But at this point it’ll bug me if I don’t hammer out a generic solution.

It’s interesting, and slightly frustrating, that objects providing “virtual” quantities of what they represent is clearly something that was considered during development; that’s what ResolveInfo.quant_ is there for:

    /* 
     *   By default, each ResolveInfo counts as one object, for the
     *   purposes of a quantity specifier (as in "five coins" or "both
     *   hats").  However, in some cases, a single resolved object might
     *   represent a collection of discrete objects and thus count as more
     *   than one for the purposes of the quantifier.  
     */
    quant_ = 1

But nothing in stock adv3 actually uses it.

3 Likes

Hm. So commands with counts are mildly wonky in stock adv3. I was banging my head on the fact that there are bits that need to be done in noun resolution (to prevent the parser from bailing with an insufficientQuantity exception early on) and, separately, there are bits that need to be done in action verification (to prevent >TAKE 3 PEBBLES from giving object announcements and verification failures for pebbles in the player’s inventory, for example).

But stock adv3 doesn’t really handle things in what I’d consider the correct way either. If you have two pebbles in an “ordinary” container and another pebble on the floor:

class Pebble: Thing '(small) (round) pebble*pebbles' 'pebble'
        "A small, round pebble. "
        isEquivalent = true
;

startRoom: Room 'Void' "This is a featureless void. ";
+me: Person;
+bucket: Container 'bucket' 'bucket' "It's a bucket. ";
++Pebble;
++Pebble;
+Pebble;

…and you try to take three pebbles from the bucket what you get is:

Void
This is a featureless void.

You see a bucket (which contains two pebbles) and a pebble here.

>take 3 pebbles from bucket
the pebble in the bucket: Taken.
the pebble in the bucket: Taken.

the pebble on the floor: The pebble isn't in that.

So that looks like something that wants modification by itself, independent of all the fancy dispenser stuff.

2 Likes

So this doesn’t really satisfy the original design requirements, but here’s another implementation that works, kinda. Where the “kinda” seems to be “within the confines of what adv3 can do with commands containing counts without major revision”.

What this does is create a specified number of resources in each factory object, and optionally automatically re-fills them when one is taken. So technically you can take an arbitrarily large number of resources from any (auto-refilling) factory, but only (at most) a fixed number at a time.

The code

Action Messages

First the messages, which are mostly the same as above. The success messages aren’t used in this code, but I’m planning on using them via transcript re-writing later. The thing worth noting here is that the original concept had an action with a count handled as a single action. But adv3 really wants to handle actions with counts as a sequence of individual actions: >TAKE 3 PEBBLES is internally just >TAKE PEBBLE; TAKE PEBBLE, TAKE PEBBLE, with each individual >TAKE action getting its own success/failure message(s).

Anyway, the messages again just for completeness:

modify playerActionMessages
        // Default success message.
        okayTakeResource(src) {
                gMessageParams(src);
                return('{You/He} take{s} {an dobj/her} from
                        {the src/him}. ');
        }

        okayTakeResourceCount(n) {
                return('{You/He} take{s} <<spellInt(n)>>
                        <<((n == 1) ? gDobj.name : gDobj.pluralName)>>
                        from {the iobj/him}. ');
        }
        
        // Factory doesn't accept anything.
        cantPutAnythingInFactory = '{You/He} can\'t put anything in
                {the iobj/him}. '

        // Factory accepts returns, but dobj isn't its resource.
        cantPutInNonResource = '{You/He} can\'t put {that dobj/him}
                in {the iobj/him}. '
;

The Resource class

The current Resource class is much simpler than the previous version, and its dobjFor(Take) stanza just checks to see if the taken instance is in its factory object, and displays a custom message and pings the factory object to potentially refill itself.

class Resource: Thing
        // Custom >TAKE result message.  Note the "factory" message
        // parameter, which matches our factory object.
        resourceTakeDesc = "{You/He} take{s} {an dobj/her} from
                {the factory/him}. "

        // Tweak vocabulary likelihood so we don't show up in disambiguation
        // prompts if we're still in the factory.
        vocabLikelihood = (isInFactory(self) ? -30 : 0)

        isEquivalent = true

        hideFromAll(action) { return(isInFactory(self)); }

        dobjFor(Take) {
                action() {
                        local factory;

                        // Remember if we started in our factory.
                        factory = nil;
                        if(isInFactory(self))
                                factory = location;

                        // Handle the normal >TAKE logic
                        inherited();

                        // If we started out in our factory, replace the
                        // stock take message with our custom message and
                        // then tell the factory to refill itself.
                        if(factory) {
                                gMessageParams(factory);
                                resourceTakeDesc;
                                factory.refillFactory();
                        }
                }
        }
;

The ResourceFactory class

Finally the new factory class. It contains almost all of the logic, and includes a couple new properties:

  • maxResources = 1
    The maximum number of resources that the player can take from the factory at a time. Defaults to 1 but can be more or less anything, with the caveat that the factory will create this many resource instances and so it shouldn’t be arbitrarily large unless you understand the potential performance consequences.

  • refillResources = true
    Boolean flag. If true the factory will automatically refill up to maxResources whenever a resource is taken.

    If this isn’t set then the factory will behave more or less like an ordinary container.

In addition there’s a new method worth mentioning:

  • refillFactory(force?)
    This tells the factory to refill itself up to maxResources.

    If refillResources is true this won’t be useful unless something else is pulling resources out of the factory (that is, via some mechanism other than the >TAKE command).

    The utility is calling refillFactory(true) when refillResources is nil. This will force-refill the factory, and is intended to be used to implement things like timer-based resource dispensing limits. A tree that only lets you harvest three apples per day or something like that.

A revised demo

Putting this into a trivial “game”:

class Pebble: Resource '(small) (round) pebble*pebbles' 'pebble'
        "A small, round pebble. "
;

startRoom: Room 'Void'
        "This is a formless void containing a bucket which always contains
        three pebbles. "
;
+me: Person;
+bucket: Fixture, ResourceFactory '(infinite) (pebbles) bucket' 'bucket'
        "It contains...let's see...looks like an infinite number of
        pebbles. "
        resourceClass = Pebble
        resourceReturnable = true
        maxResources = 3
;

This works “correctly” within the bounds of adv3’s standard action-with-count handling:

Void
This is a formless void containing a bucket which always contains three
pebbles.

>take 3 pebbles
pebble: You take a pebble from the bucket.
pebble: You take a pebble from the bucket.
pebble: You take a pebble from the bucket.

>take 3 pebbles
pebble: You take a pebble from the bucket.
pebble: You take a pebble from the bucket.
pebble: You take a pebble from the bucket.

>drop all
pebble: Dropped.
pebble: Dropped.
pebble: Dropped.
pebble: Dropped.
pebble: Dropped.
pebble: Dropped.

>take 4 pebbles
pebble: Taken.
pebble: Taken.
pebble: Taken.
pebble: Taken.

>take 4 pebbles
the pebble on the floor: Taken.
the pebble on the floor: Taken.
the pebble in the bucket: You take a pebble from the bucket.
the pebble in the bucket: You take a pebble from the bucket.

>put all pebbles in bucket
your pebble: Done.
your pebble: Done.
your pebble: Done.

the pebble in the bucket: The pebble is already in the bucket.

the pebble in the bucket: The pebble is already in the bucket.

the pebble in the bucket: The pebble is already in the bucket.

your pebble: Done.
your pebble: Done.
your pebble: Done.
your pebble: Done.
your pebble: Done.

>take 4 pebbles
You don't see that many pebbles here.

>take 3 pebbles
pebble: You take a pebble from the bucket.
pebble: You take a pebble from the bucket.
pebble: You take a pebble from the bucket.

>put 3 pebbles in bucket
pebble: Done.
pebble: Done.
pebble: Done.

I think what I’m going to have to do is just sand off the sharp corners on this by using transcript re-writing, but I really want to be working on other things.

2 Likes