TADS 3 Adv3: How to get and read timed input from the player?

Back again so soon? Yes, I am. Hello, I have another question:

I’m messing with the concept of a minigame, kind of like a trivia-esque sort of thing. Anyways, I want the player to be presented with a question. The player can then input their answer, and something happens if it’s right or wrong. I was doing something kind of like this:

"This is my question. What is your answer?
\n1). a1
\n2). a2
\n3). a3";

local input = inputManager.getInputLine(nil,{: "\b> " });
if (input == '1')
{
	"Correct answer! ";
}
else
{
	"Incorrect answer!";
}

And it worked, until I decided that I wanted the input to be timed. So when, say, 5 seconds passes, the question is timed out, the input is cancelled, and the game ends automatically - pc loses. The issue is that I’m not sure how to go about it.

I first tried messing with real time fuses, calling one 5 seconds after the question is presented (with inputManager.getInputLine set to true instead of nil). The issue with this is that once the game ends, getInputLine() is still waiting for something. The next command entered by the pc triggers the if/else even after the game is ended. It didn’t seem like there was a way to revoke getInputLine() once it was called - is this true?

I also tried it on with inputLineTimeout(), kind of like this:

local str = inputLineTimeout(5000);
 
if (str[1] == InEvtTimeout)
{
	"You took too long! ";
	inputLineCancel(true);
}
else
{

	"OK, you answered in time. But what did you answer? ";
}

However, I’m struggling to incorporate it while also getting the input from the player at the same time. I’m just very unfamiliar with these functions. If the pc answers in time, how can I then get the answered input and respond to that input? From my experience, getInputLine() and inputLineTimeout() don’t appear to play nice with each other, and I think I’m missing something here. What’s a better way to go about this?

I’m not sure how well I articulated my issue, but I’ll be happy to clarify. Thanks for any advice.

1 Like

I haven’t looked into anything whatsoever, but would it work to set up a realtime fuse that calls the inputLineCancel?

THANKS !!!

you have just given the decisive nudge in solving a years long oustanding problem of mine, how to do CYOA scenes in TADS3/Adv3Lite (and, with few changes, also in adv3+scenes extension).

The prototype, highly perfectable is below in its rough glory:

cyoatest: Scene//, EventList
startsWhen = (gPlayerChar.isIn (uplab))
endsWhen = (done == true)
recurring = true // def is nil, unneeded 4 one-off scenes.
pts = 0
done = nil
whenStarting() {
  "let's answer some questions...";
  letscyoa();
}
whenEnding() {"that's all, folks !";}
//eachTurn() {}
//preAction(lst) {}
//eventList = []
finishOff(howEnded) {}

letscyoa() {
"This is my first question. What is your answer?\b
1). a1\n
2). a2\n
3). a3\n";

local input = input('please choose>');
if (input == '1')
{
  "Correct answer! ";
 pts++;
}
else
{
  "Incorrect answer!";
}

  "This is my second question. What is your answer?\b
1). a1\n
2). a2\n
3). a3\n";

local input2 = input('please choose>');
if (input2 == '2')
{
  "Correct answer!\n";
 pts++;
}
else
{
  "Incorrect answer!";
}
  "This is my third and last question. What is your answer?\b
1). a1\n
2). a2\n
3). a3\n";

local input3 = input('please choose>');
if (input3 == '3')
{
  "Correct answer!\n";
 pts++;
}
else
{
  "Incorrect answer!\n";
}

"Thanks for your answers.\n For the record you have given <<pts>> correct 
answers.\n";
done = true;

} //letscyoa
; //cyoatest

thanks again, and

Best regards from Italy,
dott. Piergiorgio.

1 Like

I’ve attempted attaching inputLineCancel() to the realtime fuse in my testing, but it doesn’t seem to have any impact on inputManager.getInputLine, which is why I moved on to inputLineTimeout(). I wasn’t sure if there was something else I should be using instead to cancel the input.

1 Like

I’m glad you were able to solve this problem! I assume the adv3+ scenes extension you’re referencing is the one by Jbg? I have not incorporated any scene extensions as of yet. Though I might look into it if I’m unable to sort this problem without additional extensions.

Cheers,

Might be clunkier than you want, but could the realtime fuse just say ≈ “You’re too late! (Hit Enter to proceed)”

1 Like

no, the scenes extension are the ones written by Eric Eve (the author of adv3Lite; hence the easiness of porting between the scenes extension and adv3Lite’s scene component (and vice-versa)

for the record, eric eve’s scenes.h are here in the IF Archive:

https://www.ifarchive.org/if-archive/programming/tads3/library/contributions/scenes.t

HTH and

Best regards from Italy,
dott. Piergiorgio.

1 Like

I’ve never had cause to play with this, but kind of fired my imagination. Will not have time to play with it until later tonight, but if not solved by then here is what I’m going to try:

  1. Set 5 real-time events, probably one creating the next. The last would invoke inputLineCancel the others would update countdown indicator
  2. Use getKey(true, promptFunc) where true means allow real-time events, and where promptFunc would display the countdown indicator.
  3. If couldn’t make work, devolve to inputLineTimeout, with a timeout case invoking inputLineCancel
3 Likes

I could try going a little clunky as a solution. It would require a good bit of tweaking as the result is pretty awkward.

At the moment, inputManager.getInputLine waits for a response AFTER the realtime fuse is called, which ends the game. Any input after that triggers something. Using my first code example, it would either trigger “Correct answer!“ or “Incorrect answer!“. This would happen in response to and after the realtime fuse has called something like “Game over! Press enter to continue.“, which is a bit odd timeline wise - for obvious reasons.

To counter that, the text can be swapped out and customized, and I could add an if (gameIsActive == true) to the mix for a unique response once the game is ended. The response still prints an odd blank line, and this only applies if the timeout occurs, though. Depending on if you timed out during the game or not, you would get something like this:

Press enter to continue. 

> 

Ok. Game is officially over now. //customized response for game over

>

or

Press enter to continue. 

> 

I beg your pardon? //default "huh?" response from parser

> 

And this is just versus the normal “Press any key to continue”, which looks like this:

"Random text before key press."

Press any key to continue. 

"Random text after key press”

> 

So, over all… it could work? It’s clunky for certain, but I’m sure it could be worked around if it comes to that.

1 Like

In your example the player input will be in str[2]. inputLineTimeout() returns an array. The first element is the return value (whether or not input timed out, which you’re testing for already). The second value is the input.

As a working example, here’s a simple action, >FOOZLE, which prompts the player for a timed response:

DefineSystemAction(Foozle)
        execSystemAction() {
                local r;

                "Prompting for input...\n";
                r = self.inputThing();
                "\nValue = <<toString(r)>>\n ";
        }

        inputThing() {
                local r;

                "\b&gt; ";
                r = inputLineTimeout(2000);
                if(r[1] == InEvtTimeout) {
                        inputLineCancel(true);
                        "\nTime's up\n ";
                        return(nil);
                } else {
                        return(r[2]);
                }
        }
;
VerbRule(Foozle) 'foozle' : FoozleAction
        verbPhrase = 'foozle/foozling';

Transcript (waiting for timeout in the first case, typing a response in the second):

>foozle
Prompting for input...

> 
Time's up
Value = nil

>foozle
Prompting for input...

> x
Value = x

3 Likes

This is perfect. Thank you for breaking down how inputLineTimeout() works. I was able to incorporate your explanation into the minigame, and things are working great!

1 Like

No problem. The inputLineTimeout() semantics are explained in comments in tadsio.h in the TADS3 distribution and I think nowhere else. So it’s a little obscure.

There are also wrappers around inputLineTimeout() and inputLineCancel() called aioInputLineTimeout() and aioInputLineCancel(). They’re both straight wrappers—they’re just aliases, they don’t implement any additional logic. Which I mention because there’s an example of use in inputManager.getInputLineExt(). It includes handling for the various other return values from aioInputLineTimeout() apart from InEvtTimeout. Which I don’t think are explained anywhere else.

2 Likes

Ftr, could not make 1 and 2 work. getKey does not seem to have an obvious inputLineCancel capability. @jbg beat me to#3!

2 Likes

So, I have a sickness. I tried, but simply could not leave this alone. What I am about to share is both WAAY overkill, and deeply neurotic. Here’s the thing. I have a strong preference for the zippy gameplay ofgetKey where single character choices are requested, over getLine which requires A WHOLE EXTRA CR. UNACCEPTABLE!!!

I also really, really wanted a countdown indication.

To enable this requires a new, specialized capability based on adv3 inputManager getEventOrKey(). The new method basically combines a narrowed version with a real-time fuse to detect either its firing, or an approved keystroke.

Look on my works ye mighty and despair:

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

versionInfo: GameID;

gameMain : GameMainDef
    initialPlayerChar = me

    // countdown mgt attributes
    count = 5  // would need reset if reused
    tiktikboom() { count--; }

    showIntro() {

        local choice = nil;

        "Choose one, quickly:\n\n
        \t1.  One\n
        \t2.  Two\n
        \t3.  Three";

        do {
            choice =
                inputManager.getKeyTimeout(1000,
                    {: "\n<<charX(count, '.')>> >"}, ['1','2','3'],
                    self, &tiktikboom);
        } while ((count > 1) && (choice == nil));
        if (choice == nil)
            "\nTime\'s up!<p>";
        else
            "You chose <<choice>>.<p>";

    }
;
startRoom: Room 'Featureless Room'
    "This is a featureless room.  "
;
+me: Person ;

 /* ====================================================================
 *
 *   getKeyTimeout
 *
 *  single key input handler, with timeout, based on input.t getEventOrKey()
 *  stripped down, special purpose event handler, starts realtime fuse
 *  and returns entered key or nil if times out
 *  Parameters:
 *      timeout - time value in ms
 *      promptFunc - function that displays prompt for input
 *      validKeys - optional list of keys to accept.  if not provided (nil)
 *                  accept any key
 *      fuseObj - optional object that implements method executed on fuse
 *                expiry.  if none provided, default to do-nothing
 *      fuseProp - if fuseObj specified, pointer to method executed on
 *                 expiry.  if none provided, default to local
 *                 placeholder do-nothing method
 *
 *  Returns:
 *      character typed, or nil if none (timed out)
 *
 * ====================================================================
 */
 modify inputManager
    gkTimeoutMethod() { } // default do-nothing timeout trigger

    getKeyTimeout(timeout, promptFunc, validKeys?,
                    fuseObj=self, fuseProp=&gkTimeoutMethod) {

        // make sure the command transcript is flushed
        if (gTranscript != nil)
            gTranscript.flushForInput();

        local toFuse = new RealTimeFuse(fuseObj, fuseProp, timeout);

        // keep going until we get a keystroke or timeout
        for (;;) {
            local result;
            local timedOut;

            // process real-time events, if possible
            timedOut = processRealTimeEvents(true);

            // show the prompt and any pre-input codes
           inputEventBegin(promptFunc);

        getInput:
             //  Read the input.  (Note that if our timedOut is nil, this
             //  will simply act like the ordinary untimed getKey.)
            result = aioInputEvent(timedOut);

            // check the event code from the result list
            switch(result[1]) {
            case InEvtNoTimeout:
                 //  the platform doesn't support timeouts - note it for
                 //  future reference so that we don't ask for input with
                 //  timeout again, then go back to try the input again
                 //  without a timeout
                 //
                noInputTimeout = true;
                timedOut = nil;
                goto getInput;

            case InEvtTimeout:
                //  We got a timeout without finishing the input line.
                //
                inputEventEnd();
                return nil;

            case InEvtKey:
                // keystroke - if valid finish the input and return the value
                //
                if ((validKeys == nil) || validKeys.indexOf(result[2])) {
                    toFuse.removeEvent();
                    inputEventEnd();
                    "<<result[2]>>\n";
                    return result[2];
                } else
                    break;

            default:
                // ignore other events
                break;
            }
        }
    }
;
/* ====================================================================
 *
 *   charX utility function
 *
 *  Produces a string of requested length of repeated character
 *  used to align proportional font output
 *  Parameters:
 *      num - repeat count 
 *      char - character to repeat.  If not given, default to unicode blank
 *
 *  Returns:
 *      repeated string
 *
 * ====================================================================
 */
charX(num, char = '\u2007') {
    local str = '';
    if (num == 0) return '';
    for(local i = 1; i<= num; i++) str += char;
    return str;
}

This provides output like so

Choose one, quickly:
           1.  One
           2.  Two
           3.  Three
..... >
.... >
... >3
You chose 3.

Featureless Room
This is a featureless room.

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

Choose one, quickly:
           1.  One
           2.  Two
           3.  Three
..... >
.... >
... >
.. >
. >
Time's up!

Featureless Room
This is a featureless room.

>

That’s it! No one may ever use it, but now I AM FREE!!!

4 Likes

Love it, neurosis and all…

2 Likes