How to setup the correct logic for picking a random item from a list based on item's rarity i.e "rare" "normal"

StackOverflow https://stackoverflow.com/questions/23437573

  •  14-07-2023
  •  | 
  •  

Question

I'm writing a game using Corona SDK in lua language. I'm having a hard time coming up with a logic for a system like this;

I have different items. I want some items to have 1/1000 chance of being chosen (a unique item), I want some to have 1/10, some 2/10 etc.

I was thinking of populating a table and picking a random item. For example I'd add 100 of "X" item to the table and than 1 "Y" item. So by choosing randomly from [0,101] I kind of achieve what I want but I was wondering if there were any other ways of doing it.

Was it helpful?

Solution

items = {
    Cat     = { probability = 100/1000 }, -- i.e. 1/10
    Dog     = { probability = 200/1000 }, -- i.e. 2/10
    Ant     = { probability = 699/1000 },
    Unicorn = { probability =   1/1000 },
}

function getRandomItem()
    local p = math.random()
    local cumulativeProbability = 0
    for name, item in pairs(items) do
        cumulativeProbability = cumulativeProbability + item.probability
        if p <= cumulativeProbability then
            return name, item
        end
    end
end

You want the probabilities to add up to 1. So if you increase the probability of an item (or add an item), you'll want to subtract from other items. That's why I wrote 1/10 as 100/1000: it's easier to see how things are distributed and to update them when you have a common denominator.


You can confirm you're getting the distribution you expect like this:

local count = { }

local iterations = 1000000
for i=1,iterations do
    local name = getRandomItem()
    count[name] = (count[name] or 0) + 1
end

for name, count in pairs(count) do
    print(name, count/iterations)
end

OTHER TIPS

I believe this answer is a lot easier to work with - albeit slightly slower in execution.

local chancesTbl = {
    -- You can fill these with any non-negative integer you want
    -- No need to make sure they sum up to anything specific
    ["a"] = 2,
    ["b"] = 1,
    ["c"] = 3
}

local function GetWeightedRandomKey()
   local sum = 0
   for _, chance in pairs(chancesTbl) do
      sum = sum + chance
   end

   local rand = math.random(sum)
   local winningKey
   for key, chance in pairs(chancesTbl) do
      winningKey = key
      rand = rand - chance
      if rand <= 0 then break end
   end

   return winningKey
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top