Howdy, Stranger!

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

Managing multiple instances of similar variables

So I'm fiddling around with Twine for the first time, mocking up a simple RPG.

I have next to no experience with this kind of thing, so apologies if this is an obvious question, but is there some way to make an "array of arrays?"

Consider for example an party of 3 characters, each with health, mana and level. Past forum answers suggest to me that I ought to use an array for each (in this example, a 3-variable array, health, mana and level). With multiple arrays being created and destroyed through character creation and death, how do I manage this? My first inclination was to hard-code 10 or so character arrays, but that would require constant checks to see if each array is populated by a character or not. I've got one array working just fine, but I can't find guidance on going farther.

Basically, the question is this: how should I manage several sets of similar variables that apply to different characters, objects, etc? I hesitated to use the term "array of arrays," since it sounds wretched, but that's what comes to mind. Where an array is a list (one dimension), I'm looking for a grid (two dimensions).

Comments

  • edited July 2015
    Since you're new, I'm assuming you're using the default Harlowe format.

    I'm not really an expert on arrays and array-type things, but in some of my projects I've found the (datamap:) type to be pretty handy. It's a specialized array that you set up with pairs of string variables and their values, which can be either numbers or strings. You can access the sub-variables with commands like (set: $datamapA's propertyInQuestion to NewValue) or (print: $datamapB's yetAnotherProperty).

    So, for instance, you can do something like this:
    ::Datamapinit
    (set: $Hiro to (datamap: "name","Hiro Protagerson","health",100,"mana",50,"panache",12,"favorite food","ramen"))
    (set: $Heela to (datamap: "name","Heela McTightpants","health",80,"mana",150,"spunk",12,"favorite food","cake"))
    (set: $Bruti to (datamap: "name","Bruti The Boisterous","health",150,"mana",10,"RAGE",10000,"favorite food","blood"))
    (set: $party to (datamap: "Leader",$Hiro,"Support",$Heela,"Murder Specialist",$Bruti))

    (goto: "datamaptest")

    ::Datamaptest

    [Display Party]<taggo|(click-replace: ?taggo)[(print: $party)]

    and it will produce output like this:
    iVeG0zO.jpg

    The downside of using a map that contains other maps is you'll have to re-insert each slot variable if you change a character value. So, say Hiro gets attacked by a monster. You can set his health down easily using the 's method, but in order to get $party to realize it, need to do something like
    (link: "Monster attacks Hiro")[(set: $Hiro's health to 42)(set: $party's Leader to $Hiro)(goto: "datamaptest")]

    All that said, there are other options as well, especially if you try the Sugarcane format that has more javascript functionality. Maybe someone who knows more about it can help you there, or correct any mistakes I made above, since I'm still relatively new to the coding side of things myself.

    Edit: had a dumb bug in my examples up there. You should set the initial values in a different passage than the one you're using for testing, because if you come back to the one that sets them to begin with, they'll just be reset... whoops.
  • Since you're new, I'm assuming you're using the default Harlowe format.

    I am. Beg pardon, should have included that.
    The downside of using a map that contains other maps is you'll have to re-insert each slot variable if you change a character value. So, say Hiro gets attacked by a monster. You can set his health down easily using the 's method, but in order to get $party to realize it, need to do something like...

    I feel like I am right on the cusp of figuring this out. I'll use your example. Say I want to swap characters in and out of slots in the party lineup, e.g. swap character datamaps in and out of a party datamap. So we put Hiro in slot 1 of the party datamap, the leader slot.
    (link: "Monster attacks Hiro")[(set: $Hiro's health to 42)(set: $party's Leader to $Hiro)(goto: "datamaptest")]

    If I read this code correctly, it's hardcoded that the monster's attack hits Hiro. How do we get the attack to hit the guy in slot 1, regardless of whether that's Hiro or not? How can we get a subtraction from "slot 1 guy's health," have the code recognize that "slot 1 guy" is Hiro, and assign the subtraction to his datamap?

    Thank you for your help.
  • edited July 2015
    If you want to access the individual heroes by party slot, you could use something like this:
    (link: "Monster attacks Hiro")[(set: $party's Leader's health -= 58)(goto: "datatest")]
    (link: "Heela heals Hiro")[(set: $party's Leader's health += 50)(set: $party's Support's mana -= 11)(goto: "datatest")]
    (link: "Bruti uses Unleash!")[(set: $party's "Murder Specialist"'s health -= 21)(set: $party's "Murder Specialist"'s RAGE -= 2)(goto: "datatest")]
    *Operators like (set: $var += 1) are shorthand for (set: $var to ($var + 1))

    That's probably more convenient if you want to keep $party as your main container object. However, keep in mind that when it's set up like this, the individual $character arrays are separate from their representations in the $party one; in essence, the $party datamap contains copies of the original characters, not the originals. I'm not entirely sure if there is any way to tell the $party datamap to reference or contain the character maps themselves or a pointer to their contents or anything. It might be possible, but I'm not that well versed in this stuff.

    If you prefer to keep the $party as the primary object you're going to interact with, you'll probably want to do something like this when/if someone changes their party slot:
    (link: "Replace Heela with Sparkie")[(set: $Heela to $party's Support)(set: $party's Support to $Sparkie)]

    This will copy $party's version of Heela into her base object ($Heela) for reference purposes and put the (currently undefined) contents of $Sparkie in the $party's Support slot.

    (you can also use (put:) instead of (set:) - it's essentially the reverse process, which translates into words easier. (put: $party's Support into $Heela) is putting the contents of the support slot into Heela's reference variable. (put: $Sparkie into $party's Support) does basically what it says on the tin. Saying (set: $x to 2) is readily understandable enough, but copying variable contents around can get tricky to remember.)

    Of course, since Sparkie is a dog who can't heal at all, you can just invert it like so:
    (link: "Sparkie is a terrible support, let's get Heela back")[(set: $Sparkie to $party's Support)(set: $party's Support to $Heela)(goto: "datatest")]

    You can also extend this as far as you want, as far as I know, though it does get sort of cumbersome eventually.
    ::datainit
    ...
    (set: $equipment to (datamap: "Righthand","Falcon Paunch","Lefthand","Sinister Fingers","Top","Topless!","Bottom","Comfy Underpants","Accessory","Panache Bangle"))
    (set: $Hiro to (datamap: "name","Hiro Protagerson","health",100,"mana",50,"panache",12,"favorite food","ramen","Equipment",$equipment))

    ::datatest
    ...
    (set: $sword to (datamap: "Name","Hiro's Bumper Sword","Type","Blunt, Actually","Attack","Lots","Magislots",12))
    (link: "Give `$Hiro` a sword")[(set: $Hiro's Equipment's Righthand to $sword)(goto: "datatest")]
    (link: "Give `$party's Leader` a sword")[(set: $party's Leader's Equipment's Righthand to $sword)(goto: "datatest")]

    (This also illustrates the need to remember if you're referencing the $party copy (aka $party's Leader) or the reference $Hiro. I've made a lot of those mistakes in the past. You'll note that the first link, which is the way I would usually intuitively write the code, doesn't work for the copy of Hiro currently in the party, and might actually make unwanted changes to his reference variable.)
  • I'm sorry, I'm sure I'm being dense, but there's one part of this that still eludes me.

    I'll try to come at this from another example. Let's say we're back at camp sitting around the campfire that all RPG heroes apparently have to sit around. The player character can talk to each character, interact with them, swap equipment etc.

    So for example, let's say there's a series of "talk to" passages that call a character's morality and attitude variables. Naturally, that code should exist once rather than individually for each character. Therefore, it has to somehow call the variables of the character being talked to. Going by your patient explanation, I have gotten to taking a character's variables from his datamap and putting them into a "character currently being interacted with" datamap. What I cannot figure out how to do is denote which character this is, so that the code can put the data back in the right datamap when the conversation is complete.

    In terms of your original example, I get mapping Hiro's data and I get using a party map. What I do not get is how to tell the code that it's Hiro in slot 1 when the player tells the game to bench Hiro, thus taking him out of the party. There's data in slot 1; it needs to go back into Hiro's datamap; how does one specify that?

    Put another way, how do I use datamaps to write code that can change variables in any of a set of datamaps, depending on which datamap the player specifies?

    Thanks again.
  • While struggling to figure out how to explain this, I came up with another way to describe it.

    (Describing this with my level of competence is like trying to explain what Spanish word you'd like to know, while being mute. Sorry.)

    Here's the function I want. Where $char1 is the datamap of the character in slot 1, Hiro; and $Hiro is the datamap of his stats elsewhere:

    (link: Send $leader's name back to camp.)[(put: $leader into [WHICHEVER DATAMAP's 1st = HIRO])(goto: "datatest")]

    The [ALLCAPS] bit is what I'm missing. Search a set of datamaps for the one whose 1st variable matches the datamap in $leader, copy $leader datamap over that datamap.
  • edited July 2015
    Generally elements/items within collections like (array:) and (datamap:) can either be accessed by using a index position (number) or by using a key (name), but not both ways.

    Array's use index positions (numbers):
    (set: $array to (array: "one", "two", "three"))
    The array's First element is: (print: $array's 1st)
    The array's Third element is: (print: $array's 3rd)
    
    Datamap's use keys (names):
    (set: $map to (datamap: "health", 100, "age", 18))
    Bob's health is: (print: $map's health)
    Bob's age is: (print: $map's age)
    
  • greyelf wrote: »
    Generally elements/items within collections like (array:) and (datamap:) can either be accessed by using a index position (number) or by using a key (name), but not both ways.]

    This is why I'm trying to use datamaps, because for something like a character with 10+ variables its much easier to call $map's age than it is to call $array's 12th.

    Another way to come at this: is there a way to call a datamap based on that datamap's name, or in other words, to call not $map's age but $x's age, where x is a string defined as "map"?


  • Something like the following?
    (set: $bob to (datamap: "health", 100, "age", 18))
    (set: $people to (datamap: "bob", $bob))
    
    (set: $whom to "bob")
    (set: $person to $people's $whom)
    Person's health (print: $person's health)
    
  • greyelf wrote: »
    Something like the following?

    Only if I can get data back into a datamap this way, as well as getting it out.

    Is there some way to (set:) variables in $bob based on something in $people so that if $sally were also present, we could (set:) the health of whichever of $bob or $sally were in $people? That's what I'm after.

  • Not that I know of.

    Personally I don't think Twine is the right tool for creating a RPG, especially one featuring a complex object based npc sub-system.

    But if I had to do something like that then I would use SugarCube instead of Harlowe purely because it allows Author to add custom features to the story format and allows direct access of $variable system from Jacascript code.
  • edited July 2015
    I think that in order to do what you're asking, you'd have to dynamically generate a variable name in the expression. In theory, it might be something like this:
    (link: "update the char after talking")[(set: ('$' + ($talkTarget's name)) to $talkTarget)]
    ...the idea being that if $talkTarget's name key returns 'Hiro', it evaluates to $ + Hiro, concatenates it together and copies $talkTarget over $Hiro

    ...but that doesn't seem to work. It might just be that I can't figure out the syntax, or that you can't get the system to recognize a variable by concatenating a $ to it, or that this is just not possible in Harlowe (or perhaps Twine in general).

    You could try using a bunch of conditionals to determine whether a given character is present, but basically if you really want to do complex variable structures, you might be better off trying SugarCube or maybe a more robust engine that will let you use a full object-based code language.

    If you do decide to try SugarCube, this thread might be helpful: http://twinery.org/forum/discussion/3079/retrieving-values-from-javascript-objects-sugarcube
Sign In or Register to comment.