Using strings as logical expressions in IF macros?

0 votes
asked 6 days ago by Flamekebab (230 points)

I'm working on an encounter system that checks an array of objects for various things to see if they can be encountered in the current game state (e.g. a party happens in the evening time slot) and want to add arbitrary conditions to be checked (I've no idea what kind of trigger conditions I'll want further down the line).

I iterate through my array checking each prerequisite condition until I get to this one:

<<if _encounter.triggerCondition>> 

Currently the triggerCondition is a string ("1 eq 1") and the <<if>> clause activates.

However in testing something else I wanted to disable the encounter and set it to "1 eq 0" but the result still returns true. The cause is doubtless something sensible like the fact that anything at all is returned (rather than undefined) counts as a true result. I'm not particularly bothered by why it happens - the question is whether I can make the trigger conditions work?

Ideally I want to be able to set a string that'll be called and then evaluated in the context of the if statement. Thanks!

(Sugarcube 2.20.0 and Twine 2.1.3.)

(Also where'd the code button go in the post formatting controls - something to do with last night's maintenance?)

1 Answer

+1 vote
answered 6 days ago by Chapel (26,410 points)
selected 6 days ago by Flamekebab
 
Best answer
Since an if statement can only ever return true or false, why are you reinventing the wheel? Instead of saving a string that contains a chunk of code that will yield a boolean why not just set the value to the boolean itself?

It just seems like an odd choice, and I can't really imagine any valid situation where this would be necessary. If you're setting the value by creating it from other values, then there's still no need for this.

For example, if you have something like this:

<<set $var to $var1 + ' eq ' + $var2>>
/% evaluates to a string like '1 eq 1' %/

You could just simplify it to this:

<<set $var to $var1 eq $var2>>
/% evaluates to a boolean %/

And this would let you pull the evaluation as a boolean without the extra step: it would yield true or false from evaluating the expression.

And I have no idea what became of the toolbar, but I miss it.
commented 6 days ago by Flamekebab (230 points)

Since an if statement can only ever return true or false, why are you reinventing the wheel? Instead of saving a string that contains a chunk of code that will yield a boolean why not just set the value to the boolean itself? 

The "1 eq 1" example is a placeholder designed to return true for testing.

The idea is that an encounter from the list has to pass several tests before the player can encounter it. By having an arbitrary trigger condition clause I can do things like:

  • The player is low on money, a scenario where a friend offers to lend them some money appears.
  • The player has met certain characters and a scenario involving those people can now happen
  • Or no special trigger condition at all

By having it be an arbitrary string the conditions can be as complex as needed whilst only being hardcoded to the particular encounter rather than the code that checks for encounters.

commented 6 days ago by Chapel (26,410 points)
edited 6 days ago by Chapel
Ah, I see.  If you need the evaluation to occur later, I suggest using a function, though it is possible to use a <<print>> to force the evaluation instead.  I recommend the former more highly, but it will take a very small amount of comfort working in JavaScript.  Here are some examples:

<<set $moneyEvent to {

    event : 'some passage',

    type : 'friend event',

    etc : 'whatever else you need',

    trigger : function () {

        return State.variables.money <= 30;

    }

}>>

<<set _event to $moneyEvent>>

<<if _event.trigger()>>

    /% fire event %/

<</if>>

To use the <<print>> method:

 

<<set $moneyEvent to {

    event : 'some passage',

    type : 'friend event',

    etc : 'whatever else you need',

    trigger : '$money lte 30'

}>>

<<print '<<set _event to ' + $moneyEvent.trigger + '>>'>>

<<if _event>>

    /% fire event %/

<</if>>

Again, the first example is more in line with how you typically want to approach a problem like this; it also gives you access to other features you might want, including arguments.

Using the <<print>> method is fine, but it has limitations and this generally isn't the best solution; forcing these sorts of evaluation, especially in a loop, is not exactly ideal in terms of performance, but YMMV.

I think this is what your asking for, but I've been having an off week, and the quality of my help and my ability to understand what's being asked for has been off, so I apologize if I'm still missing the point.
commented 6 days ago by Flamekebab (230 points)

You understood perfectly as far as I can see!

I've yet to get the code to work though. I tried the print method to no avail so I figured I'd give the JS version a go:

{passageName: "flatParty1",
preamble: "Might as well go check on the fridge. Sure, you had dinner an hour ago but who knows? Maybe in the intervening time something enticing will have appeared. No one's been shopping today but it could still happen. Fridges do that sometimes.",
linkText: "You step out of your room to hunt for tasty things.",
type: ["flatmate", "party"],
slot: "evening",
triggerCondition: function () {
return State.variables.player.money > 400;},
}

That works just fine. Changing the final line to:

return State.variables.player.money > 400;},

Results in the following error:

Error: <<encounterDeterminator>>: error within widget contents (Error:<<if>>: bad conditional expression in <<if>> clause: State.temporary.encounter.triggerCondition is not a function)

So that's, erm, baffling. I seem to either be able to return true or error but not false.

commented 6 days ago by Chapel (26,410 points)
I'm not sure where that error is coming from off hand. I don't see any major issues with what's posted here. That said, the code you said you changed looks identical to me.

Can I see the widget code? At a glance, it seems that the temporary variable isn't getting the object reference, or that its breaking at some point.
commented 6 days ago by TheMadExile (14,870 points)
edited 6 days ago by TheMadExile
Check your examples.  The line you said you changed looks the same as the previous "working" line.  One is likely not what you actually tried.

Preferably, show the full example in both cases, including where you set the temporary variable.  I say that because even if you botched the line you said you changed, the function should simply throw an error when invoked, not have the system claim that it's not a function.
commented 6 days ago by Flamekebab (230 points)
I've figured it out - there's currently two events in the encounter list. If the first one fails the trigger condition it moves onto the next one - except I forgot to setup the JS function on the second encounter meaning there was no function to run.

Cheers!
...