ID:157767
 
I want to get the following distribution and I haven't figured it out yet. All my efforts get me the wrong distributions.

a = 60%
b = 30%
c = 10%

Pick_Something()
var/a = 0
var/b = 0
var/c = 0

var/ap = 60
var/bp = 30
var/cp = 10

if (prob(ap))
a++;

// Now what?


var/total = a + b + c;
world << "a%=[round(a/total * 100)]%"
world << "b%=[round(b/total * 100)]%"
world << "c%=[round(c/total * 100)]%"


ts
What exactly do you want??

From what I understood, this should be what you want(its simple math tho, so I dont think this was the problem).

Here is the code anyway

Pick_Something()
if(prob(60))
Here are the 60%
else //this else would be the same as prob(40)
if(prob(75)) //You wanted 30% and 10%, thats 3 to 1, so 25x3 to 25x1
Here are the 30%
else
Here are the 10%
In response to Abrax
More generically, the probabilities for the subsequent if() statements should be (desired chance / remaining chance). So, if the first is 60%, then the second should be (30% / (100% - 60%)) = (30%/40%) = 3/4 = 0.75, which is translated to 75 because I really think prob() is a bit silly in this respect.
In response to Garthor
Aside from divide by 0 checks, this should be what I need.

    else if (prob((bp / (100 - ap)) * 100))
else
// 10% ends up here.


Thank you both.

ts
Another way you can do it if you want to think about it in terms of weights instead of probabilities is this:

var/ap = 20
var/bp = 10
var/cp = 30
var/dp = 10

var/x = rand(1, ap + bp + cp + dp)

if(x <= ap)
a++
else if(x - ap <= bp)
b++
else if(x - ap - bp <= cp)
c++
else if(x - ap - bp - cp <= dp)
d++


If the random number x is in the 1..ap range, a is selected. If x is in the ap+1..ap+bp range, b is selected. If x is in the ap+bp+1..ap+bp+cp range, c is selected. If x is in the ap+bp+cp+1..ap+bp+cp+dp range, d is selected.

The ap/bp/cp/dp variables aren't proper probabilities, they're just weights. Sometimes this can be easier to work with since they don't have to sum to 100. Since ap is twice as much as bp, a is twice as likely as b to be picked. If you wanted to add another outcome you'd just need another weight and another "else if".
You shouldn't be using prob() for this kind of thing. Instead you should use rand().

Every time you use prob() or rand() or such, it's like rolling a die. (You can also use roll().) If you want a single set of outcomes that adds up to 100%, you probably only want a single roll.

Thus the best option is:

var/r = rand()
if(r < 0.6) // the first 60%
++a
else if(r < 0.9) // the next 30%
++b
else // the last 10%
++c


This can be done with multiple prob() calls too, but it's pointless and more often than not it'll just be mathematically annoying to figure out. For instance, if the first prob(60) fails then in the else block you can use prob(75) to separate the remaining 30% from 10% since of the 40% left, but dividing to get bp_modified=bp*100/(100-ap) is really more trouble than it's worth for general use. The easy solution is what I did above: Call a random number generator once, store the result in a var, and check the value to see which range it falls into.

Lummox JR
In response to Lummox JR
That's neat and it's definitely easier and cleaner than subtracting all previous values from the current one. I was designing one of my condition trees last night which had 8 possibilities and by the 4th one I was like, "oh man... this is nuts.".

Thank you!
ts


In response to Tsfreaks
Tsfreaks wrote:
That's neat and it's definitely easier and cleaner than subtracting all previous values from the current one.

It's cleaner because it includes the exact probability values you had used instead of computing them from ap, bp, and cp.

I was designing one of my condition trees last night which had 8 possibilities and by the 4th one I was like, "oh man... this is nuts.".

It sounds like you're using this type of code in several places, so you might want to put some more thought into how things are set up. Consider these examples:

mob/proc/attack(mob/target, damage)
var/r = rand()

if(r < critical_hit_chance)
target.health -= damage * 2
else if(r < hit_chance + critical_hit_chance)
target.health -= damage
else
src << "You missed!"


mob/proc/attack(mob/target, damage)
var/e = random_event(critical_hit_chance, hit_chance)

if(e == 1)
target.health -= damage * 2
else if(e == 2)
target.health -= damage
else
src << "You missed!"

proc/random_event()
// figure out which event happens.


The first example contains the math related to probabilities in the attack code. Every time you want to add a new outcome to the attack event you have to think about the math. Every time you want to create a new event that's similar to attack you have to think about the math. In both of these cases, you might not remember the math perfectly and make a mistake, or even just a typo.

The second example hides the math in another function. You can add another outcome to attack without having to think about how the math works. Once you write the random_event() function, you never have to think about the math again unless you want to modify that function.

If you want to add outcomes to attack but the code is getting too messy, you might decide that it would be too much of a hassle and not bother. Clean code is easier to work with. The problem is if you want clean code in the future, you need to work on keeping the code clean every step of the way (even when it doesn't seem necessary).