Weighted Chance System

This tutorial requires a good understanding of Lua!

Hello! Today I will teach you how to make a Weighted Chance System. To those that do not know, this is a system that will do something based on chance. For this tutorial, I will be using a prize system.

  1. You want to start by having a table of prizes and the chances of getting them ranging from 0-1.
    image

  2. Next, you want a Variable called Weight to hold a value of 0.
    image

  3. Now, you want to iterate through the PrizeTable and add the chances to the Weight variable. Times it by 10 because next, we are going to get a random number from our Weight.

  4. Now you have a Weight with all the chances added. Use math.random to get a random number from Weight. Since math.random will not return a value from 0.1-1.8, we had to times it by 10.
    image

  5. Now reset your Weight to 0 and repeat step 3, except now we want to check if it matches our random number.
    image

Basically, a higher Chance, the more likely math.random will generate it.
image

There you have it, an easy Weighted Chance System! If you have any questions feel free to ask. :slight_smile:

258 Likes

This is a great tutorial, although I already knew how to set it up, but here’s how I made mine:

-- Module script:
local Rarities = {
   {"Item1", 0.00001}
   {"Item2", 0.01}
   {"Item3", 0.1}
};

return Rarities;

-- script:
local Rarities = require(--[[Module script]])

local function GetItem()
   local RNG = Random.new(); -- You could use math.random, this is just what I chose.
   local Counter = 0;
   for i, v in pairs(Rarities) do
	   Counter += Rarities[i][2]
   end
   local Chosen = RNG:NextNumber(0, Counter);
   for i, v in pairs(Rarities) do
	   Counter -= Rarities[i][2]
	   if Chosen > Counter then
		   return Rarities[i][1]
   	   end
   end
end)
48 Likes

Haha! pretty much the same thing, it is a popular system after all.

8 Likes

It was pretty much the only way I could use anything below 1% when for a case opening system, it’s very good lol

3 Likes

Yo check this out :sunglasses:


local Rarities = { -- ObjectName = frequency (frequency must be an integer)
   Item1 = 1,
   Item2 = 2,
   Item3 = 100,
};

local RollTable = {}

for object_name, frequency in pairs(Rarities) do
  for i = 1, frequency do
    table.insert(RollTable, object_name)
  end
end

local function Roll() --> ObjectName [string]
  return RollTable[math.random(1, #RollTable)]
end

Although it may look odd, it’s technically completely good and would be faster to write for non-changing rarity tables.

64 Likes
local function roll(t, _a, _b)
    return
        _a
        and (_a > 0
        and -math.random(_a)
        or _a < 0
        and (select(2, next(t, _b)) >= -_a
        and next(t, _b)
        or roll(t, _a + select(2, next(t, _b)), next(t, _b)))
        or (next(t, _b)
        and select(2, next(t, _b)) + roll(t, 0, next(t, _b))
        or 0))
        or roll(t, roll(t, roll(t, 0)))
end
 
print(roll({Common = 100, Rare = 10, Legendary = 1}))
34 Likes
local function roll(t, _a, _b, _c)
	return not t and (tonumber(_c) and _c>_b and _a or _c) or (not _c and _a and math.random(_a) or roll(nil, _b, _a, roll(t, _a and _a + _c or 0, next(t, _b))))
end

print(roll{Common = 100, Rare = 10, Legendary = 1})
11 Likes

Nothing better than a module based off of the mechanic people hate most in video games: RNG.

4 Likes

What’s the most preferred method then?

2 Likes

There is no other preferred method, unfortunately.

1 Like

Don’t worry I have you covered

___=next;____=math.random;_____=tonumber;___________=print
-- INLINE CACHINEG??? DOESNT EXIST!!!! OPTIOMIZE!@
function ______(_______,________,_________,__________)return(not(_______)and(_____(__________) and __________>_________ and ________ or __________)or(not(__________)and(________)and(____(________))or(______(nil,_________,________,______(_______,(________)and(________+__________)or(0),___(_______, _________))))))end
___________(______{Common=100,Rare=10,Legendary=1}) -- hhhh
-- AAAAAAAAAAAAA
-- :)
40 Likes

Bro whot is that, I can’t understand a thing…

8 Likes

In full seriousness, unlike my previous reply, there is a better way to do this.

Here is my version of it:

local Rarities = {
	Common = 0, -- 60% chance
	Rare = 0.6, -- 30% chance
	Legendary = 0.91, -- 9% chance
	Mythic = 0.99, -- 1% chance
}

local function PickRarity()
	local Index = math.random()
	local HighestRarity = "Common"
	
	for RarityName, Value in pairs(Rarities) do
		if Index >= Value and Value >= Rarities[HighestRarity] then
			HighestRarity = RarityName
		end
	end
	
	return HighestRarity
end

for i = 1,100 do
	print(PickRarity())
end

When using math.random, it will return any number between 0 and 1. The dictionary of rarities stores values that require math.random to be greater than or equal to the value in the dictionary. For example, if you want a rare item (from my example), then you would need the random value to be at least 0.6, but less than 0.9 (so that it’s still rare, and not legendary or mythic).

This simplifies rarities and easily allows for adjustments at any time.

@loleris Not sure if you use the initial method you posted long ago, but this may be a better alternative to prevent memory buildup.

31 Likes

:joy: thanks I just made it a solution as a joke to anyone who understood

local function roll(t, _)
	return
		not _
		and roll(t,{0,next(t)})
		or _
		and table.move({_[#_-2]+_[#_],next(t,_[#_-1])},1,3,#_+1,_)
		and #_%3 == 0
		and roll(t,_)
		or _[1] == 0
		and not table.move({math.random(_[#_])},1,1,1,_)
		or table.move({},1,1,#_-#_%3+1,_)
		and _[#_] >= _[1]
		and _[#_-1]
		or table.move({_[1]-_[#_]},1,1,1,_)
		and not table.move({},1,3,#_-2,_)
end

print(roll({Common = 100, Rare = 10, Legendary = 1}))
26 Likes

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