Help with multi dimensional array

So I want to keep track of a schedule for the player and think a two dimensional array would work. The first index would be the day (1-365), the second index would be schedule entries. For each day there would be multiple entries I would need to loop through using the length of the second element of the array. Can this be done in Sugarcube? Thanks.

If you are requesting technical assistance with Twine, please specify:
Twine Version:2.314
Story Format: Sugarcube 2.314

Standard JavaScript doesn’t include a true multi-dimensional array data-type, however you can emulate one using Arrays within Array technique…

In SugarCube it would looks something like the following…

/* Initialise a 365 element Array, with each element containing an empty Array. */
<<set $schedule to Array.from(Array(365), () => new Array())>>

/* Add an 'entry' for the 1st day. */
<<run $schedule[0].push('The start of the year')>>

/* Add an 'entry' for the last day. */
<<run $schedule[364].push('The end of the year')>>

Days: $schedule.length
First event of First Day: $schedule[0][0]
First event of Last Day: $schedule[364][0]

…as JavaScript Array’s are offset (zero) based.

If you want the ‘index’ of the outer dimension to be the numbers 1 to 365, and be able to iterate through the ‘days’ in numerical order, then you may want to use a Map object instead.

/* Initialise a 365 element Map, with each element containing an empty Array. */
	<<set $schedule to new Map()>>
	<<for _i to 1; _i <= 365; _i++>>
		<<run $schedule.set(_i.toString(), new Array())>>

/* Add an 'entry' for the 1st day. */
<<run $schedule.get('1').push('The start of the year')>>

/* Add an 'entry' for the last day. */
<<run $schedule.get('365').push('The end of the year')>>

Days: $schedule.size
First event of First Day: <<= $schedule.get('1')[0]>>
First event of Last Day: <<= $schedule.get('365')[0]>>

If the ability to iterate the ‘days’ isn’t important then you could use a Generic Object instead.

/* Initialise a 365 key Object, with each key referencing an empty Array. */
	<<set $schedule to {}>>
	<<for _i to 1; _i <= 365; _i++>>
		<<set $schedule[_i.toString()] to new Array()>>

/* Add an 'entry' for the 1st day. */
<<run $schedule['1'].push('The start of the year')>>

/* Add an 'entry' for the last day. */
<<run $schedule['365'].push('The end of the year')>>

Days: <<= Object.keys($schedule).length>>
First event of First Day: $schedule['1'][0]
First event of Last Day: $schedule['365'][0]
1 Like

Yup, it’s simply an array with arrays in it. See the MDN JavaScript Array documentation if you need help with working with arrays or understanding the code below.

To create the array you can do:

<<set $schedule = Array.from({ length: 366 }, function () { return []; })>>

which will create an array of 366 elements (0 to 365), and each element will contain an empty array. (Note: Arrays always start at index zero.)

You can then access the elements of that array like this:

// Set the current day to day 20.
<<set $currentDay = 20>>

// Add an event object to the current day's schedule.
<<set _eventObject = { name: "Event name", description: "Blah, blah, blah..." }>>
<<set $schedule[$currentDay].push(clone(_eventObject))>>

// Get the current day's schedule.
<<set _daySchedule = $schedule[$currentDay]>>

// Get how many events there are for the current day.
<<set _eventCount = _daySchedule.length>>

// Get the name of the first event in today's schedule, if there is one.
<<if _eventCount > 0>>
	<<set _eventName = _daySchedule[0].name>>
	<<set _eventName = "(none)">>

Those are just examples, you don’t need to do any of it exactly like that.

Now, just a quick primer on the difference between “object data types” and “primitive data types” and how they behave in Twine/SugarCube, for those who don’t know, since you can get yourself into trouble if you don’t understand how they work.

In the above code, where it does <<set _daySchedule = $schedule[$currentDay]>>, after that, _daySchedule will act exactly the same as $schedule[$currentDay] (based on whatever value $currentDay had at that time) because they’re now both referencing the same JavaScript Object. This means that you could do $schedule[$currentDay][0].name instead of _daySchedule[0].name above, and it would work exactly the same. It also means that if you change something within a value of one of those variable’s objects (e.g. <<set _daySchedule[0].name = "Xyz">>), it will also change the value the same way in the other one, because both variables are referencing the same Array object.

That’s why I used the SugarCube clone() function above, to make a separate copy of the _eventObject object that’s added to the array. That way, if you later modified a value within the _eventObject object, it wouldn’t also change the value that had been added to the $schedule array earlier using the .push() method.

I should also note that, for SugarCube story variables, all JavaScript Objects (including Array objects) are cloned during passage transitions in Twine/SugarCube. So, even if $A holds a reference to an object of some sort and you do <<set $B = $A>>, the two story variables will only point to the same object within that passage. In the next passage, each of those story variables will now contain references to separate copies of the original object.

For contrast, variables which are set to data types which are not objects (i.e. numbers, strings, Booleans, undefined, and null), which are known as “primitive types”, store the value directly in the variable, as opposed to storing a reference to the data the way it would with an object data type. In other words, if $A is set to a value which is a primitive type, you do <<set $B = $A>>, and then change something within the value of one of those variables, the change will not also occur in the other variable, because each variable gets their own copy of that value.

TL;DR: Primitives are stored in variables by value, while objects are stored by reference, though SugarCube breaks references upon each passage transition.

Hopefully that wasn’t too confusing, but when you’re working with objects, it’s a good idea to have a basic understanding of the way that they work.

Have fun! :slight_smile:

EDIT: Fixed array creation code.

1 Like

The <Array>.fill() method is for filling the target array with static values. Your example fills each slot in the target array with the same array reference.

To fill an array’s slots with distinct arrays you want to map them. Greyelf’s example is one way to accomplish that. Another would be:

<<set $schedule to Array.from({ length : 365 }, () => [])>>
1 Like

Ha! Good catch! Extra ironic since I just explained how object references work. :stuck_out_tongue:

I’ve fixed my earlier code using yours as a basis, but changed the arrow function to a regular function, since IE doesn’t support arrow functions, and I like my example code to be as broadly supported as possible. Mostly unnecessary, yes, but there’s roughly 1 per 200 people out there still using IE worldwide. (source)

Hope not to hijack this thread too much, but the Venn diagram of legacy browser users vs people who play IF may be a thing as yet undiscovered.

Is there a need to define a reasonable, minimal set of configurations for testing IF in the browser?
Not just for Twine. I’m feeling this myself in my own efforts to deliver a reliable experience over the web.

It’s definitely going to be a balance of time spent vs likely return on that time investment, which is going to vary depending on the size and complexity of your project.

The more people you’re planning on selling it to, the more important it is to have a solid, well tested, product. The bigger and more complex the game is, the harder that will be to accomplish.

So, you’ll need to figure out what your time is worth, and how much of that time you’re willing to spend just doing testing and/or writing and running automatic testing code. The total time you get to spend vs. the size of your project will determine how much of it gets tested in the various browsers and browser setups.

This is pretty much true of any web-based project.

Hope that helps! :slight_smile:

1 Like