Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

[SugarCube 2] Arrays within arrays?

Hello,

I'm trying to figure out how to build a relationship matrix within Twine 2/Sugarcube 2. I feel I am missing something very basic here.

I have $people[$i] where my people are described. For starters they have attributes of $people[$i].id, $people[$i].business, $people[$i].age, $people[$i].gender, $people[$i].favoriteSportTeam, and so on. What I want to be able to do is add something like $relationship[$k].id (which would be a people[$i].id, $relationship[$k].relationship (mother, brother, friend, etc.) and $relationship[$k].relationshiptype (probably a numeric scale of -10 (hatred and loathing) to 10 (love and trust)).

Can you have multidimensional arrays in Twine/Sugarcube? If so, how exactly are they referenced? I've been struggling trying to word my searches to find an answer. My best guess is $people[$i].$relationship[$k].id (or other attribute), but that doesn't seem to be working.

Thanks!

Comments

  • True multidimensional arrays? No, as JavaScript only has single dimensional arrays. Effectively multidimensional arrays? Yes, by nesting single dimensional arrays within one another.

    That said, based on your descriptions, it seems like that's not what you want in the first place.
    1. It looks like each member of the $people array is an object.
    2. Each people object should have a relationship property.
    3. The relationship property should be an array of objects.
    If so, simply assign each of the people object's relationship property an array of relationship-info objects. You managed to get $people setup, this shouldn't be any more difficult.

    I'd give some examples, but you didn't bother to show examples of any of your code, so I can't say which would be the best way for you to do so.

    To access someone's relationships, you'd do something like:
    $people[$i].relationship[$k].id
    

    Also, have you though about using generic objects all the way down, rather than arrays? Which is better for your needs depends on what you're doing, obviously, but accessing such a system could look like the following:
    // Access by iteration.
    $people[$i].relationship[$k].id
    
    // Access by name.
    $people['steve'].relationship['mother'].id
    $people['calvin'].relationship['susie'].id
    
  • edited September 2016
    MadExile,
    Sorry about the lack of code. Let's see if this will give you a better idea. It's a bit more random in some of the details right now, but that's just so I can see what I can do with populating an array.
    <<set $names to ["Abby", "Bill", "Carly", "Dennis", "Erica", "Fred"]>>
    $names
    $names.length
    <<set $teams to ["Badgers", "Cougars", "Foxes", "Pumas", "Hawks", "Doves", "Osprey", "Civits"]>>
    $teams
    $teams.length
    <<nobr>>
    <<set $people to []>>
    <<set _counter to $names.length>>
    <<for _i to 0; _i lt _counter; _i++>>
    	<<set $people.push({
    		name: $names.pluck(0,0),
    		id: 1000 + _i,
    		favoriteSportsTeam: $teams.random(),
    		age: random (18, 65)
    	})>>
    <</for>>
    <</nobr>>
    <<for _j to 0; _j lt $people.length; _j++>>
    _j: $people[_j].name, $people[_j].id, $people[_j].favoriteSportsTeam, $people[_j].age <</for>>
    
    
    gives a result like:
    Abby, Bill, Carly, Dennis, Erica, Fred
    6
    
    Badgers, Cougars, Foxes, Pumas, Hawks, Doves, Osprey, Civits
    8
     
    0: Abby, 1000, Hawks, 58 
    1: Bill, 1001, Doves, 32 
    2: Carly, 1002, Foxes, 52 
    3: Dennis, 1003, Cougars, 49 
    4: Erica, 1004, Badgers, 32 
    5: Fred, 1005, Foxes, 52 
    

    So, the question is really how do I go about making a relationship matrix?

    I want something that I can later put more code around so that I can add this kind of data.
    Abby:
    Relationship.id    Relationship.Type   Relationship
    1001                   Son                        8
    1002                   Sister                      7
    1003                   Neighbor                 -2
    1004                   Daugther-in-law     3
    1005                   Brother-in-law         -4
    
    Bill:
    1000                   Mother                    9
    1002                   Aunt                        5
    1003                   None                       0
    1004                   Wife                         10
    1005                   Uncle                       5
    
    etc.           
    

    Does that help?

    Your examples show a refence scheme $people[$i].relationships[$k].id so if I am undestanding correctly (using my data above):
    $people[0].relationships[0].id
    
    would return:  1001
    and $people[1].relationships[4].type
    
    would return:  wife
    

    Am I understanding you correctly?
  • Apparently you can't use italics inside code indents. :(
  • How would I even go about initializing the sub-array? Would it look like this?
    <<set $people to []>>
    <<set _counter to $names.length>>
    <<for _i to 0; _i lt _counter; _i++>>
    	<<set $people.push({
    		name: $names.pluck(0,0),
    		id: 1000 + _i,
    		favoriteSportsTeam: $teams.random(),
    		age: random (18, 65)
                    <<for _k to 0; _k lt _counter - 1; _k++>>
                             <<set $people[i].relationships.push({
                                    id: 0,
                                    relationshipType: none,
                                    relationship: 0
                               })>>
                     <</for>>
    	})>>
    <</for>>
    
  • Zhark wrote: »
    […] Let's see if this will give you a better idea. It's a bit more random in some of the details right now, but that's just so I can see what I can do with populating an array.
    <<set $names to ["Abby", "Bill", "Carly", "Dennis", "Erica", "Fred"]>>
    $names
    $names.length
    <<set $teams to ["Badgers", "Cougars", "Foxes", "Pumas", "Hawks", "Doves", "Osprey", "Civits"]>>
    $teams
    $teams.length
    <<nobr>>
    <<set $people to []>>
    <<set _counter to $names.length>>
    <<for _i to 0; _i lt _counter; _i++>>
    	<<set $people.push({
    		name: $names.pluck(0,0),
    		id: 1000 + _i,
    		favoriteSportsTeam: $teams.random(),
    		age: random (18, 65)
    	})>>
    <</for>>
    <</nobr>>
    <<for _j to 0; _j lt $people.length; _j++>>
    _j: $people[_j].name, $people[_j].id, $people[_j].favoriteSportsTeam, $people[_j].age <</for>>
    
    I'm going to address a few issues before moving on to the main topic.
    1. As a general rule of thumb. Bits of code which should, very likely, be done before the start are better off within the StoryInit special passage. You're testing right now, so having them someplace where you can see output is fine, however, you will want to place them appropriately before moving to production.
    2. Similarly, <<nobr>> is no replacement for <<silently>>. If a bit of code has to go someplace where output is generated, but the code itself should not, use <<silently>>.
    3. Removing and returning a member off of the front of an array should be done via the <Array>.shift() method—<Array>.pop() if you want the element from the end. Only use <Array>.pluck() if the location is not the start or end. In other words, use $names.shift() rather than $names.pluck(0,0).
    4. Style cop. Do not put whitespace between a function's name and its parameter list (i.e. do not → random (18, 65), do → random(18, 65)). It's not an error, but it's not idiomatic.
    5. Do not be overly descriptive with either variable or object property names. For example, unless you plan on recording teams which are not a person's favorite and/or not a sporting team, then favoriteSportsTeam is magnificently overspecified and isn't conveying anything to you, the coder, that team doesn't. This matters because these names go into the serializations—both for the play session and saves—so smaller is definitely better. I'm not saying to use cryptically small names, however, brevity is a virtue here.

    Zhark wrote: »
    So, the question is really how do I go about making a relationship matrix?
    If the people referenced within the relationship objects should come from within the $people array, then you cannot add the relationship objects until the $people array is complete. Basically, you'll create each person's relationship array as part of their creation, though it will remain empty at that time. After the $people array is complete, you'll iterate through its objects filling in each person's relationships.

    In short, since you're creating each person with randomized details, you cannot fill in their relationships until you have all of the people created.

    So your current code would simply need to add a relationships property which holds an empty array. After the initial loop finishes creating all of your people, you'd then iterate over them all creating their relationships. For example:
    <<silently>>
    /*
    	Set up the name and teams arrays.
    
    	NOTE: If you're not going to use these later, they're probably better off as temporary variables.
    */
    <<set $names to ["Abby", "Bill", "Carly", "Dennis", "Erica", "Fred"]>>
    <<set $teams to ["Badgers", "Cougars", "Foxes", "Pumas", "Hawks", "Doves", "Osprey", "Civits"]>>
    
    /* Set up the $people array, filled in with randomized people. */
    <<for _i to 0; _i lt $names.length; _i++>>
    	<<set $people.push({
    		name : $names.shift(),
    		id : 1000 + _i,
    		team : $teams.random(),
    		age : random(18, 65),
    		relationships : []
    	})>>
    <</for>>
    
    /* Set up each person's relationships array, filled in with randomized people. */
    <<for _i to 0; _i lt $people.length; _i++>>
    	<<set _currentShips to $people[_i].relationships>>
    	<<set _currentShipCount to /* TBD */>>
    	<<for _k to 0; _k lt _currentShipCount; _k++>>
    		<<set _id to /* TBD */>>
    		<<set _type to /* TBD */>>
    		<<set _currentShips.push({
    			id : _id,
    			type : _type,
    			value :  0
    		})>>
    	<</for>>
    <</for>>
    
    <</silently>>
    
    The /* TBD */ bits are the parts where I have no clue how you want to go about assigning relationship details. For example:
    1. How many relationships should each person have?
    2. Once you know that, how do you determine which people are going to be connected to each other? Further, do those connections change the number of relationships?
    3. Once you know that, how do you determine what each type should be? Specifically, you're not specifying gender, so how do you determine that person A is person B's mother, sister, aunt, daughter, girlfriend, father, brother, uncle, son, boyfriend, friend, acquaintance, enemy?
    Et cetera ad nauseam.

    Randomizing simple details is fairly easy. Randomizing relationships/connections is a tad more complicated.

    Zhark wrote: »
    Your examples show a refence scheme $people[$i].relationships[$k].id so if I am undestanding correctly (using my data above):
    $people[0].relationships[0].id
    
    would return:  1001
    and $people[1].relationships[4].type
    
    would return:  wife
    
    Am I understanding you correctly?
    It seems like you're on the right track.
  • Thank you very much TheMadExile. That has been very informative. I will play around with what you have told me. Thank you also for the coding style advice. It has been years since I have actually coded, so my skills are a bit rusty.

    ~Z
  • Okay, the rusty newbie that I am is probably making a hash of this... I replaced $/*TBD*/ with some data, so I am attempting to 'debug' this code:
    <<silently>>
    /*
    	Set up the name and teams arrays.
    
    */
    <<set $names to ["Abby", "Bill", "Carly", "Dennis", "Erica", "Fred"]>>
    <<set $teams to ["Badgers", "Cougars", "Foxes", "Pumas", "Hawks", "Doves", "Osprey", "Civits"]>>
    
    /* Set up the $people array, filled in with randomized people. */
    <<for _i to 0; _i lt $names.length; _i++>>
    	<<set $people.push({
    		name : $names.shift(),
    		id : 1000 + _i,
    		team : $teams.random(),
    		age : random(18, 65),
    		relationships : []
    	})>>
    <</for>>
    
    /* Set up each person's relationships array, filled in with randomized people. */
    <<for _i to 0; _i lt $people.length; _i++>>
    	<<set _currentShips to $people[_i].relationships>>
    	<<set _currentShipCount to 2>>
    	<<for _k to 0; _k lt _currentShipCount; _k++>>
    		<<set _id to 1000+_k>>
    		<<set _type to "none yet">>
    		<<set _currentShips.push({
    			id : _id,
    			type : _type,
    			value :  0
    		})>>
    	<</for>>
    <</for>>
    
    <</silently>>
    

    and I am getting the following error response from Twine:
    	Error: <<set>>: bad evaluation: Cannot read property 'push' of undefined
    Error: <<set>>: bad evaluation: Cannot read property 'push' of undefined
    Error: <<set>>: bad evaluation: Cannot read property 'push' of undefined
    Error: <<set>>: bad evaluation: Cannot read property 'push' of undefined
    Error: <<set>>: bad evaluation: Cannot read property 'push' of undefined
    Error: <<set>>: bad evaluation: Cannot read property 'push' of undefined
    
    
    Error: <<for>>: bad conditional expression: Cannot read property 'length' of undefined
    

    For my testing, I have no story javascript nor any story stylesheet. I've attached a screen shot of the errors with the mouse hover-over.

    It seems I have to define the relationships: [] part of the code before I can push them into the $people array. Am I interpretting that error correctly?
  • Zhark wrote: »
    It seems I have to define the relationships: [] part of the code before I can push them into the $people array. Am I interpretting that error correctly?
    That is not the problem.

    As to what is. I'm assuming that the issue is that $people is not being initialized as an array. I didn't include that in my original example as I kind of assumed that it was implied, and I didn't expect that you would not include it yourself. I did include the $names and $teams arrays, so I suppose I should have added $people as well. Mea culpa.

    FIND:
    /* Set up the $people array, filled in with randomized people. */
    <<for _i to 0; _i lt $names.length; _i++>>
    
    REPLACE WITH:
    /* Set up the $people array, filled in with randomized people. */
    <<set $people to []>>
    <<for _i to 0; _i lt $names.length; _i++>>
    

    Zhark wrote: »
    Okay, the rusty newbie that I am is probably making a hash of this... I replaced $/*TBD*/ with some data, so I am attempting to 'debug' this code:
    /* Set up each person's relationships array, filled in with randomized people. */
    <<for _i to 0; _i lt $people.length; _i++>>
    	<<set _currentShips to $people[_i].relationships>>
    	<<set _currentShipCount to 2>>
    	<<for _k to 0; _k lt _currentShipCount; _k++>>
    		<<set _id to 1000+_k>>
    		<<set _type to "none yet">>
    		<<set _currentShips.push({
    			id : _id,
    			type : _type,
    			value :  0
    		})>>
    	<</for>>
    <</for>>
    
    I see a few issues straight away, and, again, I suppose that I should have been clearer.

    I didn't mean that you'd literally be able to use a simple expression to determine the ID of the relations when I wrote:
    <<set _id to /* TBD */>>
    
    You cannot simply do something like:
    <<set _id to 1000+_k>>
    
    Unless you really want to assign the first person within $people, whose ID happens to be 1000, a relationship with themselves, as the first iteration of 1000 + _k will be 1000.

    Additionally, you're probably also going to need code, after pushing the current relationship object, to add a similar object to the other person—i.e. you create a relation within person A with person B, so you should also create the reciprocal relation within person B with person A.

    For example: (I also changed most of the variables to try to make it a little clearer as to what's going on)
    /*
    	Set up each person's relationships array, filled in with randomized people.
    */
    <<for _i to 0; _i lt $people.length; _i++>>
    	<<set _currentPerson to $people[_i]>>
    	<<set _relationshipCount to 2>>
    	<<for _k to 0; _k lt _relationshipCount; _k++>>
    		/*
    			Set up the ID and type of the partner (somehow).
    		*/
    		/* set _partnerId to something, this will likely require logic */
    		/* set _partnerType to something, this will likely require logic */
    
    		/*
    			Add a relationship for the current person with the partner.
    		*/
    		<<set _currentPerson.relationships.push({
    			id : _partnerId,
    			type : _partnerType,
    			value :  0
    		})>>
    
    		/*
    			Get the partner and set up the reciprocal type.
    		*/
    		<<set _partner to $people.find(function (p) { return p.id === _partnerId; })>>
    		/* set _reciprocalType to the reciprocal of _partnerType (e.g. mother → son) */
    
    		/*
    			Add a reciprocal relationship for the partner with the current person.
    		*/
    		<<set _partner.relationships.push({
    			id : _currentPerson.id,
    			type : _reciprocalType,
    			value :  0
    		})>>
    	<</for>>
    <</for>>
    
  • I did include the $names and $teams arrays, so I suppose I should have added $people as well. Mea culpa.

    It isn't your responsibility to write my code for me. I do appreciate the pointers and help. As I said, I am rusty at coding (haven't done it for over a decade), so this is a re-learning experience for me. I've tried to follow the examples I've found and the documentation as best I can, but at times it seems like I am missing some things, or confusing my old programming experience with the new languages/formats.

    Again, I REALLY do appreciate your help and patience.

    That said:
    I didn't mean that you'd literally be able to use a simple expression to determine the ID of the relations when I wrote:
    <<set _id to /* TBD */>>
    
    My first attempt was to make it a for loop with an if statement to not add an relationship.id that equaled the $people.id. When I got the errors, I trimmed it back to just shoving some test data into the setting in order to simplify my testing. My initial code attempt for data fill was:
    /* Set up each person's relationships array, filled in with randomized people. */
    <<for _i to 0; _i lt $people.length; _i++>>
    	<<set _currentShips to $people[_i].relationships>>
    	<<set _currentShipCount to $people.length -1>>
    	<<for _k to 0; _k lt _currentShipCount; _k++>>
    		<<if $people[_i].id neq 1000 +_k>>
    			<<set _id to 1000 +_k>>
    			<<set _type to "none">>
    			<<set _currentShips.push({
    				id : _id,
    				type : _type,
    				value :  0
    			})>>
    		<</if>>
    	<</for>>
    <</for>>
    

    I apparently clipped the $people array initializer line when I was updating the code with your suggestions. Thanks for the catch. That cleared up the majorty of my code errors.

    Here is my final test code:
    <<silently>>
    /* 	Set up the name and teams arrays.
    */
    <<set $names to ["Abby", "Bill", "Carly", "Dennis", "Erica", "Fred"]>>
    <<set $teams to ["Badgers", "Cougars", "Foxes", "Pumas", "Hawks", "Doves", "Osprey", "Civits"]>>
    
    /* Set up the $people array, filled in with randomized people.
    */
    <<set $people to []>>
    <<set _counter to $names.length>>
    <<for _i to 0; _i lt _counter; _i++>>
    	<<set $people.push({
    		name : $names.shift(),
    		id : 1000 + _i,
    		team : $teams.random(),
    		age : random(18, 65),
    		relationships : []
    	})>>
    <</for>>
    
    /* Set up each person's relationships array, filled in with randomized people.
    */
    <<set _counter to $people.length>>
    <<for _i to 0; _i lt _counter; _i++>>
    	<<set _currentShips to $people[_i].relationships>>
    	<<set _currentShipCount to _counter>>
    	<<for _k to 0; _k lt _currentShipCount; _k++>>
    		<<if $people[_i].id neq 1000 +_k>>
    			<<set _id to 1000 +_k>>
    			<<set _type to "none">>
    			<<set _currentShips.push({
    				id : _id,
    				type : _type,
    				value :  0
    			})>>
    		<</if>>
    	<</for>>
    <</for>>
    
    <</silently>>
    
    /* Output of test data $people array without relationship data
    */
    <<for _i to 0; _i lt $people.length; _i++>>
    	$people[_i].name, $people[_i].id, $people[_i].team, $people[_i].age
    <</for>>
    
    /*Output for each person's relationship array in $people
    */
    <<for _i to 0; _i lt $people.length; _i++>>
    	<<nobr>>
    	$people[_i].name,\\ 
    	<<for _k to 0; _k lt $people.length-1; _k++>>
    		<<set _currentShips to $people[_i].relationships>>
    		_currentShips[_k].id, _currentShips[_k].type, _currentShips[_k].value\\
    	<</for>>
    	<</nobr>>
    <</for>>
    
    An example out put is:
    Abby, 1000, Doves, 54
    Bill, 1001, Foxes, 28
    Carly, 1002, Osprey, 56
    Dennis, 1003, Hawks, 63
    Erica, 1004, Cougars, 44
    Fred, 1005, Hawks, 27
    
    Abby,\\ 1001, none, 0\ 1002, none, 0\ 1003, none, 0\ 1004, none, 0\ 1005, none, 0\ 
    Bill,\\ 1000, none, 0\ 1002, none, 0\ 1003, none, 0\ 1004, none, 0\ 1005, none, 0\ 
    Carly,\\ 1000, none, 0\ 1001, none, 0\ 1003, none, 0\ 1004, none, 0\ 1005, none, 0\ 
    Dennis,\\ 1000, none, 0\ 1001, none, 0\ 1002, none, 0\ 1004, none, 0\ 1005, none, 0\ 
    Erica,\\ 1000, none, 0\ 1001, none, 0\ 1002, none, 0\ 1003, none, 0\ 1005, none, 0\ 
    Fred,\\ 1000, none, 0\ 1001, none, 0\ 1002, none, 0\ 1003, none, 0\ 1004, none, 0\
    

    This was just to make sure that it was possible before I started down a path of insanity only to find out 10,000 lines of code later that it couldn't be done. (Been there, done that.)

    I appreciate your suggestions for the reciprocation of relationship data, and will keep them in mind when I get to the point of coding the relationships. It's going to need some age and gender and other relationship checks to get it going (like for Bob to be your uncle, he needs to be related to your parents first).

    FYI: This was just a test script. I intend for there to be many many more than just 6 people in the game, but I don't intend for each person to have an relationship array with data for every other person. It would be ridiculous to code for 100+ people and enter data for each persons relationship to all 100 other people if 90% of that data is going to be 'no relationship'. Not having an entry in the array would be 'no relationship'. Additionally, a lot of the randomness displayed in the test code will be done away with as it was just a way to fill data into the variables for testing.

    Thank you again, TheMadExile. Your assistance has been very helpful and is greatly appreciated.
  • P.S. Thanks for showing me how to comment code in Twine.
  • Zhark wrote: »
    P.S. Thanks for showing me how to comment code in Twine.
    Each Story Format defines what markup it accepts/uses to indicate a comment, SugarCube supports more styles than most.
    /* multi-line
    block */

    /% SugarCube's own
    multi-line block %/

    <!-- HTML's
    multi-line block -->
Sign In or Register to comment.