Running Twine code stored inside an object

(Sugarcube 2.3.0 Tweego)

Hi folks,

I’m creating a card game using Twine and the cards are stored like this:

<<set $cardDeck to [

    {name: "card 1",
    img: "IMG-1285.png",
    text: "this is the first card"},

    {name: "card 2",
    img: "IMG-1286.png",
    text: "this is the second card"}
]>>

Each card will have a different effect if the player decides to use it. Let’s say the first card is a ‘healing spell’ that will increase the player’s health by 2, but costs 2 mana to play:

{
name: "card 1",
img: "IMG-1285.png",
text: "this is the first card",
effect: "if player has at least 2 mana, increase player health by 2 and reduce mana by 2"}

The actual effect of the card would be executed like this:

<<if $player.mana gte 2>>
   <<set $player.mana -= 2>>
   <<set $player.health += 2>>
<</if>>`

My 2 questions are:

  1. What is the best way for me to store expressions like this within the card objects?
  2. How can I run these expressions from inside the card objects?

I was thinking of storing the lines code for each card as a string, and then trying to run that, but I’m not sure how or if it is an efficient way to do this.

I think there are ways to do that. I use the “print” macro to turn strings into html code and thus display a map based on array data, so maybe you could use that to run code too, but I think it will be messier and more complicated.

The most practical way to do what you want, in my opinion, is to set the effects as properties in the card arrays, and later set up a passage that will interpret those properties. You will also need a variable to copy the exact content of each chosen card, so it will be targeted to be interpreted and operated. Per example, first set the cards like this:

<<set $cardDeck to [

    {name: "card 1",
    img: "IMG-1285.png",
    text: "this is the first card",
    effects: [  /* AFTER THIS BRACKET YOU CAN STORE SEVERAL EFFECTS */ 
         {   /* THIS BRACKET MARKS THE CONTENT OF THE 1ST EFFECT */ 
              name:"Magic healing",
              description: "Spends 2 mana to heal 2 health points.",
              condition: [  /* STORE SEVERAL CONDITIONS HERE */
                  {property: "mana", value: 2},
                  {property: "WhateverOtherYouWant", value: 25739}
              ],
              result:[  /* STORE SEVERAL RESULTS HERE */
                  {property: "mana", value: -2},
                  {property: "health", value: 2}
              ]
          },
          { ANOTHER EFFECT HERE (you know what I mean)}
     },

    { ANOTHER CARD HERE IN THE SAME STYLE }

]>>

Then, set a new variable (in this example, I’ll name it “$chosencard”) to copy the content of the card whose effects you want to trigger, using also a variable to identify the number (in this case, “$cn”, standing for “card number”):

<<set $chosencard to $cardDeck[$cn]>>

You should, obviously, set $cn when the player clicks a card, or something like that.

And then, finally, you should call another passage with the “include” macro (or the old “display” macro) that would silently operate all of this, using a loop to search all through the card “effects”. This would be quite long to write so I’ll just give you some hints.

/* LOOP FOR EACH EFFECT */
<<for _loop to 0; _loop lt $chosencard.effects.length; _loop++>>
/* LOOP FOR EACH EFFECT'S CONDITION */
<<for _loopcon to 0; _loopcon lt $chosencard.effects[_loop].condition.length; _loopcon++>> 

if any/all of the condition's name value matches or is smaller than any/all of the player's value of the same name, then:

do here another loop to search through the "results" part, and inside:

order to "set" each player property whose name matches with the name of each "result", as it's "value" demands.

<</for>>

<</for>>

With some imagination, you can write a code that can handle anything. This will be hard work, however.

EDIT: Typos, sorry. Also, another idea that may help: store “and” and “or” string values -or something like that- inside the “conditions”, so you can manage if you want all of them to be needed to trigger the results, or just one of them. This should be also operated as orders in the “silent interpreter” passage. You could also add strings like that in the effect’s “results”, or even something like “random”, to specify that you want to run all of them, or just one of them, or even trigger some randomness with percentages in the “silent interpreter” passage, too. With imagination and hard working, possibilities are endless!

1 Like

Oh this is very clever and very clearly described too - thank you so much! It’s a little late here for my brain to fully soak it all in, but I’m very much looking forward to trying this out tomorrow…

1 Like

By the way, and before I forget, there is a way to avoid “for” loops in some cases (they tend to slow your game if there are several loops inside other loops), that consist in this type of checks instead:

<<if $arrayorobject.include($stringornumberorevenwholeobject)>>[whatevercontent]<<endif>>

This above will check if something equal to the value of $stringornumberorevenwholeobject is inside $arrayorobject without the need for “loop checking”.
The opposite version of this -checking that it doesn’t include it-, simply has a “!” symbol in front of it, like this:

<<if !$arrayorobject.include($stringnumberorevenwholeobject)>>[whatevercontent]<<endif>>

I don’t know if this would apply to any of the “for” loops that I hinted you here, but it may come handy sooner or later.

Search for more info about the “include()” method, I barely use it because in my game it is not compatible with some other stuff I do, so I can’t say much else. Experiment with caution!

1 Like

Well, it kind of depends on how you want to access the cards. If you want them accessed by an index number, then arrays of objects are fine. If you want them accessed by something else, such as card name, then make it a “card list” object which contains “card” objects, and refer to the “card list” object using card names from your “deck” array.

However, assuming the cards don’t change, instead of storing them in a story variable (a variable that starts with a “$”), you should use the setup object. For example, you could put something like this in your StoryInit passage:

<<set setup.cardList = {
	card1: {
		img: "IMG-1285.png",
		text: "this is the first card" },
	card2: {
		img: "IMG-1286.png",
		text: "this is the second card" }
	}
>>

The reason why you should use the setup object instead of a story variable, is that, since the data won’t change, there’s no need to store an exact copy of all of that data within every step in the game’s history. The larger your game’s history, the slower your game will be during passage transitions, saves, and loads.

Note: It’s important that you set any setup object properties like that, either within the StoryInit passage or in your JavaScript section, so that way they’re set properly when you load a saved game.

You could build a deck from that object like this:

<<set $cardDeck = Object.keys(setup.cardList).shuffle()>>

After doing that, $cardDeck will be set to an array of all of the card names, randomly shuffled. (See the Object.keys() method and the .shuffle() method for details.)

If you wanted to access the properties of a card in the deck, you could do that like this:

<<set _cardText = setup.cardList[$cardDeck[_index]].text>>

This way, the only thing you’re storing in the game’s history is the list of card names in the deck.
 

Use the simple trick of storing the code you want the card to run as a string, and then execute that code when needed. For example, if you had a card set up like this:

<<set setup.cardList = {
	card1: {
		img: "IMG-1285.png",
		text: "this is the first card",
		code: "<<set $player.mana -= 2, $player.health += 2>>" }
>>

Then you could execute that code like this:

<<run $.wiki(setup.cardList["card1"].code)>>

and that would silently (no output shown on screen) run the code within the “code” property on “card1”. (See the jQuery.wiki() method for details.)

Hope that helps! :slight_smile:

1 Like

Thank you both for your detailed help - I really appreciate it! This kind of advice is fantastic - the little tricks and tips you’ve described are going to be invaluable as I try to get this project going again. It’s very motivating :slight_smile: