Can a key in a datamap have multiple values?

Twine Version: 2.71.0
Story Format: Sugarcube 2.36.1

Hi folks! I’m trying to make my first procedurally generated story in Sugarcube, and it involves creating a database of paintings for the player to select from for a gallery exhibition. Each painting has defined colors, tones, styles, etc., and for every painting the player selects, I’ll track the characteristics and add them up to see what the overall trends are. But - is it possible to have multiple values per key/value pair? A painting’s colors might not be JUST blue/purple; it might also have some red.

For example, this is how I’ve structured my datamap:

<<set $paintingSelection to {
	"Blackbyrd":  {
		id: 1,
		color: "grayscale",
		subject: "animal",
		mood: "melancholia/depression",
        style: "illustration/sketch"
	},
	"Voice of Perseverance":  {
		id: 2,
		color: "yellow",
		subject: "human",
		mood: "joy/happiness",
        style: "photorealism"
	}
}>>

Would it be possible to say that Blackbyrd’s color values are BOTH “grayscale” and “red” (let’s say it’s a mostly grayscale painting with a single pop of red)? Or would I have to create a second key/value pair called secondaryColor?

Also, how do you select and call a random datamap element in a passage? I see plenty of examples where you’d do that with an array, but I’m not experienced enough to know if datamaps work the same way.

Thanks so much for any help you can provide!

a value can itself be an array. so (pseudocode)

blackbyrd: {
color: [grayscale, red]
}
1 Like

Hello,

You could nest the color like so.

<<set $paintingSelection to {
	"Blackbyrd":  {
		id: 1,
		color: {
               prominent: "red",
               primary: "grayscale"
               },
		subject: "animal",
		mood: "melancholia/depression",
        style: "illustration/sketch"
	}
}>>

Or what Aster suggests. :slight_smile:
Also, Change this object to an array of Objects. Then you can randomly select one and access the values with dot notation.

<<set $paintingSelection to [
	{
		id: 1,
        name: "Blackbyrd",
		color: {
               prominent: "red",
               primary: "grayscale"
               },
		subject: "animal",
		mood: "melancholia/depression",
        style: "illustration/sketch"
	},
	{
		id: 2,
        name: "Voice of Perseverance",
		color: {
               prominent: "black",
               primary: "yellow"
               },
		subject: "human",
		mood: "joy/happiness",
        style: "photorealism"
	}
]>>
<<set _temp to either($paintingSelection)>>
<<= _temp.name >>
//color
<<= _temp.color.primary >>

The output would then be the randomly selected object values.

3 Likes

If all this data is never going to change, it should definitely be put in the setup object or it will bog the engine down.

by “setup object” do you mean the StoryInit passage? (That’s where it all is currently)

Preface. The term datamap is a Harlowe-ism used to refer to a Map object. Beyond that, what you’re using is a generic Object (henceforth: object), rather than a Map.

 

Yes. You have three basic options:

1. As you surmised, add a new property (henceforth: key) to hold the secondary color. For example:

"Blackbyrd": {
	id: 1,
	color: "grayscale",
	secondaryColor: "red",
	/* additional properties… */
},

Accessing the colors:

/* Primary color. */
$paintingSelection["Blackbyrd"].color

/* Secondary color. */
$paintingSelection["Blackbyrd"].secondaryColor

 
2. As suggested by Aster F, change color’s value to be an array that holds the colors. You will need to remember the order, but that should be easy. For example:

"Blackbyrd": {
	id: 1,
	color: ["grayscale", "red"],
	/* additional properties… */
},

Accessing the colors:

/* Primary color. */
$paintingSelection["Blackbyrd"].color[0]

/* Secondary color. */
$paintingSelection["Blackbyrd"].color[1]

 
3. As suggested by dsherwood, change color’s value to be an object that holds a key/value pair for each color. For example:

"Blackbyrd": {
	id: 1,
	color: {
		primary: "grayscale",
		secondary: "red"
	},
	/* additional properties… */
},

Accessing the colors:

/* Primary color. */
$paintingSelection["Blackbyrd"].color.primary

/* Secondary color. */
$paintingSelection["Blackbyrd"].color.secondary

 
All three options will accomplish what you want, so use whichever feels best to you.

 

You have two basic options:

1. Use the Object.entries() static method, which returns an array of an object’s own key/value pairs where each pair is returned as a [key, value] array, along with the <Array>.random() instance method to randomly select a key/value pair from the base object. For example:

<<set [_paintingName, _painting] to Object.entries($paintingSelection).random()>>

 
2. As suggested by dsherwood, change $paintingSelection’s value to be an array that holds the painting objects. For example:

<<set $paintingSelection to [
	{
		id: 1,
		name: "Blackbyrd",
		/* additional properties… */
	},
	{
		id: 2,
		name: "Voice of Perseverance",
		/* additional properties… */
	}
]>>

Selecting a random member:

<<set _painting to $paintingSelection.random()>>
/* Accessing the painting's name is: _painting.name */

That said. There is a, minor, downside to using an array as the overall container. Namely, you can no longer easily access a painting simply by its key (name). For example, say you wanted to access the Blackbyrd painting object:

/* You must search the array to find the correct painting object. */
<<set _painting to $paintingSelection.find(painting => painting.name === "Blackbyrd")>>

Conversely, when using an object as the overall container it’s simply:

<<set _painting to $paintingSelection["Blackbyrd"]>>

If your plan is to normally pick a painting randomly, then the downside is mostly irrelevant as you won’t be accessing the paintings container by key often, or at all.

 
Both options will accomplish what you want, so use whichever feels best to you.

 

They mean the special predefined setup object that authors may use to set up various bits of static data. Generally, you would use this for data that does not change and should not be stored within story variables as that would make it part of the history—thus bloating both in-play memory usage and storage for play sessions and saves.

The best place to initialize data going into setup is generally within the StoryInit special passage, same as with story variables, or the Story JavaScript.

For example, within the StoryInit special passage:

<<set setup.paintings to {
	"Blackbyrd": {
		id: 1,
		/* other properties… */
	},
	"Voice of Perseverance": {
		id: 2,
		/* other properties… */
	}
}>>

Or, within the Story JavaScript:

setup.paintings = {
	"Blackbyrd": {
		id: 1,
		/* other properties… */
	},
	"Voice of Perseverance": {
		id: 2,
		/* other properties… */
	}
};
5 Likes

Thank you so much for the replies here, all of which have been helpful to me! After some tinkering, I now have the ‘color’ property set up as an array within the object (also, now I know that ‘object’ is the more correct term here, much appreciated! I thought it was called a datamap in both Harlowe and Sugarcube). I now have an object that looks like this:

<<set $paintingCollection to [
	{
          id: 1,
          name: "Blackbyrd",
          primaryColor: "grayscale",
          secondaryColor: "red/pink",
          subject: "human",
          mood: "melancholia",
          style: "line art",
          description: "It's a painting that looks like line art, with thick black strokes of paint against a gray background. A woman holds a single pink carnation, the only hint of color on the canvas. She looks up at a blackbird hovering above her. The creature's glittering eyes are trained on the flower. As you gaze at the canvas, you feel a sense of regret, of lost time."
      },
     {
          id: 2,
          name: "Perseverance",
          primaryColor: "yellow",
          secondaryColor: "blue/purple",
          subject: "human",
          mood: "joy",
          style: "photorealism",
          description: "A fist raised in the air, painted with such detail that every wrinkle and bump of the skin stands out. A corona of light spreads out from the fist like it is the sun, in shades of bright gold and radiant purple. The colors tell a story of joyful determination."
      },
      {
          id: 3,
          name: "Owl",
          primaryColor: "blue/purple",
          secondaryColor: "green",
          subject: "animal",
          mood: "fear",
          style: "neo-expressionism",
          description: "A neo-expressionist owl dominates the canvas. Heavy lines of blue paint, dotted with green, give the bird of prey a scowling, threatening expression. Its feathers look cold and heavy, like metal."
      },
//this continues for quite some time, with many paintings

The paintings will always be random, so I don’t have to access a specific object. Before you replied, I’d tried dsherwood’s method and modified it a tad, and that’s working. It looks like this:

The painting you pull is called <<set $currentPaintingPull to either($paintingCollection)>><<= $currentPaintingPull.name >>. <<=$currentPaintingPull.description >>

Then, if the player decides they want to keep this painting, I change the values in a second object to “save” the painting choice. Otherwise the player will keep pulling random paintings from a stack until they’ve decided on 8 that they want to keep.

<<nobr>>
<<if $numberPaintingsPulled is 1>>
	<<set $chosenSelections[0].name to $currentPaintingPull.name>>
    <<set $chosenSelections[0].primaryColor to $currentPaintingPull.primaryColor>>
    <<set $chosenSelections[0].secondaryColor to $currentPaintingPull.secondaryColor>>
    <<set $chosenSelections[0].subject to $currentPaintingPull.subject>>
    <<set $chosenSelections[0].mood to $currentPaintingPull.mood>>
    <<set $chosenSelections[0].style to $currentPaintingPull.style>>
<<elseif $numberPaintingsPulled is 2>>
	<<set $chosenSelections[1].name to $currentPaintingPull.name>>
    <<set $chosenSelections[1].primaryColor to $currentPaintingPull.primaryColor>>
    <<set $chosenSelections[1].secondaryColor to $currentPaintingPull.secondaryColor>>
    <<set $chosenSelections[1].subject to $currentPaintingPull.subject>>
    <<set $chosenSelections[1].mood to $currentPaintingPull.mood>>
    <<set $chosenSelections[1].style to $currentPaintingPull.style>>
//again, this goes on for quite some time to account for all 8 selections...there is probably a more efficient way to do this so that I don't repeat the code 8 times, but hey, it works :P
<</if>>
<</nobr>>

My next challenge is to figure out how to handle “restocking” the $paintingCollection object if the player goes through every single one and hasn’t picked 8. The structure I’ve chosen means that no paintings get repeated, which is good, but then I get an error message if they’ve all been pulled. But that’s a problem for me in like…a week, haha.

Again, thanks to everyone who responded for your expertise. I’m still learning a lot about JavaScript, as you can see, and this has taught me a great deal!

Hello,

A loop will make thing easier. Be sure to match the selected painting with the paintingCollection.id when you set the $numberPaintingPulled value. Unless the $numberPaintingPulled value is not the id but rather a count. Then I am not sure how you are choosing the painting…

<<set $numberPaintingsPulled to 2>>
<<set $newArray to []>>
<<for _i to 0; _i lt $paintingCollection.length; _i++>>
<<if $numberPaintingsPulled eq $paintingCollection[_i].id>>
    <<run $newArray.push($paintingCollection[_i])>>
    <</if>>
<</for>>