adv3Lite: Do Need a LiteralTIAction?

I know, there is no such Action type. What I’m pondering is how to implement the command ‘read page 17 in book’. There may be as many as 30 different numbered pages where information is to be read, but there are also hundreds of other numbers that will need to produce nothing helpful (using a ShuffledEventList). The book object is presumably the gDobj in this action. And I can create Topics with the name ‘page 17’, ‘page 291’, and so forth. The problem is what to do if the user types a non-implemented number. Currently I’m getting this:

…and that won’t work at all. The parser is failing to understand the input. I need for the parser to understand that ‘page’ is sensible, and that ‘book’ is also sensible (that is, there are two objects, the page object being a Component of the book object), but that the number is an unrecognized string literal.

I have no theory of how to make this work. Suggestions welcome!

The section on Actions with Three Objects at the end of the chapter on Defining New Actions in the Adv3Lite Library Manual has some discussion of defining, or at least faking, actions like LiteralTIActions, so that might be a good place to start if you haven’t looked at it already.

In brief, you’d need to define a TIAction but define a VerbRule with a third slot, something like:

[code]
DefineTIAction(ReadIn)
execAction(cmd)
{
/*
* Store the literal value from the aobj (or acc) property of
* the current command object.
*/

    literal = cmd.aobj.theName;
    
    /* Then carry out the inherited handling. */
    inherited(cmd);
}

literal = nil

;

VerbRule(ReadIn)
‘read’ singleDobj literalAObj ‘in’ singleIobj
: VerbProduction
action = ReadIn
verbPhrase = ‘read (what) (in what) (what)’
missingQ = ‘what do you want to read; what do you want to read;
what do you want to read in’
;[/code]

That said, I’m surprised you can’t get this to work with a TopicTAction, something like:

DefineTopicTAction(ReadIn)
;

VerbRule(ReadIn)
    ('read') topicIobj 'in' singleDobj
    : VerbProduction
    action = ReadIn
    verbPhrase = 'read (what) (in what)'
    missingQ = 'what do you want to read in; what do you want to read'
    dobjReply = singleNoun
    
    priority = 60 // You may need to add this to avoid the "You can't see any page 10 in book" problem
;

This would seem to me to be a simpler solution; you could then perhaps treat the book as a Consultable and the pages as ConsultTopics (or create a similar kind of setup).

Disclaimer: I’m not at home and I don’t have TADS 3 on this machine, so I can’t test any of this, but hopefully it will gice you some pointers.

EDIT: Now I am back home and have been able to test it. Here’s a quick example that seems to work okay along with the definition of the ReadIn action above:

+ book: Consultable 'book'
    dobjFor(ReadIn) asDobjFor(ConsultAbout)
;

++ ConsultTopic @tPage1
    "That's just the title page. "
;

++ ConsultTopic @tPage2
    "That's just the dedication. "
;

++ DefaultConsultTopic
    "The book doesn't have that many pages. "
;

tPage1: Topic 'page 1';
tPage2: Topic 'page 2';

Obviously it needs to be embedded in a complete (if minimal) game to work. With this I get the following output:

The Living Room
The living room

You can see a book here.

>get book
Taken. 

>read page 1 in book
That’s just the title page. 

>read page 2 in book
That’s just the dedication. 

>read page 3 in book
The book doesn’t have that many pages. 

I may have misunderstood the original question, but Eric’s solution doesn’t exactly solve what I perceived to be Jim’s problem.

I read the description of the problem to mean a book of some 300 pages, with meaningful content on a small number of them defined in advance, nonsense content generated by an event list of some kind on most of them, and “not that many pages” if anything above 300 is requested.

Eric’s solution, it seems to me, limits the pages that will have any content at all to only those that are predicted in advance—pages for which there is a named topic.

My solution (which cribs the Verb rule from Eric’s) can handle any number of pages. Here’s output where the book is 300 pages long. I have text for pages 35 and 291, with any other page below 300 pulling text from a random event list.

Here’s the code, which intercepts the command entered on the command line and parses if for page number, then replaces the command text with reference to a single ConsultTopic that allows me to trap the action in dobjFor(ConsultAbout)

#charset "us-ascii"

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

versionInfo: GameID
    IFID = '445C38A3-AD1B-4729-957A-F584600DE5C1'
    name = 'test'
    byline = 'by Jerry Ford'
    htmlByline = 'by <a href="mailto:jerry.o.ford@gmail.com">
                  Jerry Ford</a>'
    version = '1'
    authorEmail = 'Jerry Ford <jerry.o.ford@gmail.com>'
    desc = 'Testing book pges'
    htmlDesc = 'Testing book pages.'

;

gameMain: GameMainDef
    initialPlayerChar = me
    paraBrksBtwnSubcontents = nil
   
;

me: Actor 'me' @livingRoom
    "The main man.<.p>"
    isHim = true
    person = 2
;

livingRoom: Room 'The Living Room' 
    "The living room."
;
+ book: Consultable 'book'
    
    pageRequested = 0
    pageNum = new BigNumber(pageRequested)

    dobjFor(ReadIn) asDobjFor(ConsultAbout)
    dobjFor(ConsultAbout)
    {
        action()
        {
            if(pageRequested == '35')
            {
                "On page 35, Newtons 3rd law is examined in excrutiating
                detail.";
            }
            else if(pageRequested == '291')
            {
                "The precise cause of the fall of Rome is examined, beginning on
                page 291.";
            }
            else if(pageNum > 300)
            {
                "There aren't that many pages in the book.<.p>";
            }
            else
                getPageText();
        }
    }
;

++ ConsultTopic @tPage
;
tPage: Topic 'page';

DefineTopicTAction(ReadIn)
;

VerbRule(ReadIn)
    ('read') topicIobj 'in' singleDobj
    : VerbProduction
    action = ReadIn
    verbPhrase = 'read (what) (in what)'
    missingQ = 'what do you want to read in; what do you want to read'
    dobjReply = singleNoun
    
    priority = 60 // You may need to add this to avoid the "You can't see any page 10 in book" problem
;

StringPreParser
    doParsing(str,which)
    {
        if (str.toLower.startsWith('read page ') &&
            str.toLower.endsWith(' in book'))
        {
            local words = str.split(' ');
            book.pageRequested = words[3];
            str = 'read page in book';
        }
        return str;
    }
;
getPageText()
{
    return text.doScript();
}
text: RandomEventList
{
    eventList = 
    [
        'Drivel. <.p>',
        
        'Blather. <.p>',
        
        'Stuff \'n such. <.p>',
        
        'Such as it may appear. <.p>'
    ]
};
 

(EDIT—the string if if statements in dobjFor(ConsultAbout).action() needs to be a string if if/else statements. Change made.)

I can see where you’re going with this, Jerry. I’m going to experiment with it. But it suffers, I think, from a very serious defect, which is that I would have to hand-code every single combination of words the player might use to refer to the book. ‘in book’, ‘in the book’, ‘in the leather-bound book’, ‘in the fat book’, ‘in fat book’, ‘in leather book’, … and the book is called “My Travels,” so I’ll also have to include ‘in my travels’, ‘in travels’, ‘in travels book’… (Reminds me of ADRIFT, for some reason. Or maybe I’m thinking of Quest.) No, I hope there’s a better way.

I think I’ve got it. This may still be a tiny bit buggy … haven’t checked all of the possible edge cases yet. But by combining Jerry’s idea with the idea of defining a new LiteralAction and LiteralTAction, I was able to get it. This code isn’t really a spoiler for the game, as the page numbers will change, and the book itself won’t be too hard to find (although … well, that would be a spoiler).

[code]DefineLiteralAction(ReadLiteral)
execAction(cmd) {
if (travelGuide.isIn(gPlayerChar)) {
doInstead(ReadLiteralIn, travelGuide, gLiteral);
}
else "There seems to be no reading material nearby that addresses that topic. ";
}
;
VerbRule(ReadLiteral)
(‘read’ | (‘turn’ ‘to’)) literalDobj
: VerbProduction
action = ReadLiteral
verbPhrase = ‘read/reading (what)’
missingQ = ‘what do you want to read’
;

DefineLiteralTAction(ReadLiteralIn);
VerbRule(ReadLiteralIn)
(‘read’ | (‘turn’ ‘to’)) literalDobj ‘in’ singleIobj
: VerbProduction
action = ReadLiteralIn
verbPhrase = ‘read/reading (what) (in what)’
missingQ = ‘what do you want to read; what do you want to read that in’
iobjReply = singleNoun
;
modify Thing
dobjFor (ReadLiteralIn) {
verify() { }
check() {
"Nothing is written in {the iobj}. ";
}
}
;

// This useless action handles the possibility that the player might type
// ‘read door in book’ when there is actually a door in scope. Without it,
// the string ‘door’ would be interpreted as a literal, causing a run-time
// error:
DefineTIAction(ReadSomethingIn);
VerbRule(ReadSomethingIn)
(‘read’ | (‘turn’ ‘to’)) singleDobj ‘in’ singleIobj
: VerbProduction
action = ReadSomethingIn
verbPhrase = ‘read/reading (what) (in what)’
missingQ = ‘what do you want to read; what do you want to read that in’
;
modify Thing
dobjFor(ReadSomethingIn) {
verify() { illogical (’{The subj dobj} is not readable. '); }
}
iobjFor(ReadSomethingIn) {
verify() { illogical ('You can’t read {the dobj} in {the iobj}. ');
}
}
;

travelGuide: Consultable
‘fat leather-bound book; (my) leather bound well-thumbed well thumbed thick travel; guide travels’
@endTable
“The leather-bound book is a couple of inches thick, and well-thumbed. On the cover,
in ornate gold-embossed letters, it says, My Travels by Sir Ralph Warburton-Leaming.
readDesc = "The book is far too thick to read from cover to cover. "
dobjFor(Read) {
preCond = [objVisible, objHeld]
}
dobjFor(ConsultAbout) {
preCond = [objVisible, objHeld]
}
// a handy utility function:
getPageNum (str) {
local words = str.split(’ ');
local x = new BigNumber(words[2]);
return x;
}
dobjFor(ReadLiteralIn) {
preCond = [objVisible, objHeld]
check() {
local str = gLiteral;
if (str.toLower.startsWith('page ')) {
local x = getPageNum(str);
if (x > 600) "Flipping clear to the back, you find that the book has only
600 pages. ";
else if (x < 1) "The book is certainly thicker than that. ";
else if ((x != 19) && (x != 124) && (x != 301)) {
pageText.doScript();
exit;
}
}
else "You flip through the book at random, but find nothing relevant. ";
}
action() {
local str = gLiteral;
local x = getPageNum(str);
if (x == 19) "On page 19 you find a fascinating discussion of the
pollination of the hibiscus. ";
else if (x == 124) "On page 124 you find a complete explanation of
the quantum theory of gravity. ";
else if (x == 301) "On page 301 you find a doodle of a rabbit. ";
else "Error – this should never print. ";
}
}
// a list of messages telling you you haven’t found anything useful on that page:
pageText: ShuffledEventList { [
'Blah blah blah. ',
'More blah blah blah. ',
'We are poor little lambs who have blah blah blah. ',
'Yer grandma wears army boots. ’
] }
;[/code]

So what you want is for the TADS parser to be responsible for matching the text the player input on the command line with the vocab property of your book, and let the game engine act accordingly.

The purpose of the code I suggested was to suppress the page number so that the ConsultAbout command could act on a single tPage topic, rather than you having to program a separate tPage topic for every page in your book.

For my suggested approach, I was happy with just “read page in book” but if you want to preserve the text the player entered in its entirety (minus an actual page number), this will do it…

StringPreParser
    doParsing(str,which)
    {
        if (str.toLower.startsWith('read page '))
        {
            local words = str.split(' ');
            book.pageRequested = words[3];
            local wordCount = words.length;
            local wordAdded = 1;
            local wordAddedType = dataType(wordAdded);
            local newStr = '';
            while(wordAdded <= wordCount)
            {
                if(wordAdded != 3)
                    newStr += words[wordAdded] + ' ';
                wordAdded++;
            }
        }
        return newStr;
    }
;

As for data type of the pageNum property (which you raised in a separate forum post), doesn’t matter what the user entered. If it was text, not a numerical reference, then pageNum will be set to 0. (note the decimal). If your code sets the viable page range for the book at 1-300, then pageNum of 0 will fall out of that range, and your code can react accordingly.

With these two concepts together, any of the following would be a legal command, as long as you have implemented books with vocabulary and page ranges…

read page 3 in the big bad book of knowledge
read page three in the little bitty book of rhymes
read page somesuch in the old leather bound volume

That makes sense. My design preference, though, is still to avoid manually parsing the input whenever possible. That kind of code is hard to maintain and debug. A simple example: You’re assuming an input that starts with ‘read page’. An equally likely input is ‘turn to page’ – and that requires a different word count. And if the player happens to type ‘open book to page’ or ‘look in book at page’ … oh, my, that’s two more possible word counts. Defining all this stuff in a VerbRule is much easier to maintain. That’s why I’m going to stick with a LiteralTAction, at least for now.

Thanks for illustrating PreParser code, though! It may come in handy at some point.