Clone() weird behaviors with arrays and "goto"

I’m having weird problems with clone(), whenever I’m not cloning a simple variable with a string or numerical value. The two problems I noticed are the following:

1). Let’s say I set an array and try to make another as a clone copy of the former one:

<<set $array1 to [
{blah:“bledeblah”,blues:10},
{onemore:[“much”,“stuff”],},
]>>

<<set $array2 to []>> /* I assume this is not necessary, but just in case */

<<set $array2 to clone($array1)>>

Fine, sometimes I found out the inner values match, but then if I do this:

<<if $array1 eq $array2>>
or
<<if $array1 is $array2>>

the result is always false, and I don’t know why.

2). Let’s then stick to the basic <<set $array2 to $array1>> then. I’d like to avoid using it because I understood that it won’t make $array2 an independent array with the same data: it will make them somehow dependant, and changing one may change the other. That’s what I understood anyway, correct me if I’m wrong.

Anyway, let’s say I have no other option than doing it with the simple “set” way. Then the same problem arises when a “goto” order comes in.

To bring a bit more of context, in my game almost everything happens in the same “main” passage. Everything unfolds by “display” connections to different passages, activated by variables, and by “replace” orders coming from widgets. Everytime you click something, an “update” widget makes the passage div’s “display” content to be replaced with the same “display” links as before, thus running it again but with the new variable/array info.

As long as I use this “custom” update method, copying one array to another by the “set” command works just fine, but as I said, if a “goto” kicks in (like pressing enter on a textbox), suddenly <<if $array1 eq $array2>> goes false, even though if I print their inner data, everything seems to match between them.

If the data is the same, could it be the spaces between the commas or something like that? Could it be the “array spaces” get rearranged when loading a passage, making the “is/eq” checks to be false?

How can I prevent this?

EDITS: Sorry, had lots of typos

Okay… So this is sort of complex, but the way that sugarcube works is that every time you navigate to a new passage, it creates a new copy of every story variable (the old ones are placed into history so that when you click “back”, the values are restored). This is fine for strings and numbers, but when it comes to objects, your comparison is going to break.

The reason is because when comparing an object to an object, it’s not comparing the contents or value, it’s comparing the reference. It’s really comparing whether it’s truly the same object. So when sugarcube creates new copies, they may hold the same data, but they’re both different objects.

There are two ways that I know of to get around this and they’re both somewhat clunky.

The easiest way that has the most overhead is to use JSON.stringify(). Using that you can compare a string representation of both objects.

if (JSON.stringify($a) == JSON.stringify($b)) {

To be honest, I haven’t tried this method myself, but I believe it should work. But I wouldn’t want to use it in a loop or something because it’s going through each object and all of its members and converting it to a string.

The safer method is to use IDs and not references.

<<set game_objects to {
	"item1": {
		{blah:“bledeblah”,blues:10},
		{onemore:[“much”,“stuff”]}
	}, 
	"item2": {
		{"bleh": "whatever"}
	}
}>>

<<$array1 = "item1">>
<<$array2 = "item1">>

/% on the next passage now %/
<<if $array1 eq $array2>> /% this will be true %/
	/% this object is only valid for this passage but is handy to save typing %/
	<<set _obj to $game_objects[$array1] >>
	[... etc...]

Phew. I hope that all makes sense.

Edit:
I should also mention that if you go with the ID route (which I recommend) and the object has fixed data for all its members (for example, prices of weapons that never change), you should put it in the setup object so that it’s not getting saved to disk or recreated over and over for no reason.

Just use that same code I posted, but use setup.game_objects in place of $game_objects. Be sure to put the defining of it either in your javascript passage or StoryInit so it’s defined even when loading a save.

1 Like

Thanks for the answer, I’ll try all that tomorrow and will tell the results.

What does the <<set _obj to $game_objects[$array1] >> do? That’s the only thing I couldn’t figure out.

And one last thing. What you just explained… it means that after a passage load, every copied variable or array becomes totally independant from their “twins”. So what I thought about copies being affected by modifying their former copy source, is just wrong, isn’t it? I think I read about doing <<set $variable1 to $variable2>> and then modifying one of them do cause changes on the other. But I’m not sure I got that right.

$game_objects is an object. There are two ways of accessing members of an object:

$obj = {"name": "Bob};
$a = $obj.name;    // $a is set to "Bob"
$b = $obj["name"];    // $b is set to "Bob"

The latter method is really useful because you don’t need to explicitly state the member that you’re talking about and can use a variable.

$item = "name";
$a = $obj[$item];    
// $a is set to "Bob" because it accesses the "name" member

Because of how this works, if you access the object members (in our case, our object is a list of items, but it’s the same principle) using variables with ID strings, you can get the item in the list without having to know its name, and can easily compare the string IDs in the two variables to see if they’re the same. Unlike objects, strings don’t have references and won’t break from passage to passage.

I’m sorry if I’m not explaining it well. It’s a difficult concept to explain and it’s a major hurdle for most people new to programming, but it really becomes second nature as you get more familiar with programming.

As for your question object copies: using copy() breaks the reference. That’s actually its intended purpose. copy() is so that you can create a second item that you can modify without changing the original. So yeah, you were mistaken on your original plan.

1 Like

I see. I actually use that a lot, but never with temporary variables. I’m scared of them since in my game the info is constantly flowing between different passages with the “display” trick, without ever moving from the main passage, so I don’t know if temporary variables are 100% safe to use in this context.

About the last paragraph, please help me getting it straight. The copy() method inmediately creates an independant variable/array; on the contrary, the <<set $variable/array1 to $variable/array2>> won’t… until the next passage load? Did I got it right or am I mixing it up?

English is not my native language, let me apologize for needing extra explanations.

Correct.

Mostly correct. Objects, array, maps, etc will break references from passage to passage, but not within that same passage. Simple variables like numbers and strings compare by value instead of by reference, so those comparisons won’t break from passage to passage.

So this will work on the same passage but will break as soon as you move to a new one:

$a = $objects["apple"];
$b = $objects["apple"];
if ($a == $b)    // this is true

Once you move to the new passage, $a, $b, and $objects[“apple”] will become new copies. They’ll have the same data, but they’re counted as three distinct objects so $a == $b is not true anymore.

That’s where IDs are helpful. Because you can just compare the ID strings (“apple” in this example) and then when you want to do something with it, just toss it into a temporary object. You’ll lose it in the next passage, but the reference will break anyway so it’s no loss.

$a = "apple";
_food = $object[$a];
_food.eaten = true;

/* next passage */
_eaten = $object[$a].eaten;   // _eaten will be true

It’s okay if the object list keeps getting copied as long as you use IDs to access the members. The modified data will also be copied so it’s like it’s the same object. It’s just a little bit more typing.

1 Like

Ok, this time I could understand the whole thing.

Since there’s too much code to change if I wanted to compare IDs instead of references, i used the “stringify” method, and it worked just fine. It even let me use the clone() copy method as I intended in the first place.

I also realized that the few times that array copying worked in my code it was because I was in fact comparing an inner element ID without thinking too much in the difference.

Now it all makes sense and I understand better how my own code works. Thanks!

If it helps, my Universal Inventory System (UInv), written for Twine/SugarCube, includes functions to compare arrays, objects, sets, and maps to determine if they’re equal. If you add the UInv code to your JavaScript section, then you can do this:

<<if UInv.valuesAreEqual($variableA, $variableB)>>

and it will compare the values to see if they’re the same. So, it will tell you that, for example, two arrays are the same, even if one is a clone of the other, because it compares the values, instead of the references.

If you want to, you’re free to edit UInv’s JavaScript down to just the utility functions you need.

Generally, it’s preferable to use temporary variables whenever you can, since they get cleared at the next passage transition. This means that they don’t waste any space in the game’s history, which can slow down passage transitions, saves, and loads.

The only time it may be “unsafe” to use a temporary variable is if you’re using a temporary variable with the same name as one used in an <<include>>ed passage or a widget which you’re using in that same passage. (Also, <<display>> has been depreciated in favor of the <<include>> macro, so you should use that instead.)

Also, I’m not quite sure exactly what you’re doing when you say “info is constantly flowing between different passages”, but it sounds like you should probably be using widgets instead.

Hope that helps! :slight_smile:

1 Like

Why so much depreciating?