set multiple variables to be picked in x% chance each?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Larck_Drakengold

I have a clicker game I am working on, and I am in need of picking a random variable at a % chance. However, with some of the examples I’ve found on these forums:
Unequal Chance
1 in X chance

These posts show how to set up a random number picker with a hard coded set chance that is based on a single number being pulled, and if you want something to happen 20% of the time, you will never see it. (at least I haven’t.)

The Question:
How can I set a random % generator that lets me set certain outcomes to the same chance? ex: red = 40% blue = 40% green = 40% yellow = 30% orange = 20% and so on

The Code:

func pickrando():#this spawns random color orbs based on % chance
print("func rando called")
var i = rand_range(0,1)
if i < 0.9:
	$colorsprite.texture = load("res://source/art/red orb.png")
	emit_signal("red")
	o = "red"
elif i < 0.8:
	$colorsprite.texture = load("res://source/art/orange orb.png")
	emit_signal("orange")
	o = "orange"
elif i < 0.7:
	$colorsprite.texture = load("res://source/art/yellow orb.png")
	emit_signal("yellow")
	o = "yellow"
elif i < 0.6:
	$colorsprite.texture = load("res://source/art/green orb.png")
	emit_signal("green")
	o = "green"
elif i < 0.5:
	$colorsprite.texture = load("res://source/art/blue orb.png")
	emit_signal("blue")
	o = "blue"
elif i < 0.4:
	$colorsprite.texture = load("res://source/art/purple orb.png")
	emit_signal("purple")
	o = "purple"
elif i < 0.1:
	$colorsprite.texture = load("res://source/art/rainbow orb.png")
	emit_signal("rainbow")
	o = "rainbow"
elif i < 0.05:
	$colorsprite.texture = load("res://source/art/copper orb.png")
	emit_signal("copper")
	o = "copper"
elif i < 0.03:
	$colorsprite.texture = load("res://source/art/silver orb.png")
	emit_signal("silver")
	o = "silver"
elif i < 0.01:
	$colorsprite.texture = load("res://source/art/gold orb.png")
	emit_signal("gold")
	o = "gold"
emit_signal('set_orb',o)

Right now I have the red set to 90%, but that also forces the only other option available to be rainbow, which is the remaining 10%, so I only ever see the 2 results.
I’m not keen on numbers, so I often get confused and start to mentally blank out when trying to do arbitrary math.
Should I make variables for every color I have in this setup, and assign it some number to be used in its % chance? is that a thing? Or is there no simple way to set this up in gd script?
My goal is to be able to have multiple colors share the same % chance if I so choose, so I can scale this up with more options when I ultimately add more colors to this project. This makes me think of some kind of loot pool type thing like in online games drop chances, (much like WoW) and pull from the pool at a certain percent chance. Maybe I am overthinking this setup and am just blind to how numbers work.

:bust_in_silhouette: Reply From: the.Alien

First of all, I think that your original question is mispelled, because you CANNOT ask for three different events happening with a 40% chance each one, as the sum of them would be 120%, a thing simply impossible! A thing that you can do is to assign “weights” to your events, as an example 40, 30, 20, as in your question, but the final probability will be the original weights divided by the SUM of all weights. In your example with red, blue, green, yellow and orange, the sum of all weights is 170 (3 x 40 + 30 + 20), so the probability of, say, red is = 40 / 170 ~ 0,24 (24%).
If you use the range_rand() function the simplest way to do it is to ask for a random number up to the sum of your weights, and then choose the color if the returned number is in the required range. In your example:
var n = range_rand(170)
then, if n is between 0 and 40 you pick red, if it’s in the range 40-80 you pick blue, 80-120 is green, 120-150 is yellow, 150-170 is orange and so on…

I’ve never used weights in any of my projects before, and I have no clue how to go about setting that up, can you show me a code snippet of how to use weights on a variable? I’ve seen it mentioned in my linked posts in the OP, but I didn’t really understand it.

Larck_Drakengold | 2020-03-24 19:30

:bust_in_silhouette: Reply From: njamster

Right now I have the red set to 90%, but that also forces the only other option available to be rainbow, which is the remaining 10%, so I only ever see the 2 results.

This is not how it works! When one is calling the function pickrando(), it’s generating a random float between 0.0 and 1.0 and saves it to a variable called i. Thus the chance that this value will be 1.0 or smaller is 100%, while the chance of it being smaller than 0.0 is 0%. The chance of it being smaller than 0.4? Right, 40%! You’re seeing a pattern? Now we’re using that pattern for probabilistic triggers:

func pickrando():
    var i = rand_range(0.0, 1.0)
    if i < 0.0:
        print("This line will never be printed!")
    if i < 0.4:
        print("This line will be printed in 40% of the cases!")
    if i < 1.0:
        print("This line will always be printed!")

Note that I’m only using if-clauses here, no elif. So if i is equal to 0.3, we will print “This line will be printed in 40% of the cases!” and “This line will be always printed!”. When using elif-clauses the order of your conditions will matter:

func pickrando():
    var i = rand_range(0.0, 1.0)
    if i < 0.8:
        print("This line will be printed in 80% of the cases!")
    elif i < 0.4
        print("This line will never be printed, oops!")

I’ve tried using all just ifs before but for some reason, it prints out multiple strings, as it picked more than 1 time in one function call, which I find odd. so when it should have picked red, it also picked yellow and green, and the resulting return of the color is the last one picked. so I’m seeing values change more than once, instantaneously, which I find a little confusing. Should I ignore that, or am I able to change that? because using the elif only got me the red or rainbow every time. (after you showed me you put 0.1, 1.0 instead of just 0 and 1, I tried that, and I got the same results with the elif, but with just if, I am seeing a bunch of random results, but as before, it’s setting them multiple times each function call…

Larck_Drakengold | 2020-03-24 20:00

I’ve tried using all just ifs before but for some reason, it prints out multiple strings, as it picked more than 1 time in one function call, which I find odd. so when it should have picked red, it also picked yellow and green, and the resulting return of the color is the last one picked.

Nothing odd about that. The procedure follows along these steps:

  1. call the function pickrando()
  2. generate a random floating point number i between 0.0 and 1.0, let’s say 0.57
  3. check the 1. condition i < 0.9, it’s true, load the red texture
  4. check the 2. condition i < 0.8, it’s true, load the orange texture
  5. check the 3. condition i < 0.7, it’s true, load the yellow texture
  6. check the 4. condition i < 0.6, it’s true, load the green texture
  7. all other conditions will evaluate as false, so the texture will be green!

using the elif only got me the red or rainbow every time

Judging from the script you provided, red should be the only possible outcome! If i is smaller than 0.9, you will load the red texture and skip over all other conditions. If it isn’t smaller than 0.9, all other conditions will evaluate to false as well, including the rainbow-condition (if i would be smaller than 0.1, it would be also smaller than 0.9 and as such the rainbow-condition would never be reached). So as I stated in my answer: the order of elif-conditions matters! You need to invert the order of your conditions, checking the gold-condition first and the red-condition last:

func pickrando():
    var i = rand_range(0,1)
    if i < 0.01:
        $colorsprite.texture = load("res://source/art/gold orb.png")
        emit_signal("gold")
        o = "gold"
   elif i < 0.03:
        $colorsprite.texture = load("res://source/art/silver orb.png")
        emit_signal("silver")
        o = "silver"
    elif i < 0.05:
        $colorsprite.texture = load("res://source/art/copper orb.png")
        emit_signal("copper")
        o = "copper"
    elif i < 0.1:
        $colorsprite.texture = load("res://source/art/rainbow orb.png")
    emit_signal("rainbow")
        o = "rainbow"
    elif i < 0.4:
        $colorsprite.texture = load("res://source/art/purple orb.png")
        emit_signal("purple")
        o = "purple"
    elif i < 0.5:
        $colorsprite.texture = load("res://source/art/blue orb.png")
        emit_signal("blue")
        o = "blue"
    elif i < 0.6:
        $colorsprite.texture = load("res://source/art/green orb.png")
        emit_signal("green")
        o = "green"
    elif i < 0.7:
        $colorsprite.texture = load("res://source/art/yellow orb.png")
        emit_signal("yellow")
        o = "yellow"
    elif i < 0.8:
        $colorsprite.texture = load("res://source/art/orange orb.png")
        emit_signal("orange")
        o = "orange"
    elif i < 0.9:
        $colorsprite.texture = load("res://source/art/red orb.png")
        emit_signal("red")
        o = "red"
    emit_signal('set_orb',o)

It’s worth pointing out that you can roll a number bigger than 0.9. And in that case, none of your conditions will evaluate to true, so the value of o won’t change!

njamster | 2020-03-25 11:27

I see, I got the script all changed up now to match the order here. I see a big difference in how the orbs get picked. I’m liking the results. Though for some reason I still want to check out how to add weights to a variable like what the.Allen posted below.

Larck_Drakengold | 2020-03-29 06:35

I still want to check out how to add weights to a variable like what the.Allen posted below.

What the.Alien proposed is doing the same thing - but in an (imho) less intuitive way! Instead of directly generating a probability (i.e. a float between 0.0 = 0% and 1.0 = 100%), you’re generating a integer (in the example used by the.Alien: between 0 and 170). However, in order to find out the probability of a certain weight, you’d need to divide it by the sum of all weights. So in the example used by the.Alien a weight of 40 equals a probability of 40 / 170 ≈ 0.24 ≈ 24%. So these are two different ways to say the same thing: “The orb will become red in roughly 24% of the cases” VS “The orb will become red in 40 of 170 cases”. Pick whatever you like, but it’s essentially the same!

njamster | 2020-03-29 11:36

:bust_in_silhouette: Reply From: rakkarage
var _data = {
	"a": 9,
	"b": 9,
	"c": 1
}

func _ready() -> void:
	for _i in range(10):
		print(_getPriority(_data))

func _getPriority(d: Dictionary) -> Object:
	var o
	var total := 0
	for value in d.values():
		total += value
	var selected := Random.next(total)
	var current := 0
	for key in d.keys():
		o = key
		current += d[key]
		if current > selected:
			return o
	return o

https://www.codeproject.com/Articles/420046/Loot-Tables-Random-Maps-and-Monsters-Part-I