Can you achieve shuffled or <<stopping>> "illogical" messages?

I am a complete sucker for varying my messages, or for making a wry parser riposte the first time but printing something more neutral on succeeding repetitions. Clearly, shuffled and stopping as embedded expressions are fundamental tools for accomplishing that in most applications.
Most TADS users quickly become aware, however, that verify routines are called numerous times “behind the scenes” before the action is ultimately carried out upon entering a command. And every time that “invisible” verify is called, the illogical message gets invisibly evaluated, causing the shuffled/stopping mechanisms to advance even though their results aren’t seen. Practically speaking, you can never do a [first response/subsequent response] setup in an illogical message. The shuffling ends up looking more like a call to rand.
Does anyone have any neat solutions for this? I know that I thought about it a couple years ago, but I was feeling the crunch of trying to complete a massive game and left my investigations without having found anything really satisfactory.

If you’re really desperate for a first/subsequent, you can just use <.reveal> tags, but that gets cumbersome for wholesale use. A few times I made use of a custom <.ver> tag, where the illogical message was just the tag and its argument, and the conversationManager replaced the tag with a message property given by the tag argument. That way the actual message was not evaluated repeatedly.
But it had limitations, and was prohibitively cumbersome as well.
If anyone wants an academic challenge of how to effect what I’m asking for, I’d love to hear about it!

2 Likes

I’ve run into this too, and to avoid a lot of hackery, I almost always opted to have the message emitted in check(), although that has its problems as well (especially if it’s important to mark the Action as illogical).

I’m not in a position to test this out, but I wonder if you could something like this:

illogicalMsgs = '''
<<one of>>
  Nope!
<<or>>
  No way.
<<or>>
  Nuh-uh.
<<shuffled>>
'''

illogicalMsg = nil

nextIllogicalMsg() {
  if (!illogicalMsg)
    illogicalMsg = illogicalMsgs;

  return illogicalMsg;
}

dobjFor(DoSomething) {
  verify() {
    if (!canDoSomething)
      illogical(nextIllogicalMsg);
}

…and then use a PromptDaemon to set illogicalMsg to nil at the end of each turn.

I’d have to think more about a way to automate this and avoid having to repeat this logic every place you need it, but that’s my first thought (which is not always the best one!)

2 Likes

Thanks for your input! And your idea has set a few gears turning in my head. Also without testing, I doubt if PromptDaemon can be the primary accomplisher of the objective (if illogicalMsg is changed in verify itself), because verify can be called about a dozen times between the time you hit “Enter” and the moment you see the result appear on the screen…
But your idea of having the illogicalMsg property be nil until it is needed could possibly work! I will look and see if I can find a point where the verify results “winner” is known and about to be printed… if I can, we could do your illogicalMsg = illogicalMsgs at the right moment, and then the PromptDaemon could do the resetting after that. (Or maybe even a try/finally.)
Thanks Jim, gonna go take a look at this!

(Afterthought: I guess the real challenge will be, like you mentioned, how to make this some kind of universal behavior so not every dobjFor has to register somewhere for it to work…)

@jnelson I think I see my hook in doActionOnce. Don’t have time at the moment, though, to try to figure out the universal behavior aspect…

For the specific case of handling the failure differently the first time I just stuff the logic into action(), via something like:

modify playerActionMessages
        cantTakePebble = '{You/He} can\'t take {the dobj/him}. '
        cantTakePebbleFirst = 'The idea that {you/he} could take {the dobj/him}
                is ridiculous. '
;

+pebble: Thing 'small round pebble' 'pebble'
        "A small, round pebble. "

        msgFlag = nil

        dobjFor(Take) {
                verify() {
                        if(msgFlag)
                                illogical(&cantTakePebble);
                }
                action() {
                        if(!msgFlag) {
                                reportFailure(&cantTakePebbleFirst);
                                msgFlag = true;
                        }
                        inherited();
                }
        }
;

As a first pass at handling multiple actions cleanly this can be adapted to use a LookupTable for the flags:

class MsgToggle: object
        msgToggles = perInstance(new LookupTable)
        _gPhrase() { return(gAction ? gAction.getPredicate().verbPhrase: nil); }
        checkMsgToggle { return(msgToggles[_gPhrase()]); }
        setMsgToggle { msgToggles[_gPhrase()] = true; }
;
+pebble: Thing, MsgToggle 'small round pebble' 'pebble'
        "A small, round pebble. "

        dobjFor(Take) {
                verify() {
                        if(checkMsgToggle())
                                illogical(&cantTakePebble);
                }
                action() {
                        if(!checkMsgToggle) {
                                reportFailure(&cantTakePebbleFirst);
                                setMsgToggle();
                        }
                        inherited();
                }
        }
;

This uses Action.verbPhrase as a unique-ish key. Without looking deeper into it I don’t know if there’s a more reliable way to uniquely identify the action class without using reflect.t.
You can’t just use gAction itself as a key because it’s a new instance every turn/action, and you can’t rely on the superclass list because of how classes work in TADS3.

2 Likes

That’s an interesting approach… as mentioned before, I’m afraid I’m greedy! and I’d like to find a solution where I don’t have to define a second message property at all. Or rather, I like to use string values in my illogical macros because the scope of my NPCs using scripted library actions (as opposed to doing tailored stuff in AgendaItems) is very limited and I can virtually always assume that gActor is the PC when it comes to specialized fail messages… so really I just want to be able to put strings into illogical.
I’m hoping to find a solution where I can crank out responses with this kind of minimalism:

dobjFor(Verb) {
   verify { 
      if(cond) illogicalStop('<<one of>>We had hoped it would
              readily apparent that you cannot verb the object
              when cond is true. <<or>>You can't verb the object
              right now. <<stopping>>');
   } }

I’ve got some ideas but we’ll see if they pan out…

1 Like

That’s the idea behind using PromptDaemon. You can call nextIllogicalMsg() many times during the same turn and always get the same result. At the end of the turn, the PromptDaemon sets illogicalMsg to nil and the list of messages rotates to the next one.

Now that I have a little more time, I put this together:

class SameMessagePerTurn: InitObject
  msgs = ''

  nextMsg() {
    if (_currMsg == nil)
      _currMsg = msgs;

    return _currMsg;
  }

  endOfTurn() {
    _currMsg = nil;
  }

  _currMsg = nil

  _pd = nil

  // init
  execute() {
    _pd = new PromptDaemon(self, &endOfTurn);
  }
;

Thing 'solid' @gameStart
  moveMsgs: SameMessagePerTurn {
    msgs = '<<one of>>
      one
    <<or>>
      two
    <<or>>
      three
    <<or>>
      four
    <<cycling>>'
  }

  dobjFor(Move) {
    verify() {
      illogical(moveMsgs.nextMsg);
    }
  }
;

Yielding these results:

> move solid
one

> move solid
two

> move solid
three

> move solid
four

But if I make this change to the code:

      // don't do this!
      illogical(moveMsgs.msgs);

I get these results:

> move solid
two

> move solid
four

> move solid
two

> move solid
four

…which is the problem that nextMsg() avoids.

I could see doing a lot of improvements to SameMessagePerTurn—including finding a better name for it—but that’s the skeleton of one approach to solving the verify() problem.

2 Likes

I stand corrected regarding the prompt daemon! I’ve been away from gamemaking for awhile, and I forgot that when you assign a <<.one of>> string somewhere, it only copies the current iteration and doesn’t reproduce the ability to keep on <<.one of>>ing. Hence I was imagining that the multiple verify calls would advance the state of _currMsg as well.
But, given that with

Thing
propA =  '<<one of>>1<<or>>2<<or>>3<<or>>4<<stopping>>'
methodA { local b = propA; 
          // say(b) multiple times;  }

even though b will permanently be 1, as propA has already moved on to 2 just by being assigned, I don’t know that I have much hope of creating a custom IllogicalVerifyResult that can take <<.one of>> strings as arguments, because that argument gets assigned to a property in the process of construction…

Generally, I prefer keeping the stock illogical messages (if don’t clash with the narrative mood or style, of course) as cues to the player about the illogicality efforts. and nonstandard, varied response messages is a sure way to stuck players into futile efforts to do illogical actions.

Best regards from Italy,
dott. Piergiorgio.

2 Likes

Not a bad plan, master Piergiorgio… but my typical application for this kind of thing is a specific verb response to a specific game world object, essentially guaranteed to only be triggered by the player character. I don’t typically think of employing the first/subsequent pattern to stock messages…

How could I have forgotten my faithful old friends the anonymous functions?! Voilá, my friends, you can indeed make this work*:

Thing 'thing' 'thing' 
  dobjFor(Eat) {
    verify { 
      if(cond) illogStop({:"<<one of>>1<<or>>2<<stopping>>"}); } }
;

Here’s the very minimal how:

class IllogStopVerifyResult : IllogicalVerifyResult
	msgFunc = nil
	construct(func) {
		msgFunc = func;
	}
	showMessage{
		(msgFunc())();
	}
;

#define illogStop(msg) \
    (gVerifyResults.addResult(new IllogStopVerifyResult(msg)))

// We could make a class and a #define for all 
// versions (illogicalNow etc.)

Now for that asterisk. What I did above would probably have satisfied the needs of my game, and so I probably would have left it at that.
Note that as written, embedded expressions and message params still work. HOWEVER: in my example they are evaluated at the time the verify fail msg is printed, whereas in normal verify results, the message text is resolved at the time the result is constructed.
In other words, if you tried to get too tricky with embedded/param material, there is some possibility that somewhere along the line a {the dobj/he} might evaluate to something you didn’t intend. But as I said, I wasn’t too concerned about that because practically every situation where I wanted to use “stopping” in verify, I was sufficiently confident about the environment it would print in.
I suspect that if resolving the message text immediately is something you were concerned about, more work could be done to ensure that, while still allowing the terse usage syntax shown at the beginning.

In the end, we get the happy result:

>eat thing
1
>eat thing
2

SEPARATE ACADEMIC QUESTION FOR TADS GURUS

If we go back here, I am completely confuddled:

class IllogStopVerifyResult : IllogicalVerifyResult
	showMessage{
		(msgFunc())();
	}

Anything look funny? I had to call the result of calling msgFunc. Here’s why:

  construct(func) {
         func(); // this would print a 1
         msgFunc = func;
         msgFunc(); // this yields another anonymous
                    // function pointer!
  }

Can anonymous funcptrs not be assigned normally (or in other words, why did assigning it end up as a pointer to a pointer)? I don’t remember coming across anything like that in the system manual.
Anyone got an explanation?

@jbg and @jnelson, I appreciate the input, I probably should have been more specific in the OP that I was looking for a solution that allowed putting string values into the illogical macro…

1 Like

You’re trying to create a method but you’re using a property.

What you want is probably something like:

class IllogStopVerifyResult : IllogicalVerifyResult
        msgFunc = nil
        construct(fn) { setMethod(&msgFunc, fn); }
        showMessage() { (msgFunc)(); }
;
1 Like

@jbg, you’re right, I actually tested out setMethod earlier with an anonymous method (but I hadn’t tried using an anonfunc as argument); my question was more academic, namely, can’t funcptrs be assigned and copied? Why did I end up with a pointer to a funcptr?

@jbg I see you’re replying but I got a little more light…
I see that you can copy a funcptr in a local variable, and it can be called. It’s the fact that I assigned it to a property that made it act odd. Still not sure if I would’ve called that it would assign as pointer to a pointer though…

They can, but I think they’re structured differently than you’re expecting—the arg the constructor is getting contains both the anonymous function and the context (the object that called the constructor).

You can see this by compiling with reflect.t and then doing something like:

class IllogStopVerifyResult : IllogicalVerifyResult
        msgFunc = nil
        construct(fn) {
                aioSay('\nfn = <<toString(fn)>>\n ');
                setMethod(&msgFunc, fn);
        }
        showMessage() { (msgFunc)(); }
;

…then defining an object like:

+pebble: Thing '(small) (round) pebble' 'pebble'
        "A small, round pebble. "
        dobjFor(Examine) {
                verify() { illogStop({: "<<one of>>Foo.<<or>>Bar.<<stopping>>" }); }
                action() { defaultReport('This is the action. '); }
        }
;

…gets you…

>x pebble
fn = {anonFunc},pebble
fn = {anonFunc},pebble
fn = {anonFunc},pebble
Foo.

>x pebble
fn = {anonFunc},pebble
fn = {anonFunc},pebble
fn = {anonFunc},pebble
Bar.
1 Like

As a further note to any inquiring readers, I’ve been using “stopping” as the example (and in the class name), but this code will produce correct shuffled behavior as well (in those situations where shuffled is to be desired over random).

A second further note to the world at large is that if you really want stopping/shuffled messages in verify and you want to be certain that the text is processed the same way that the library ordinarily does it, you should check out the approaches provided above by @jnelson or @jbg

…aaaaand Bookmarked! Nice.

2 Likes

In my excitement at “re-remembering” the boon of anonymous functions, I forgot that the macro can do more work for us. We can now do this (no anonfunc syntax in the usage)!:

verify { 
  illog('<<one of>>This. <<or>>That. <<stopping/shuffled>>'); 
  }

Because the macro can be written as shown near the end of this (more-comprehensive-than-above) code:

class FuncIllogicalVerifyResult : IllogicalVerifyResult
	msgFunc = nil
	construct(func) {
		setMethod(&msgFunc,func);
	}
	showMessage{
		msgFunc();
	}
;
class FuncIllogicalNowVerifyResult : IllogicalNowVerifyResult
	msgFunc = nil
	construct(func) {
		setMethod(&msgFunc,func);
	}
	showMessage{
		msgFunc();
	}
 ;
class FuncIllogicalAlreadyVerifyResult : IllogicalAlreadyVerifyResult
	msgFunc = nil
	construct(func) {
		setMethod(&msgFunc,func);
	}
	showMessage{
		msgFunc();
	}
 ;

   // make these invoking names whatever you want

#define illog(msg) \
    (gVerifyResults.addResult(new FuncIllogicalVerifyResult( {: say(msg) } )))

#define illogNow(msg) \
    (gVerifyResults.addResult(new FuncIllogicalNowVerifyResult( {: say(msg) } )))
	
#define illogAlready(msg) \
    (gVerifyResults.addResult(new FuncIllogicalAlreadyVerifyResult( {: say(msg) } )))

I have a few words to add for anyone who aspires to make the verify message text resolve at the same juncture which the library results do.
And if you do this, please share here at the end of the thread! At the moment I don’t feel like carrying this idea out fully because of time constraints and no personal need for it.
Here is some (possibly) helpful advice:
Add an action_ property so that upon construction, you can store the gAction in the FuncXXVerifyResult.
Then, duplicate MessageResult.resolveMessageText found in exec.t line 1620, naming it something else. Func..Result.showMessage will first extract the next iteration from msgFunc, and then process the text by calling this method. This altered resolveMessageText only needs to be changed to call an alternate version of langMessageBuilder.generateMessage(msg) on line 1814. The alternate generateMessage (found in output.t, line 1084) could have a second parameter, and with it basically needs to replace any occurrences of “gAction” with the instance stored in the action_ property. I expect with this kind of approach, the “illog” macros will need to be changed so that the anonymous functions say “return msg” instead of "say(msg), so the final step in showMessage would be to say(the resolved text).
I haven’t tested any of this to see if it works, but it may give someone a start.

@jjmcc

1 Like

One final amendment. I edited the previous post so that the “illog” macros can take a single-quoted string instead of a double, so that usage is completely unchanged from the norm, other than the name of the macro itself.

I also looked a little further into the message param resolution process, and I still think that resolving the text to reflect the point of construction, rather than the moment of displaying, is feasible. I changed the “advice” block considerably, but still didn’t take the trouble to implement and test it myself.

(@jjmcc pinging you one last time just 'cause you said you bookmarked it, wanted you to end up with the “cleanest” version)

2 Likes