Counting the Cash
That the handling of cash could actually be simplified if one stops to think about implementing a more general solution shows that there's often more than one way to make a mousetrap in code, and that the first workable solution one comes up with isn't necessarily the best, the easiest or the most elegant. Even if we wanted to stick to having four coins in our game rather than a more abstract concept of money, we could have handled it better, and produced a better-looking output as a result. So for the sake of completeness we'll look at another way this could have been handed, although it is not exactly for the faint-hearted and introduces some techniques that are really rather advanced for a Getting Started guide; it may thus be this is something you'll want to skip on first reading.
The way we went about it before, using a fuse to sum up the result of handing over multiple coins in one turn, is perfectly workable, but the library does offer another way of doing it which, if not a great deal simpler, at least offers better control over what is displayed to the player. This is illustrated by the routine for handing coins to Bob in the sample game. We can adapt that code to our situation by redefining the shopkeeper's GiveTopic object for coins thus:
|
matchTopic(fromActor, obj)
{
return obj.ofKind(Coin) ? matchScore : 0;
}
handleTopic(fromActor, obj)
{
shopkeeper.cashReceived ++;
currency = obj;
if(shopkeeper.cashReceived <= shopkeeper.price)
obj.moveInto(shopkeeper);
/* add our special report */
gTranscript.addReport(new GiveCoinReport(obj));
/* register for collective handling at the end of the command */
gAction.callAfterActionMain(self);
}
afterActionMain()
{
/*
* adjust the transcript by summarizing consecutive coin
* acceptance reports
*/
gTranscript.summarizeAction(
{x: x.ofKind(GiveCoinReport)},
{vec: 'You hand over '
+ spellInt(vec.length())+' ' + currency.name+'s.\n' });
if(shopkeeper.saleObject == nil)
{
"<q>What's this for?</q> asks {the shopkeeper/she}, handing the
money back, <q>Shouldn't you tell me what you want to buy
first?</q>";
shopkeeper.cashReceived = 0;
}
else if(shopkeeper.cashReceived < shopkeeper.price)
"<q>Er, that's not enough.</q> she points out, looking at you
expectantly while she waits for the balance. ";
else
{
"{The shopkeeper/she} takes the money and turns to take
<<shopkeeper.saleObject.aName>>
off the shelf. She hands you <<shopkeeper.saleObject.theName>> saying,
<q>Here you are then";
if(shopkeeper.cashReceived > shopkeeper.price)
", and here's your change";
".</q></p>";
shopkeeper.saleObject.moveInto(gPlayerChar);
shopkeeper.price = 0;
shopkeeper.cashReceived = 0;
shopkeeper.saleObject = nil;
}
}
currency = nil
;
|
Clearly this is not the whole story; we have also replaced the entire fuse mechanism. In effect the call to gAction.callAfterActionMain(self) in handleTopic does a job analogous to the call to shopkeeper.cashFuseID = new Fuse(shopkeeper, &cashFuse, 0) that it replaces, in that it registers that once we have iterated over all the coins being given in this command, we want to handle the aggregate result of the transaction in the afterActionMain() method of self, i.e. the current object. Note that unlike the code to create a new fuse, there is no need to check that this has not been called on a previous iteration, since the registration will only be effective first time round. So far, there is not a great gain of simplicity compared with using the fuse to do the same job, but we are at least using a library mechanism designed to do the job we want, rather than trying to invent our own ad hoc mechanism, and this does allow all the code for handling the giving of coins to be put on the appropriate GiveShowTopic.
But we have not exhausted what this alternative way of designing this a particular mousetrap can do for us, even though the part that remains is frankly not the easiest thing to grasp first time round. The trouble with the way we did it before was that for each coin Heidi handed over to Sally the shopkeeper (if there were several), the game reported "pound coin: " on a new line. We mitigated this a little by trying to make it look as if the coins were being counted out:
|
pound coin:
|
pound coin: number 2
|
pound coin: number 3
|
|
Firstly, we define what our own output for each line should be through the call to gTranscript.addReport(new GiveCoinReport(obj)). This actually does two things for us; first it allows us to define what will reported if only a single coin is handed over, and secondly it gives us a class name we have defined ourselves (GiveCoinReport) which we'll be able to use to manipulate the final report displayed if there's more than one coin handed over.
For this to work, we need to define the GiveCoinReport class:
|
construct(obj)
{
/* remember the coin we accepted */
coinObj = obj;
/* inherit the default handling */
gMessageParams(obj);
inherited('You hand over {a obj/him}. ');
}
/* my coin object */
coinObj = nil
;
|
Then comes the really clever (and complicated part); in order to replace the multiple reports of "You hand over a pound coin" that we'd otherwise see, we include the following code in the afterActionMain() method:
|
gTranscript.summarizeAction(
|
{x: x.ofKind(GiveCoinReport)},
|
{vec: 'You hand over '
|
+ spellInt(vec.length())+' ' + currency.name+'s.\n' });
|
|
Even so, unless you're familiar with the code structure here, the line we're examining may still look rather like an arcane magical incantation; well, it's not quite that, but it's close to being the next best thing - a method call involving anonymous callback functions (if you don't feel any the wiser for being told that, don't worry; this is not the most self-evident topic). Rather than confuse you any further by trying to explain exactly what anonymous callback function are, I'll try to offer some explanation for what they do here. gTranscript is an object of the CommandTranscript class. We are invoking its summarizeAction(cond, report) method. But cond and report are not any old common-or-garden parameters of the sort we were all brought up or feel at least moderately comfortable with; it turns out that they are functions, functions that the summarizeAction method will use in the form cond(cur) and report(vec). The first of these defines the condition that must apply to the report lines that we want to replace with our single summary report, and the second defines what that summary report will look like.
Our call to gTranscript.SummarizeAction is thus passing two arguments that are in effect short form function definitions. The first parameter, {x: x.ofKind(GiveCoinReport)}, in effect tells the SummarizeAction method to treat cond() as if it were defined as:
|
{
return x.ofKind(GiveCoinReport);
}
You may remember that GiveCoinReport was the custom report class we defined a little way back, so what we're effectively telling the SummarizeAction with this is "look out for those reports of the GiveCoinReport class, they're the ones we want you to count up and replace for us."
Similarly, the second parameter is passed as {vec: 'You hand over ' + spellInt(vec.length())+' ' + currency.name+'s.\n' }. This effectively tells SummarizeAction to treat report(vec) as if it had been defined as:
|
{
return 'You hand over ' + spellInt(vec.length())+' ' +
currency.name+'s.\n';
}
|
Getting Started in TADS 3
[Main]
[Previous] [Next]