Loot/reward system by percentage

solved

(- Click for resources) #1

Hello,

I’m in need of a drop/reward system by percentage, so i created (as i always do) a weighted loot system that kinda acts like having a chance in percentages.

But the problem with this system is it’s not using percentages in the way that i can set 5% and the chance will be 5 percent, it’s pulling a random number 1-100 then just divide that to get the chances.

Is there a way to do this with real percentages?

this is what i created (test file):

    random_number   = randint(1,100)
    
    if random_number <= 50:
        #white 50%
        name = 'Common'
        color = [1,1,1,1]
        loot = 100
        
    elif random_number > 50 and random_number <= 75:  
        #green  25%
        name = 'Uncommon'
        color = [0,1,0,1]
        loot = 500
        
    elif random_number > 75 and random_number <= 85: 
        #blue 10%
        name = 'Rare'
        color = [0,0,1,1]
        loot = 1000
        
    elif random_number > 85 and random_number <= 92:  
        #purple  7%
        name = 'Epic'
        color = [1,0,1,1]
        loot = 2500
        
    elif random_number > 92 and random_number <= 97:  
        #yellow 5%
        name = 'Legendary'
        color = [1,1,0,1]
        loot = 5000
        
    elif random_number > 97 and random_number <= 100:   
        #red   3% 
        name = 'Wealthy'
        color = [1,0,0,1]  
        loot = 10000

So i wonder how do you tackle this (if you do)?


(blenderaptor) #2

Can you re-explain your problem with other words ? I dont want to give a random answer :slight_smile:


(wkk.py) #3

Percentages aren’t “real” :stuck_out_tongue:

What you did there pretty much encompasses what a percentage would be, if you want it to look more like “real percentages” you could do:

random_number = random() # in [0, 1)

if random_number <= .5:
    ...

Also, weighted random selection is nothing new, I think the subject already came up on Discord in the past… Long story short, the best approach is to give arbitrary weighs and let the system calculate the actual probability (percentage), because you want your sum of all probabilities to be equal to 1 and whatnot… It is tedious to write by hand.

I would recommend the following article, looks well written:
https://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python/


(wkk.py) #4

Maybe easier: https://scaron.info/blog/python-weighted-choice.html


(- Click for resources) #5

I don’t have a problem, everything is working fine. But It feels not like acting percentages because you grab a random number (1-100) and make a formula around it. All i wanted to know is if it could be done with using real percentages (instead of random).

The random number if lucky will throw 5 wealthy chest a row while it only has 3% chance in theory. I know that random is for each try not for 100 tries a row. but still if i would get 100 chests the wealthy one should be given not that many times.

And i also wanted to know how you would tackle such a loot system if you used one.

I thought the same, but was hoping for dunno something more/else haha.

I already have some functions in mind that i can write to keep track of how many times something is handed out and adjust the numbers/chances by that, but i wanted to find out if there where other solutions first.

The articles are nice as well, maybe that would help out, need to try that else i gonna write my own method.

Thanks


(giobbo) #6

maybe you can do something like this

import random
n=random.randint(1,100)
d=random.randint(1,n)
x=int(d/n*100)

(Lyeb) #7

Two cents; applying drop chance just to the item itself doesn’t work.

What you want is to give to the player a reward that’s consistent with what they had to do to get it
so say, I have to disarm thirty traps, beat baddies who had three levels on me and unlock a chest
then random numbers play a prank on me, and I only get a pair of slippers. How exciting
but then I smash a milion crates and finally get a unique item. That’s dated gamelogic, IMO

Way I see it, way better if anything that can drop a reward has a base reward value
it could be a number, range or tag that determines what the player might get
basically, use values taken from the obstacle to pick from one or multiple reward pools

Opinions aside; half as likely to generate two numbers above fifty than only one.
so add more dice check to the equation. One for common, two for 50%, four for 25% and so on.


#8

High roller. If you have 10% chance at winning,
then 1-10 from the dice roll means you win.
if it’s 50% chance at winning, then 1-50 is the winning area. And see what the dice roll is.

However it’s different if your dividing amoung more than one player? is that what your asking, you have more than one player to divide with?

You could try say, if player 1 has 10% chance, and player 2 has 90% chance then.
random(1,10) for player 1
random(1,90) for player 2
then see who has highest number.
This would put player1 at 10% chance at winning.


(BluePrintRandom) #9

I think he means that as you get things, it adjusts the odds of the other things

like if you get 50 common things, the odds of getting common things drop to near zero

one way to do this is have a list, and when I item is chosen off the list pop it :smiley:

[so big list of many choices of loot ] .pop(currentChoice)


#10

Yeah I get this, and it would be more fun to program the game too.


(blenderaptor) #11

all this is very unclear … hum hum …:face_with_thermometer:

still not sure what you are looking for . But basicaly i guess you want to get ride of buckets. So, for exemple; you can simply use something like this

rand_num = randint(1,99)
loot = (rand_num / (100 - rand_num)) x 100


(blenderaptor) #12

Also, if you want to fix to " 5% " the probability to get - let’s say - 3 times in a row " Wealthy" . Assuming events are independant :

prob = 0.05 ** (1/3)

prob = 0.3684…


(- Click for resources) #13

Hmm maybe i can, will see/try a few things out.

I agree, but i have multiple loot systems, this is just an extra award you can earn at the end of a battle / buy with ingame earned coins (sinkhole).

Again i agree, farming your ass of for 1 item sucks, i did not plan for this.

I was thinking almost the same, but then just using 2 randoms, if both are in the range you get the reward else roll 1 dice once more to see what other price you get.(but then excluding the first roll and higher prices). So if i rolled for epic chest, it fails then i can only get common,uncommon or rare for the second roll.

No, just one player.

Yes and no, i really just wanted to know if it was possible by using real percentages.
If i roll 1-100 and get below 50, i will get a common, but if i would roll 100 times in a row below 50 it’s very sad haha. I just tought that maybe there was a better way then just grabbing a random number.

But also yes, i do want to increase/decrease quality gain, lets say after 10 common will give you an uncommon if you roll common again.

I wanted to do this for the actual rewards that you can get from the chest, not for the chest itself.

rare or higher chests are given way to much.
want to tune it down by using percentages.


But i get it, there is no basic way to do this, so i can create my own way.
I like the discussion, more suggestions/ideas are always welcome.

in the mean time maybe this helps explaining:


(Monster) #14

Your way (post one) is correct already.

Likelyhood with equal distribution means all possible values have the same chance to appear. What is “all possible values”? These are the values within the required value range. On a dice it is 1, 2, 3, 4, 5, 6. On a coin it is up and down (basically 0,1).

A good pseudo random number generator generates numbers of the full value range. E.g on an integer this is 0 to 65535 = 65536 values .

As you have a different value range you would need a different pseudo random generator that generates exactly your value range. This is not an efficient solution. Luckily mathematics allow you some thing here.

You can transform the random value into your value range (which is 1…100). You already did that by calling randint(1,100). It does the transformation for you (as it is a really often demanded requirement).

As your value range is much smaller than the internal value range, The likelihood of each of your values is pretty much the same. Differences in the likelihood are really small, and not noticable in your situation.


(- Click for resources) #15

Well for the time that your solution works it does great, BUT it wil eventually throw a lot of errors, also the random number will result in a value of over 200 in this case out of range.

Errors
Error: Python(schatkist_spawner), Python script error
Traceback (most recent call last):
  File "D:\_blender\models\schatkist.blend\script.py", line 50, in treasure_chest
UnboundLocalError: local variable 'color' referenced before assignment

why he throws this i have no clue

Error: Python(schatkist_spawner), Python script error
Traceback (most recent call last):
  File "D:\_blender\models\schatkist.blend\script.py", line 9, in treasure_chest
ZeroDivisionError: division by zero

It works better then randint(1,100) it a little more random but it throws to much rewards of higher end.

Oh wow, why didn’t i think of this before, instead of making it smaller i actually need to make it a bigger number to increase the chance of ‘more’ percentage feeling.

    random_number   = randint(1,10000)
    random_number /= 100

this does exactly what i want, throwing more white/green boxes.


(edderkop) #16

in python3.6 there is random.choices if weighted random is needed

or if you are using a older version of blender this could be the answer.

from random import random
from bisect import bisect

def Choices(items, weights=[], count=1):
    # accumulating the weights for bisect
    current = 0
    accumulated_weights = []
    for value in weights:
        current += value
        accumulated_weights.append(current)

    if len(accumulated_weights) != len(items):
        raise ValueError('The number of weights does not match the items')
    
    total = accumulated_weights[-1]
    return [items[bisect(accumulated_weights, random() * total)] for i in range(count)]

def genLootFromTable(count=2,table=None):
    out = []
    if table:
        out = Choices(list(table.keys()),list(table.values()),count)
    
    return out
  

(- Click for resources) #17

Wow! this is exactly what i needed, Thanks!

I knew about .choice, but i didn’t know there was choices as well, that works so nice.

Example for anyone wanting to use .choices.

    chests = ['Common', 'Uncommon', 'Rare', 'Epic', 'Legendary', 'Wealthy']
    weights = [100, 50, 20, 10, 5, 3]
    random_chest   = choices(chests, weights)[0]

for loop x 1000 gives:

{'Common': 547, 'Legendary': 22, 'Uncommon': 273, 'Rare': 92, 'Wealthy': 15, 'Epic': 51}

(BluePrintRandom) #18

choices is nice for iteration randomly over a list without repeating
I use it in freeing tiles now


(- Click for resources) #19

It indeed works nicely.


Hmm, i thought to be smart and put everything into a dict, but i wonder if it is possible to grab the weight out of a dict within a dict within the choices function?

i know i can do:

choices( list( chests_dict.keys() ) ) 

but how can i grab the weight value from that key and use it inside the .choices function?

what i have now: (i want to use this weight out of the dict,instead of a separated chest/weight list)

from random import choices


def treasure_chest(cont):
    
    own     = cont.owner    
    spawner = own.scene.objects['schatkist_spawner']

    chests  = ['Common', 'Uncommon', 'Rare', 'Epic', 'Legendary', 'Wealthy']
    weights = [100, 50, 20, 10, 5, 3]
    
    random_chest    = choices(chests, weights)[0]    
    chest_details   = get_chest_details(random_chest)

    treasure_chest          = own.scene.addObject('schatkist', spawner, 0)   
    treasure_chest.color    = chest_details['color']
    
    print(random_chest, chest_details['weight'], chest_details['color'], chest_details['loot']) 
 
  
def get_chest_details(chest):
       
    chest_details =   {
    
                    'Common'    :   { 
                                    'weight': 100,
                                    'color' : [1,1,1,1], 
                                    'loot'  : 100
                                    },
                    'Uncommon'  :   { 
                                    'weight': 50,
                                    'color' : [0,1,0,1], 
                                    'loot'  : 250
                                    },
                    'Rare'      : { 
                                    'weight': 20,
                                    'color' : [0,0,1,1], 
                                    'loot'  : 500
                                    },
                    'Epic'      : { 
                                    'weight': 10,
                                    'color' : [1,0,1,1], 
                                    'loot'  : 1000
                                    },
                    'Legendary' : { 
                                    'weight': 5,
                                    'color' : [1,1,0,1], 
                                    'loot'  : 2500
                                    },
                    'Wealthy'   : { 
                                    'weight': 3,
                                    'color' : [1,0,0,1], 
                                    'loot'  : 5000
                                    }
                    }
       
    if chest in chest_details:
        return chest_details[chest]

i have used dicts a lot but never this complex. Is this even possible at all?


(BluePrintRandom) #20

I would use

DropTypes = {  "Rare":{"Percent":int, "Type":String , "Color":Vector }, "LessRare" etc } 

if 'loot' in own:
   if len(own['loot'])<1:
       del own['loot']


if 'loot' not in own:
    loot = []
    for Drop_Type in DropTypes:
        for i in range(Drop_Type['percent']):
            loot.append( Drop_Type['Type'])
    own['loot']=loot

any time a item is chosen from the list pop it

then the list will refresh itself when it’s empty
(so it’s random in the time you get it, however over time you will always get the % of rare goods etc)