A quick question about randomizing in Inform 7

So I’m playing around with a small project that requires a lot of randomizing based on probability. I wanted to use Inform 7 for other reasons, but I can’t figure out an efficient way to pick a value at random with specific odds for each option.

For example, if I want to pick a color at random (and have color as a kind of value, with various colors listed), but want a 60% chance of red, a 30% chance of blue, and a 10% chance of green, is there a special way to say this kind of thing?

The best I can come up with is to pick a random number from 1-100 and then say things like

if X is less than 61, now the selected color is red:
if X is more than 60: [indent] if X is less than 91: [etc]

…but this is really inefficient in a situation where I’m looking to do this a lot, with longer lists of specific odds and odds than occasionally change. Is there a shorter/better way to do this? If there isn’t a built-in way to say things like “60-30-10 odds”, is there a common workaround for situations where you’d need to do this a lot?

Thank you for reading and thank in advance to anyone who can help.

All of the answers I have used in the past involve repeating through lists or tables. (They are, therefore, potentially computationally expensive, so if you want to do this sort of thing often - by which I mean ‘many times in a single turn’ - it’s likely to affect performance.)

One method is creating a local list, adding each list item to it N times, and then shuffling the list and picking one. Another is creating (or populating) a table with your items, with a weighting value in each column; calculate the total of all weighting entries by repeating through; pick a random number X from 1 to the total; and then repeat through the table again, incrementing a new value until it reaches X.

This is obviously cumbersome to use if you’re going to use it often, but it could be abbreviated considerably by using custom phrases.

1 Like

(This would probably be a useful extension, but if so it should be made by someone with considerably more optimisation chops than I’ve got.)

Unless you’re talking about more than a dozen options, your original solution is about as efficient as it’s going to get. Slight improvement:

	[N between 0 and 9 inclusive...]
	if N < 3:
		now X is red;
	else if N < 6:
		now X is blue;
		now X is orange;

You could also build a large list of options:

L is a list of colors that varies.
L is { red, red, red, blue, blue, green, orange, orange, orange }.

…and randomly select an entry from it. But I generally don’t trade off RAM for speed, unless I’ve got a specific case that I’ve demonstrated needs more speed.

Is your concern that it’s inefficient to run or inefficient to type?

Anyhow, if your concern is that it’s inefficient to type, then maga is right that you want a custom phrase. Here’s one; but as pointed out above if you want to do it lots of times a turn you’ll want something more computationally efficient. I have no optimization chops whatsoever.

[code]To decide which K is a random entry from/in (choicelist - list of values of kind K) weighted by (weightlist - list of numbers):
let total be a number;
now total is 0; [redundant, really]
repeat with counter running through weightlist:
now total is total plus counter; [so the total is the total of all numbers in the weightlist]
let the selector be a random number from 1 to the total;
repeat with N running from 1 to the number of entries in choicelist: [since we’ll be using N to look at both lists at once, we can’t just repeat through the list]
now the selector is the selector minus entry N of the weightlist;
if the selector is less than zero:
decide on entry N of the choicelist;
decide on entry 1 of the choicelist. [if we haven’t chosen anything yet, then the number of entries in the weightlist didn’t match the number of entries in the choicelist; I’m going to decide on the first entry as a default, but we might want to run a sanity check and print a problem message, at least in a not for release build]

Lab is a room. Color is a kind of value. The colors are red, blue, and green. Ingredient is a kind of value. The ingredients are tire rims, anthrax, crushed glass, and tomato.

Every turn:
say a random entry from {red, blue, green} weighted by {1, 3, 5};
say line break;
say a random entry from {anthrax, crushed glass, tire rims} weighted by {2, 2, 7}.

The nice thing about this is it works for values of any kind. It might be annoying to have to make sure that the weights line up with the choices exactly as you like, but I can’t think of a way to say it more elegantly. And you’d have to think about what to do if the lengths of the lists somehow didn’t line up. (As I’ve written it, if there are too many entries in the choicelist, the excess choices are effectively ignored; and if there are too many entries in the weightlist, the extra weight effectively gets added to the first choice. I guess if there are too many entries in the weightlist you could just add up the number of entries that are in the choicelist so the excess weights get ignored.)

Thanks to all of you for the replies.

Tragically, I’ve been unexpectedly in an area of dodgy-to-nonexistent wifi all day and am only seeing some of this now. I did wind up with a half-baked solution of my own, and now I’m rethinking what I came up with based on this thread.

Re: efficient to type vs. efficient computationally: while I’d certainly favor a computationally efficient solution if there’s a clear path to one, mainly I was looking for the former (and wanting to be entirely sure I hadn’t missed a built-in provision for this kind of problem.)

After reflecting on the problem for a bit, I based my attempt below on the premise that I might want to actually store and reuse the specific odds of various things happening, tweaking them here and there as various conditions in the game world changed. (It’s also based on the fact that I couldn’t figure out kind-variables whatsoever from the documentation, although I was very interested in coming up with something that world work with any type of value, like Matt W.'s code above does. When I’m more awake I’m going take another pass at puzzling that out since now I’ve got a working example to look at.)

Anyway, keeping in mind that this is very possibly entirely stupid, I basically set up tables that looked like this:

Table 1
Color	Odds	Index
Red	60		1	
Blue	30		2
Green	10		3

And then I wrote a chunk of code to pull odds from tables and pick a thing:

[code]Choice is a number that varies.
oddsend is a number that varies.

To pick from (T - a table name):
Sort T in reverse odds order;
now oddsend is 0;
now choice is a random number between 1 and 100;
repeat through T:
now oddsend is oddsend + odds entry;
if choice <= oddsend:
now choice is Index entry;
stop the action.
…and then I went with this ugly chunk of code for messing with the odds given in the table:

To boost odds of (C - a color) by (N - a number): 
	now x is 0;
	repeat through table 1:
		if Odds entry > 0:
			now x is x + 1;   
	now x is x - 1;  
	now x is N divided by x;
	repeat through table 1:
		if Odds entry > 0:
			if Color entry is not C:
				now Odds entry is Odds entry - x;
	choose row with a Color of C in table 1;
	now Odds entry is Odds entry + N

The last bit obviously would need to be repeated for each sort of value I’m randomizing – I thought of saying "To boost odds of (C - a sayable value) but was stumped beyond that. I also didn’t quite get around to the part that would put the boosted odds back into the table, but that’s straightforward, and I still need to look up how you get around Inform’s desire to round when dividing numbers – but in practice I can actually afford be be off by a point or two, I think.

…now I feel slightly foolish for having asked a question about efficiency and then turned up again with something that’s a bit bashed together. Regardless, I appreciate the input. If someone could point me in the direction of an explanation of kind-variables, I’ll probably continue to tinker with something like the above so that changing probabilities can be stored and reused. (My fallback plan is take Matt W’s code from above as-is and run for the border).

Well, the official explanation of kind variables is in 21.7 of the documentation, but that’s obviously not helpful. I think all you really need to know about my code is that if you say “To decide which K is…” and later on in the phrase header you say “value of kind K” or “list of values of kind K” then you’ll be OK, so long as your code doesn’t try to do anything that might not be kosher for a value of an arbitrary kind. (For instance, you couldn’t try to say K in the above code, because not all kinds of values can be said by Inform. There’s a provision to get around that by saying “sayable value of kind K” but you don’t need to know that.)

In my solution, you could store and reuse specific odds by using global list variables [EDIT: I originally said “constant lists” which was the opposite of what I meant]:

[code]The red-green-blue odds list is a list of numbers that varies. The red-green-blue odds list is {60, 30, 10}.

Every turn: say a random entry from {red, green, blue} weighted by the red-green-blue odds list.[/code]

However if this meant you had a lot of lists it could be very memory-hoggy. I wrote a game that had basically nothing in it but lists (one room, one player object, 51 lists) and it would compile to the z-machine.

Actually you could adapt my approach to working with tables; have tables full of numbers and then iterate through the table wherever the code I gave iterates through the weightlist. Tables are much less memory-intensive; lists use a lot of memory because Inform has to allocate memory for them to expand, but tables have a fixed length once you define them. (You could even keep the definition for lists if you wanted to.)

I think you could also make your table definition a bit less clunky by having one of the functions choose a row in the table rather than return an index number which you use to choose a row. Also, there isn’t any reason to sort the table in reverse odds order, is there?

No, that’s fine. Figuring out what you really wanted is part of the process. :slight_smile: