How to automatically generate hundreds of unique variables?

Hello all!
(SugarCube 2.30.0)

I basically have a map, and I would like to have spots that are different for each game, so that they have to be discovered. So they would need to be randomly generated, but then stay that way for the rest of the game. Of course if I set a variable that would work, but making five-hundred or more unique variables seems like something worth finding a shortcut for.

I was thinking if there was a way you could have a variable that was like “$random-unique” somehow, then you could just copy and paste a template.

Or if there is some other way of having something randomly generate and subsequently stay the same (no doubt there is) it would be nice to know about.

Thank you in advance!

The first thing that comes to mind is to just have the same variable name but tack a number to it. So like…

for (var i = 0; i < 500; i++) {
    State.variables["random_" + i] = false;
}

Another option is (and I’m making a lot of assumptions with your setup) is using X and Y coords, if you have you map locations set up as a grid.

// assuming a grid of 20 x 20 and 30 hidden spots
var spots = {};
var i, x, y, loc;
for (i = 0; i < 30; i++) {
	x = random(20);
	y = random(20);
	loc = x + "_" + y;
	if (loc in spots) { 
		i--; 
		continue; 
	}
	spots[loc] = false;
}
State.variables.spots = spots;

This will only create a list of 30 items, but they’ll be scattered over the 400 different possible locations. Then after setting the coords to check to $x and $y (as an example), you can check it with:

if ($spots[$x + "_" + $y]) // check for true
if (!$spots[$x + "_" + $y]) // check for false
if (($x + "_" + $y) in $spots) // check if it's a hidden spot at all

Anyway, those are my thoughts.

The coords idea is cool and I’d rather like to use it somewhere, but in this game the spots are in groups of six to twelve in fifty-four different passages.

For the first idea you would put that in the JavaScript and then every instance of $random would become different at the start of the game and then stay the same? (Sorry if I’m not getting it.)

The idea I have is basically a <<switch random()>> with widgets for the different things that would show up in the spot (a building, bare ground, a well, etc.), but of course it changes each time you visit the passage, and instead I need it to be random the first time, then stay the same for the rest of the game. More difficult because the spots are in groups in separate passages.

It’s not the prettiest code, but it works.

:: StoryInit
<<script>>
	variables().passage_items = {};

	setup.getRandomStuff = function() {
		var random_stuff = [
			"tree",
			"bridge",
			"well",
			"building"
		];
		return either(random_stuff);
	}
<</script>>

:: PassageHeader
<<script>>
	if (visited() === 1) {
		variables().passage_items[passage()] = setup.getRandomStuff();
	}
	temporary().random_text = variables().passage_items[passage()];
<</script>>

:: Start
Start passage

_random_text

[[Passage1]]
[[Passage2]]

:: Passage1

Passage 1

_random_text

[[Start]]
[[Passage2]]

:: Passage2

Passage 2

_random_text

[[Start]]
[[Passage1]]

This associates a random item for each passage on the first visit, then sets it to a temporary variable. On each subsequent visit, it grabs the stored value instead and sets it to the temporary variable.

I’m not entirely sure how wordy you wanted your random items to get, which is why I made the function. My thought was that if they were going to be long-ish, they’d be easier to manage in a function like that. If they’re really short, you could probably change the line in the PassageHeader to:

variables().passage_items[passage()] = either("tree", "bridge", "well", "building");

Is that something more like what you were looking for?

1 Like

The verbosity of the options won’t be a problem since I need to use widgets for them anyway.
The code works perfectly, but then it occurred to me that for interactive items (for example, a building you can store items in) however I produce a unique variable, in the programming I would have to make something to refer to whatever variable is set for that spot, and so would need to put a unique identifier on that spot anyway.

However, I realised that I could refer to the current passage as part of the spot’s “address”, and so would only need to specify which spot in the passage, rather than which spot of the 500 or more total spots.
To do that though I would need the widget to refer which spot it is put inside in a passage, so that the widget could say, for example, <<set $stored_items = $stored_items + $this_spot + passage()>> (though this example wouldn’t work) so that the same widget could behave differently depending on which spot it is used inside.

It’s looking like I’ll need to use your code and work out the other problems other ways; associating variable values with an “address” either takes coding I don’t know or I’m just not thinking of it.

One problem though: the spots come in batches of six to twelve in a passage, but with the current code all that are in the same passage are assigned the same value. Is there a way to assign a random value to each spot separately in the same passage?

How about something like this? It has a <<item>> macro that will display an item for that room. When it’s called, it either adds a new item for that room, or it gets that item from the item list if it’s already been created. Every time you call the macro, it jumps to the next item. So by just calling it six times in a row, it’ll always get the same six items in order (or assign new ones if they haven’t been assigned). That way you can use it for varying amounts in each passage.

What you need for your game kind of confuses me, so I’m sorry if this is off the mark.

:: StoryInit
<<script>>
	variables().passage_items = {};

	setup.getPassageItem = function() {
		var items = variables().passage_items[passage()];
		if (temporary().item_count == items.length || !items[temporary().item_count]) items.push(either("tree", "bridge", "well", "building"));
		temporary().item_count += 1;
		return items[temporary().item_count - 1];
	}

	Macro.add('item', {
		handler: function () {
			$(this.output).wiki(setup.getPassageItem());
		}
	});
<</script>>

:: PassageHeader
<<script>>
	temporary().item_count = 0;
	if (visited() == 1) variables().passage_items[passage()] = [];
<</script>>

:: Start
Start passage

<<item>>
<<item>>
<<item>>

[[Passage1]]
[[Passage2]]

:: Passage1

Passage 1

<<item>>
<<item>>
<<item>>

[[Start]]
[[Passage2]]

:: Passage2

Passage 2

<<item>>
<<item>>
<<item>>

[[Start]]
[[Passage1]]

Edit: Replaced widget with macro because I could not for the life of me get the extra spaces to disappear in the widget.

No worries! Sorry I haven’t been more clear on my question.

Basically the first time you visit the passage, it would need to be, for example:

Spot1: 50% chance of trees, 30% chance of grass, and 10% chance of buildings.
Spot2: 50% chance of trees, 30% chance of grass, and 10% chance of buildings.
Spot3: 50% chance of trees, 30% chance of grass, and 10% chance of buildings.

So that you might end up with:

Trees
Trees
Buildings

And then it stays that way so that every time you visit the passage you get:

Trees
Trees
Buildings

But when you start a new game, the passage might be:

Grass
Trees
Grass

And stay that way for that game.

I think it may only be possible with manually making a variable for each spot, and typing numbers isn’t the worst thing I could have to do. Only one time I did a few hours of copy, paste, type, etc., then found out that the vast block of text I created was unnecessary and could be replaced with a couple lines, so I thought I’d ask earlier on in the process this time. :sweat_smile:

I think I have what you want now.

You can probably guess its usage from the example as this point. I also included an error in case you forget to call setChances() before calling <<item>>.

Edit: I added a <<multipleItems [amount]>> macro for doing a bunch in a row for less copy and paste. This is fine if you want to just print a list. <<item>> is if you want to work it into your passage description better.

:: StoryInit
<<script>>
	variables().passage_items = {};

	setup.setItemChances = function(chances) {
		temporary().item_count = 0;
		if (!(passage() in variables().passage_items)) variables().passage_items[passage()] = [];
		temporary().item_chances = chances;
	}

	setup.getNewPassageItem = function() {
		var k;
		var chances = temporary().item_chances;
		while (true) {
			for (k in chances) {
				if (random(100) > chances[k]) continue;
				return k;
			}
		}
	}

	setup.getPassageItem = function() {
		var items = variables().passage_items[passage()];
		if (temporary().item_count == items.length || !items[temporary().item_count]) items.push(setup.getNewPassageItem());
		temporary().item_count += 1;
		return items[temporary().item_count - 1];
	}

	Macro.add('item', {
		handler: function () {
			if (!temporary().item_chances) return this.error("Function `setItemChances()` must be called before this macro.");
			$(this.output).wiki(setup.getPassageItem());
		}
	});

	Macro.add('multipleItems', {
		handler: function() {
			if (!temporary().item_chances) return this.error("Function `setItemChances()` must be called before this macro.");
			var i;
			for (i = 0; i < this.args[0]; ++i) {
				$(this.output).wiki("<<item>><br>");
			}
		}
	});

<</script>>


:: Start
<<run setup.setItemChances({"bridge": 50, "grass": 30, "buildings": 10})>>
Start passage

<<item>>
<<item>>
<<item>>

[[Passage1]]
[[Passage2]]

:: Passage1
<<run setup.setItemChances({"random scenery": 50, "bullet shells": 30, "chalk outlines": 10})>>
Passage 1

<<multipleItems 6>>

[[Start]]
[[Passage2]]

:: Passage2
<<run setup.setItemChances({"trees": 50, "flowers": 30, "animals": 10})>>
Passage 2

<<item>>
<<item>>
<<item>>

[[Start]]
[[Passage1]]

Well that’s the solution right there alright, and more flexible than I had hoped, being able to set it differently for each passage (some places have more wells, etc.); that was the code I was looking for. You’re going in the credits man. :slightly_smiling_face:

1 Like

My only question regarding “hundreds of unique variables”: If they’re generating something on the level of trees and grass and buildings that are inconsequential enough to be random, wouldn’t it be easier to just randomly generate what is in each location each time the player visits it and not need to remember it? With hundreds of locations, I’m not going to remember or care specifically which had trees AND grass as opposed to trees and NO grass.

Unless trees grass and buildings is just an example and it’s more complicated.

In my game, I use one room with a coordinate for a a map and occasionally generate a random pickup in the room. It also generates an atmosphere message that doesn’t need to “stick” to keep it a little more interesting for the player.

What I’m saying is if the scenery isn’t important enough for hands-on crafting, it might not be important enough to make it so consistent to have hundreds of variables keeping track of it.

Forgive me if I’ve misinterpreted and asked an impertinent and unhelpful game design philosophy question!

No worries! Yes, they’re just examples: I’m putting widgets in, some of which can store items, others produce resources, others can be built on, so the things you discover in a passage makes a big difference to the game (and has to stay the same when you return to the passage).
No impertinence on your part! Game design philosophy is always appreciated.

1 Like

Quick question (probably quick), how would one stop running “setup.setItemChances” so as to run it again with different chances in the same passage? (For example, to have a spot in the middle be more likely to be a well or a tower.)

I tried changing “run” to “set” (probably unnecessary), and then using the unset macro, but I apparently don’t know what to unset.

If you mean just rerun it, then you just have to clear the value for that passage and run it again. Then it’ll return a different set of random items.

/% First run %/
<<run setup.setItemChances({"trees": 50, "buildings": 30, "grass": 10})>>

/% Clear current values %/
<<set $passage_items[passage()] = [] >>

/% This should return a new set of random items %/
<<run setup.setItemChances({"trees": 50,  "well" : 30, "buildings": 30, "grass": 10})>>

However if you mean you want to run it a second time in the same passage every time, that’s a different issue and will require more work.

1 Like

That’s perfect! Thanks again!