Weighted Chance System

LOLERIS stop my brain hurts what does any of that even mean, like can someone please explain this lol

4 Likes

O(1) solution with O(n) preproc time
from A Lua implementation of the Alias Method, for sampling from an arbitrary distribution. · GitHub

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

sample = alias_table:new{10, 20, 15, 2, 2.3, 130}
print(sample())
7 Likes

Can’t seem to get this working, maybe I’m doing something wrong?
Code returns nothing.

function WeightedRandom.RandomFromWeightedTable(OrderedTable)
	local Weight = 0
	
	for _, Chance in pairs(OrderedTable) do
		Weight += (Chance * 10)
	end
	
	local RanNumber = math.random(1, Weight)
	
	Weight = 0
	for Piece, Chance in pairs(OrderedTable) do
		Weight += (Chance * 10)
		
		if Weight >= RanNumber then
			return OrderedTable
		end
	end
end

Could you give me the code in a function format?

You arent returning anything.

(30)

3 Likes

Yeah, he ended up solving it after posting that on another post

1 Like

Got something working, here’s something that works for me if anyone needs it.

I’m using 0.01 - 0.001 chances and they don’t seem to work (Nevermind, I fixed it by increasing the multipler (10) to 10000

Hey! Great tutorial! Can the chances be grater than 1?

Yes, sorry for the late response!

1 Like

Just incase someone wants this:

function six(seven,eight,nine,ten)
	return (
		
		not seven
				
			and (
				tonumber(ten) and (ten > nine) and eight or ten
			)
			
			or (
				not ten and eight and math.random(eight)
				
				or (	
					six(
						nil, 
						nine, 
						eight, 
						six(
							seven, 
							eight 
							and (eight + ten)
							or 0, 
							next(seven, nine)
						) 
					)
				)
			)
		
	)
end

print(six{Common=100,Rare=10,Legendary=1})

i tried

6 Likes

I know this is so late, but how do you actually convert the numbers from “Rarities” table to actual percentage.

Decide on the rarities first. No matter what, the most common item should have a rarity of 0.

Think of it this way: math.random() produces any number between 0 and 1. This means higher rarities should be larger values, since it’s harder for the random value to be at or above it.

Work backwards. Mythic is 1% chance, so that means 99% is left. Legendary is 9% chance, so that means 90% if left. Rare is 30%, so 60% is left; that 60% is the chance of the common item.

Hope that helps!

5 Likes

May I ask what the difference between the math.random and Random.new(). Im not familiar with Random.new(), I have seen it plenty just personally never used it myself!

May I bug you for a question about how some games have their numbers like 0.0000045% rarity.

this is the improved code, and you didn’t even format it properly

local PrizeTable = {
	Prize_1 = 0.1,
	Prize_2 = 0.6,
	Prize_3 = 0.4,
	Prize_4 = 0.2,
	Prize_5 = 0.6,
	Prize_6 = 0.8,
	Prize_7 = 0.9,
	Prize_8 = 0.3,
	Prize_9 = 0.2,
	Prize_0 = 0.1,
}

local Weight = 0

for _, Chance in ipairs(PrizeTable) do
	Weight += (Chance * 10)
end

local ranNumber = math.random(1, Weight)

Weight = 0
for Prize, Chance in ipairs(PrizeTable) do
	Weight += (Chance * 10)

	if Weight >= ranNumber then
		print(`Congrats you won {Prize}`)
		break
	end
end
1 Like

In what way is this improved? All you did is replace pairs with ipairs and use the new string interpolation.

2 Likes

ipairs stops yielding if it reaches a nil value, and the new string interpolation cleans the code up

1 Like

Fair, but I wouldn’t really call it an improvement, more just an “alternate” if you for some reason were changing weight values to nil.

1 Like

bro really just obfuscated a code sample :skull:

3 Likes

i tried this but it chooses the rarity with the highest number instead of the lowest because i have it like 1/5 not in decimals
how can i fix this?