Mutating objects by refferecne

Twine Version: 2
Story Format: shugarcube
Hi there.
I’m using twine + shugarcube, and have a trouble mutating object via alternative variable reffering to it.
For example. We can

<<set $gladiators to [{'name':'Alice', 'wins':0}, {'name':'Bob', 'wins':0}]>> 

in one locaton. And then on the other location (Arena) if we do show

<<print $chosenOne.name>> 
<<print $chosenOne.wins>>
 <br><br> 
<<link 'Chose First' 'Arena'>><<set $chosenOne to $gladiatos[0]>><</link>>, 
<<link 'Chose Second' 'Arena'>><<set $chosenOne to $gladiatos[1]>><</link>>, 
<<link 'Fight' 'Arena'>><<set $chosenOne.wins++>><</link>> 

it will not work properly. Gladiator wins will drop to zero as soon as we chose another one. I belive Twine make copies of State.variables for each passage visit, so $chosenOne becomes a copy of $gladiator[i] but not a refference. How can I make this kind of code to work properly?

Thanks

1 Like

Hi, and welcome to the forum!

First of all, you have a typo in your second example (gladiatos vs gladiators), which will throw an error, but I assume it wasn’t copied from your actual code, since switching between gladiators itself worked for you.

As for your question: you can just operate on the objects inside the $gladiators array directly, like this:

<<print $gladiators[$chosenOne].name>> 
<<print $gladiators[$chosenOne].wins>> 
 <br><br> 
<<link 'Chose First' 'Arena'>><<set $chosenOne to 0>><</link>>, 
<<link 'Chose Second' 'Arena'>><<set $chosenOne to 1>><</link>>, 
<<link 'Fight' 'Arena'>><<set $gladiators[$chosenOne].wins++>><</link>>

This way, the objects won’t get copied.

Tx. My actual code is WAY more complicated so I doubt if I can use your tip, but I’ll try to figure out if it works…

OK, let me know if it worked for you. It’s a general way to modify an object at a position in the array indicated by a variable, so based on your original post, I’d say it should work. Or you can just post an example from your actual code for us to see.

I failded to implement your tip in my case (

My actiual code is way to big and messy to copy-past it here, but I made another example showing my case in more details.

So wee have CSS like this

red a {
color: Red; 
}

green a {
color: Green; 
}

blue a {
color: Blue; 
}

then init passage like this

<<set $gladiators to [
  {
  'name':'Alice', 
  'nick': 'The Bloodied',  
  'wins':0, 
  'team':'green'
  }, {
  'name':'Bob', 
  'nick': 'Wolfhound',  
  'wins':0, 
  'team':'green'
  }
]>>

<<set $opponents to [
  {
  'name':'Blood', 
  'nick': 'The Aliced',  
  'wins':0, 
  'team':'red'
  }, {
  'name':'Wolf', 
  'nick': 'Bobhound',  
  'wins':0, 
  'team':'blue'
  }
]>>

the widget passage with widgets

<<widget 'fullname'>>
	<<set _list to _args[0]>>
	<<set _index to _args[1]>>
	<<set $fullname to _list[_index].name + ' ' + _list[_index].nick>>
<</widget>>

<<widget 'linkToStatScreen'>>
  <<set _text to _args[0]>>
  <<set _list to _args[1]>>
  <<set _index to _args[2]>>
  <<set _who to '_list[' + _index + ']'>>  
  <<set $statLink to '<<link "' + _text + '" "StatScreen">>' + '<<set $who to ' + _who + '>><</link>>'>> 
<</widget>>

<<widget 'wrapInColor'>>
  <<set _text to _args[0]>>
  <<set _who to _args[1]>>
  <<set _color to _who.team>>
  <<set _wraperOpen to '<'+ _who.team +'>'>>
  <<set _wraperClose to '</'+ _who.team +'>'>>
  <<set $wrapped to _wraperOpen + _text + _wraperClose + '<br>'>>
<</widget>>

<<widget 'showLinks'>>
  <<set _list to _args[0]>>
  <<for _i to 0; _i lt _list.length; _i++>>
  	<<fullname _list _i>>
	<<linkToStatScreen $fullname _list _i>>
	<<wrapInColor $statLink _list[_i]>>
	$wrapped
  <</for>>
<</widget>>

Start passage

Gladiators:
<<showLinks $gladiators>>

Opponents:
<<showLinks $opponents>>

and finaly StatScreen passage like this

This is $who.name $who.nick of team $who.team who have $who.wins wins.

<<link 'Make him win once more' 'StatScreen'>>
	<<set $who.wins++>>
<</link>>

<<link 'Return to lists' 'Start'>><</link>>

Now… can you please figure out how to pass objects to widgets in this kind of tasks to make 'em work right?

huh. I can’t even insert my code properly. Sorry ((

Isn’t it " … " to teg a code part?

You can edit your post, mark all the code and hit the </> button above the edit window for the code to show properly.

Thanks.
I allso found a bug in showList widget ther is

_i lt _list.length;

not

_i lt _list;

Other than the number of wins resetting, is this code working for you? I created a new project in Sugarcube, pasted all fo your code into appropriately named passages and all I get when I try to run it is a bunch of errors.

I changed the typo “lenght” to “length” in the showLinks widget and the links display, but clisking one just throws an error.

I can take a closer look and try to figure something out later, when I have more free time.

eah, this code needs a debug. Made it in haste
I’ll try to make it work and correct it in original post…

But what I really need, not this code running, but a conceptual solution to a problem of mutating arbitrary obgects with widgets on the fly

I edited code in my post so this one should work (if I inserted it properly). Works for me at least…

But object mutations are going to 0 as soon as I change the passage, still. Just like in my actual project

So, your corrected code mostly works for me, but there are still some issues – for example, clicking both “Alice the Bloodied” and “Blood the Aliced” sends me to the stat screen for Blood the Aliced. I honestly don’t know why this happens, perhaps it’s because the code uses repeated temporary variable names in the same passage? Anyway, I wrote a new version of the code that does what you originally asked about.

If I understood correctly, you wanted links with names based on team, drawn from an array, and the ability to increase win numbers permanently for a given character. I had issues when applying one widget to two arrays (basically, when adding a win, I had to first set $side to either $gladiators or $opponents and then increase the win at a certain index of the given side, but this caused the exact problem you were struggling with – only the variable within $side got increased, not the one in $gladiators/$opponents, and everything went back to 0 upon reloading the passage). I put all of the characters into a single “gladiators” array and added a “side” property to the objects so you can tell if someone is an ally or an opponent. Alternatively, you could just go for separate widgets and separate stat screens for each of the sides. (EDIT: That is, make a showLinksGladiators and showLinksOpponents widgets that lead to, respectively, the StatScreenGladiators or StatScreenOpponents passages).

I̶ ̶a̶l̶s̶o̶ ̶g̶a̶v̶e̶ ̶t̶h̶e̶ ̶c̶h̶a̶r̶a̶c̶t̶e̶r̶s̶ ̶t̶h̶e̶ ̶"̶n̶a̶m̶e̶"̶ ̶a̶n̶d̶ ̶"̶n̶a̶m̶e̶f̶u̶l̶l̶"̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶,̶ ̶a̶s̶ ̶I̶ ̶t̶h̶i̶n̶k̶ ̶i̶t̶’̶s̶ ̶e̶a̶s̶i̶e̶r̶ ̶t̶o̶ ̶s̶t̶o̶r̶e̶ ̶t̶h̶e̶ ̶f̶u̶l̶l̶ ̶n̶a̶m̶e̶ ̶i̶n̶ ̶t̶h̶e̶ ̶o̶b̶j̶e̶c̶t̶ ̶t̶h̶a̶t̶ ̶c̶o̶n̶s̶t̶r̶u̶c̶t̶ ̶i̶t̶ ̶u̶s̶i̶n̶g̶ ̶a̶ ̶w̶i̶d̶g̶e̶t̶.̶

Anyway, in StoryInit I have:

<<set $gladiators to [
  {
  'name':'Alice', 
  'nick': 'The Bloodied',  
  'wins':0, 
  'team':'green',
  'side': 'allies',
  }, {
  'name':'Bob', 
  'nick': 'Wolfhound',  
  'wins':0, 
  'team':'blue',
  'side': 'allies',
  },  {
  'name':'Blood', 
  'nick': 'The Aliced',  
  'wins':0, 
  'team':'red',
  'side': 'opponents',
  }, {
  'name':'Wolf', 
  'nick': 'Bobhound',  
  'wins':0, 
  'team':'blue',
  'side': 'opponents',
  }
]>>

Then in a widget-tagged passage there’s this one widget:

<<widget "showLinks">>
<<for _i to 0; _i lt _args[0].length; _i++>>
<<capture _i>>

<<if _args[0][_i].team == "green">>
<span class = "green"><<link "_args[0][_i].name _args[0][_i].nick" "StatScreen">><<set $chosen to _i>><<set $side to _args[0]>><</link>></span>
<<elseif _args[0][_i].team == "blue">>
<span class = "blue"><<link "_args[0][_i].name _args[0][_i].nick" "StatScreen">><<set $chosen to _i>><<set $side to _args[0]>><</link>></span>
<<elseif _args[0][_i].team == "red">>
<span class = "red"><<link "_args[0][_i].name _args[0][_i].nick" "StatScreen">><<set $chosen to _i>><<set $side to _args[0]>><</link>></span>
<</if>>

<</capture>>
<</for>>
<</widget>>

In the passage named Start, there’s only

<<showLinks $gladiators>>

And finally, in the StatScreen passage:

This is one of your $gladiators[$chosen].side, $gladiators[$chosen].name  $gladiators[$chosen].nick of team $gladiators[$chosen].team who won $gladiators[$chosen].wins time(s).

<<link 'Make them win once more' 'StatScreen'>>
	<<set $gladiators[$chosen].wins++>>
<</link>>

<<link 'Return to lists' 'Start'>><</link>>

This does what you wanted while also trimming down the code (I admit I used a maybe not-so-elegant if-statement to select the colour of the links, as with only 3 I think your wrapper widget was needlessly complicated. With more colours, your method may be better).

EDIT: For the coloured links in the example to work, you have to include the following in the story stylesheet:

.red a {
color: red; 
}

.green a {
color: green; 
}

.blue a {
color: blue; 
}

if I understood correctly, you wanted links with names based on team, drawn from an array, and the ability to increase win numbers permanently for a given character.

In this example - yes. But in actual game I have a character object with dosens of parameters (many of witch are objects themselfs, and including another objects), and those procedurally generated caracters are grouped in warious lists (predefined, defined by player, procedurally generated), so make one single array for all is not an option for me.

I also gave the characters the “name” and “namefull” property, as I think it’s easier to store the full name in the object that construct it using a widget.

Nicks of my characters (as most of the parameters) can be changed during the game (or be missing), so we cant just get a full name at the start and use it instead as is.

I need powerfull, abstract solution to work dynamicaly with insctanses of the NPC, or other objects. Solution that will scale.

In this case, there may not be an easy, universal solution. If there is, maybe someone more experienced with Twine will chime in - I’d be happy to learn more about this from them.

Also, don’t take this as a criticism, but a bit of advice - if you want people to give actual solutions to your problems, give the full scope of the code you actually use/want to use, not a simplified version that doesn’t cover everything you need. Otherwise, people may spend a lot of time writing answers that, as it turns out, don’t really solve the thing for you.

Sorry if I wasted your time. My intention was exactly the opposite. I can’t imagine indeed how can I insert all my actually relrevant code in a forum post.

Many thanks for your insight. It’s maybe not a complete solution, but you give me some really helpfull tips newertheless. I’ll try to use them to figure out something for my case.

It’s okay, I’m glad it was useful at least to some extent :slight_smile:
But with big, complex project, it may be impossible to find a solution without knowing the entire scope/seeing all the code. I hope you’ll be able to modify my examples to that they work for you.

EDIT: I modified my code example in the earlier post to have name and nick as separate properties, since it’s relevant for your project.

Maybe I can use JS to circumvent this problem somehow?

Sorry, I don’t know JavaScript, so someone more experienced would have to answer this question.

At this point, I’m not sure I understand the full scope of what you want to do/what you need. I played around with my example code a bit more and figured out the way to have one widget operate on the content of different arrays - it’s actually quite simple. Not sure if it’s of any use to you, but here’s the example code (very basic, only two teams, characters have a name and a win count, and you have a single widget you can use to add wins for either of the teams).

StoryInit:

<<set $teamA to [{name: "Sam", wins: 0}, {name: "Max", wins: 0}]>>

<<set $teamB to [{name: "Alice", wins: 0}, {name: "Bob", wins: 0}]>>

Widget-tagged passage:

<<widget "addWins">>
<<for _i to 0; _i lt _args[0].length; _i++>>
<<capture _i>>

<<link [["Add a win for " + _args[0][_i].name|passage()]]>><<set _args[0][_i].wins += 1>><</link>>

<</capture>>
<</for>>
<</widget>>

(this uses the passage() to always return to the current passage (the one in which the widget is used). The auto-created passage linked to this one should be deleted)

“Start” passage:


<<for _i to 0; _i lt $teamA.length; _i++>>
<<capture _i>>$teamA[_i].name, wins: $teamA[_i].wins<</capture>>
<</for>>

<<addWins $teamA>>

<<for _i to 0; _i lt $teamB.length; _i++>>
<<capture _i>>$teamB[_i].name, wins: $teamB[_i].wins<</capture>>
<</for>>

<<addWins $teamB>>

(edit: typo)

1 Like

This one looks cool. I’ll try to implement this.

TYWM