How do I make a good % system?

I am trying to make an ore generation system, I tried it with this weight system which works fairly well, but it’s not quite what I’m envisioning. I am wanting to change it to some kind of % system as I think that would work better for me. With the weight system, it is spawning gold and diamond more than i’d like. Anyone have some solutions? I appreciate it!

local items = {
	{Name = "Stone"},
	{Name = "Copper"},
	{Name = "Iron"},
	{Name = "Gold"},
	{Name = "Diamond"}
}
local lootTable = {
	{Item = items[1], Weight = 100}, -- each one of these is an entry
	{Item = items[2], Weight = 60},
	{Item = items[3], Weight = 15},
	{Item = items[4], Weight = 5}, -- each one of these is an entry
	{Item = items[5], Weight = 1}
}
-- used in weight random number generation
local function returnSumOfWeight(lootTable)
	local sum = 0
	for _, entry in ipairs(lootTable) do
		sum = sum + entry.Weight
	end
	return sum
end

-- returns a random item from given lootTable
local function getRandomItem(lootTable)
	-- returns a random number based on total weight of given lootTable
	local randomNumber = math.random(returnSumOfWeight(lootTable))

	for _, entry in ipairs(lootTable) do
		if  randomNumber <= entry.Weight then
			return entry.Item
		else
			randomNumber = randomNumber - entry.Weight
		end
	end
end
1 Like

The most common solution and the worst is by adding each item x the amount (weight) to a table and then getting a random from the table.

The one I would recommend is:

From the walker alias method. The algorithm typically use O( n log n ) or O ( n ) preprocessing time, after which random values can be drawn from the distribution in O (1) time! Credit to Ryan Pattison

local alias_table = {}

function alias_table:new(weights) 
  local total = 0
  for _,v in ipairs(weights) do
      assert(v >= 0, "all weights must be non-negative")
      total = total + v
  end

  assert(total > 0, "total weight must be positive")
  local normalize = #weights / total
  local norm = {}
  local small_stack = {}
  local big_stack = {}
  for i,w in ipairs(weights) do
      norm[i] = w * normalize
      if norm[i] < 1 then
        table.insert(small_stack, i)
      else
        table.insert(big_stack, i)
      end
  end
  
  local prob = {}
  local alias = {}
  while small_stack[1] and big_stack[1] do -- both non-empty
      small = table.remove(small_stack)
      large = table.remove(big_stack)
      prob[small] = norm[small]
      alias[small] = large
      norm[large] = norm[large] + norm[small] - 1
      if norm[large] < 1 then
          table.insert(small_stack, large)
      else 
          table.insert(big_stack, large)
      end
  end

  for _, v in ipairs(big_stack) do prob[v] = 1 end
  for _, v in ipairs(small_stack) do prob[v] = 1 end

  self.__index = self
  return setmetatable({alias=alias, prob=prob, n=#weights}, self)
end


function alias_table:__call()
  local index = math.random(self.n)
  return math.random() < self.prob[index] and index or self.alias[index]
end

return alias_table

--[[ -- usage:
alias_table = require"alias_table"
sample = alias_table:new{10, 20, 15, 2, 2.3, 130} -- assign weights for 1, 2, 3, 4, 5, 6 etc.
math.randomseed(os.time()); math.random(); math.random(); math.random();
print(sample())
print(sample())
print(sample())
print(sample())
print(sample())
--]]

If you are doing percentage then just remove the % sign. 55% becomes 55 on weight

Go here Non-uniform random variate generation - Wikipedia for other algorithms

If you want an answer that uses your naming, tell me

1 Like

As I understand it you want to make it so that you assign a percentage rather than a weight to the table. In order to do that, all you have to do is make the numbers in the table add up to a power of 10. It won’t directly be a value between 0 and 1, but you can think of it as such since you are really just shifting a decimal

for example

The total here is 1000
Iron = 500 (500/1000 = 0.5)
Gold = 300 (300/1000 = 0.3)
Diamond = 200 (300/1000 = 0.2)

You could also put in decimals directly if you would rather that, but then you need to change how it picks the random number since it’s currently only going to pick an integer.

1 Like