Comparative Value

Twine Version: 2.9.1.0
SugarCube: 2.37.0

I’m in the planning stage for my next project, and I’m wanting to have the ending determined by comparing the relative influence values of the groups that the player interacts with.

Basically, for this:

$Guild1.Inf = 5
$Guild2.Inf = 4
$Guild3.Inf = 3
$Guild4.Inf = 2
$Guild5.Inf = 1

I want it to tell me that Guild 1 has the highest influence.

Now, I did a bit of looking around, and saw that I could return the highest variable:

Math.max($Guild1.Inf, $Guild2.Inf, $Guild3.Inf, $Guild4.Inf, $Guild5.Inf)

But what that does is return “5,” because it’s the highest one. I’m not looking for what the highest variable is, I’m looking for something that will tell me which one has the highest number.

Closest I can think of is this:

<<set $MostInfluence to Math.max($Guild1.Inf, $Guild2.Inf, $Guild3.Inf, $Guild4.Inf, $Guild5.Inf)>>
<<if $MostInfluence eq $Guild1.Inf>>
You have the most influence from $Guild1.Name.
<<elseif $MostInfluence eq $Guild2.Inf>>
You have the most influence from $Guild2.Name.
<<elseif $MostInfluence eq $Guild3.Inf>>
You have the most influence from $Guild3.Name.
<<elseif $MostInfluence eq $Guild4.Inf>>
You have the most influence from $Guild4.Name.
<<else>>
You have the most influence from $Guild5.Name.
<</if>>

The problem with doing that is that it will just default to the first one that matches it, so if I have equal influence, it’s going to tell me that I have the most influence with guild 1.

Now, I could put in

<<if $Guild1.Inf eq $Guild2.Inf and $Guild1.Inf eq $Guild3.Inf and $Guild1.Inf eq $Guild4.Inf and $Guild1.Inf eq $Guild5.Inf>>
You have equal influence among the guilds.
<<else>>
The code above
<</if>>

but if the stats are

$Guild1.Inf = 4
$Guild2.Inf = 4
$Guild3.Inf = 4
$Guild4.Inf = 2
$Guild5.Inf = 1

How would I make it print

You have the most influence with $Guild1.Name, $Guild2.Name and $Guild3.Name.

I’m curious how much of each “Guilds” definition is static, and how much isn’t.
eg. how many of the properties of each “Guilds” definition have values that change during the playthrough, and how many have values that don’t change once initialised.

Because this exercise would be a lot easier if the influence values with being stored in an Object structure like…

<<set $influence to {Guild1: 5, Guild2: 4, Guild3: 3, Guild4: 2, Guild5: 1}>>

…and if each guild’s static (stateless) information was being stored on the special setup variable.

But to answer your question about your existing implementation…

note: The value of a Story Variable can also be accessed via the State.variables property, using the variable’s Name as an identifier.

Guild 1's influence is <<= State.variables["Guild1"] >>

1: Create an Array of the guild’s identifiers.

<<set _guilds to ["Guild1", "Guild2", "Guild3", "Guild4", "Guild5"]>>

2: Use the <array>.sort() method to sort those identifiers in descending order based on their associated influence value.

<<run _guilds.sort( (a, b) => State.variables[b].Inf - State.variables[a].Inf )>>

3: Obtain the max influence using 1st identifier element in sorted Array.

<<set _max to State.variables[_guilds[0]].Inf>>

4: Determine which identifiers have a influence equal to maximum.

<<set _guilds to _guilds.filter( (id) => State.variables[id].Inf === _max )>>

5: Map the guild identifiers with their Names.

<<set _guilds to _guilds.map( (id) => State.variables[id].Name )>>

6: Define a function in the project’s Story JavaScript area to convert an Array of String values to a single Oxford Comma String.

setup.toOxfordCommaString = function (array) {
	return array.length === 2
    	? array.join(' and ')
    	: array.length > 2
    	? array
        	.slice(0, array.length - 1)
        	.concat(`and ${array.slice(-1)}`)
        	.join(', ')
    	: array.join(', ');
};

7: Use the new setup.toOxfordCommaString() method to output your message…

You have the most influence with <<= setup.toOxfordCommaString(_guilds)>>.
1 Like

So, to answer your first question, the influence is meant to be a variable that changes through the game. Starts at zero, works its way up. The example was just at a random point in the game. Influence would increase with certain events (completing quests means they like you more), decrease with certain events (when you exert your status as a member of the guild, it costs some influence), or be divided (when one guild splits into three separate guilds due to ideological differences). That’s the intent, anyway.

Other variables within the guild may or may not be changed, as I haven’t wholly decided on it yet. It’s still in the planning stage, after all.

I’m definitely new to arrays, so I had to read this a few times before I understood what was happening, but I think I’m able to see it now. The exception to that is the javascript section. I’m only able to figure out part of that, but I can kind of see how it works. The rest of this, I could take apart and fix if I messed it up somewhere down the line, but thankfully, I shouldn’t have any reason to mess with the javascript.

The definition of the JavaScript function I supplied was sourced from the inter-tubes, I only converted that definition to be a setup based method (after confirming it worked as required). :slight_smile:

If needed I can explain how that function is doing what it’s doing…

I would appreciate it if you did.

I’d like to know what all the code is doing, even if I don’t necessarily need to know, as the array stuff, I’m pretty sure I could do with other variables (Guild1.Out.Prim, Guild1.Out.Sec, Guild1.MemberCount, Guild1.Rank) if I needed to know where the player was allocating outposts (both primary and secondary), which guilds have the most members, and which guilds have assigned the player the highest rank.

I don’t think I need to know what the story javascript is doing, per se, but it would be nice to know.

So JS has a “ternary” operator that uses a question mark and a colon and is basically an inline if-statement: condition ? codeIfTrue : codeIfFalse

So if the array has two entries it’s joining them with “and”:

array.length === 2 ? array.join(" and ") : etc.

And then if the array does not have two entries it asks another question: does it have more than two entries? If so, it sticks an “and” on the beginning of the last entry using the backtick template literal syntax and then joins the whole thing together with commas.

To do this it uses the slice method, which copies a slice of an array, and concat, which concatenates arrays or strings to the end of an existing array.

So array.slice(0, array.length - 1) copies all but the last element. array.slice(-1) gets the last element (starts at the last element, goes until the end of the array).

    	: array.length > 2
    	? array
        	.slice(0, array.length - 1)
        	.concat(`and ${array.slice(-1)}`)
        	.join(', ')
   	: etc.

Then finally, if array does not have more than two entries then there must be one or zero (since we already checked for exactly two) so… it joins them with commas. But there will be no commas because there aren’t multiple elements to join. This just gives you the element as a string, or an empty string if there are no elements. If you ask me, it’s a little bit of a misleading way to write it, but it works just fine.

    	: array.join(', ');

I’d write it a little differently, putting the cases in numerical order and making it easy to decide whether you want the serial comma by whether you include it in that final string in the function:

setup.toCommaString = function(array) {
    // 0 or 1 entries, nothing to join, just convert to string
    return array.length < 2 ? array.join('') 
        // exactly 2, join with "and"
        : array.length === 2 ? array.join(' and ')
        // join most of them with commas, use "and" for the last
        : array.slice(0, array.length-1).join(', ')
            // add the serial comma here if you want it
            + ' and ' + array[array.length-1];
}
1 Like

That took me a few tries, but I’m pretty sure I understand it now. Thanks!

In my C programming days I abused the ternary operator once or twice. When writing:

cond ? a : b

It’s great. But once you start down the path of:

cond2 ? a : cond2 ? b : c

That way madness lies. I would rewrite this particular example as:

setup.toCommaString = function(array) {
  switch (array.length) {
    case 0:
    case 1:
      return array.join('');
    case 2:
      return array.join(' and ');
    default:
      return array.slice(0, array.length-1).join(', ') + 
             ', and ' + array[array.length-1];
  }
}
1 Like

note: the only reason I named my variant of the JavaScript method toOxfordCommaString instead of just toCommaString, is that the later could be describing an output String like one, two, three.
eg. a comma’ed output without the Oxford and.

1 Like