Can't get weighted chance for table in RNG type game

  1. What do you want to achieve? Keep it simple and clear!
    I want to draw a random item from my item table.
  2. What is the issue? Include screenshots / videos if possible!
    I cant get it to work; it returns: 37686102510 (the weight which I printed) and this: ServerScriptService.modules.items & rarities:36: attempt to compare Random <= number
  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I’ve found many posts with similar problems but they didn’t seem to help, or maybe I just can’t understand them.

Here’s my script:

-- module def
local M = {}

-- item defs
M.items = {
	["Common"] = 2, ["Uncommon"] = 4, ["Good"] = 5, ["Natural"] = 10,
	["Rare"] = 16, ["Divinus"] = 32, ["Crystalized"] = 64, ["Rage"] = 128,
	["Topaz"] = 150, ["Ruby"] = 350, ["Forbidden"] = 404, ["Emerald"] = 500,
	["Gilded"] = 512, ["Ink"] = 700, ["Jackpot"] = 777, ["Sapphire"] = 800,
	["Aquamarine"] = 900, ["Wind"] = 912, ["Diaboli"] = 1004, ["Precious"] = 1024,
	["Glock"] = 1700, ["Magnetic"] = 2048, ["Glacier"] = 2304, ["Sidereum"] = 4096,
	["Bleeding"] = 4444, ["Zolar"] = 5000, ["Pruner"] = 6000, ["Stard"] = 6120,
	["Broke"] = 8245, ["Venus"] = 10000, ["Marvs"] = 12000, ["Jupiter"] = 15000,
	["FootLettuce"] = 20000, ["Cursed"] = 30000, ["Astronomical"] = 50000,
	["Graete"] = 75000, ["Swung"] = 100000, ["Treaded"] = 250000, ["Broken"] = 500000,
	["Chakim"] = 1000000, ["Gunks"] = 1500000, ["Mello"] = 5000000, ["Bepsi"] = 10000000,
	["NevaGunks"] = 50000000, ["ALBRUKE"] = 100000000, ["Storm"] = 600000000, ["Bellins"] = 1000000000,
	["???"] = 2000000000
}

-- this is what im stuck on, i tried to make it so that it would pick a random item with a weighted chance from the items table, but i can't seem to get it to work
function M.drawItem()
	-- set the weight and make chance stuff
	local weight = 0
	for _, chance in pairs(M.items) do
		weight += (chance * 10)
	end
	print(weight)
	local ranNumber = Random.new(1, weight)
	
	weight = 0
	
	for item, chance in pairs(M.items) do
		weight += (chance * 10)
		
		if weight >= ranNumber then
			print(item) -- im just using print for prototype purposes right now
			break
		end
	end
end

return M

Any help would be greatly appreciated.

3 Likes

I’m pretty sure in the second for loop you have to subtract the chance from the weight, not add it.

3 Likes

I’m not sure if this is what you want, but I did a game with something similar

local items = {
	{"Common", 50/100}, -- 50%
	{"Uncommon", 25/100}, -- 25%
	{"Natural", 12.5/100}, -- 12.5%
	{"Aquamarine", 6/100}, -- 6%
	{"Sidereum", 1/100} -- 1%
}

--Get a item based on it's probability 
function getItem() -- Return the item rarity string
	local p = math.random()

	local cumulativeProbability = 0

	for name, item in pairs(items) do
		cumulativeProbability += item[2]
		if p <= cumulativeProbability then
			return item[1]
		end
	end
end

I can’t explain the code in detail, because it’s not mine, and I forgot where I found it, but I believe it was here in Devforum. But basically you’ll get a random item, based on probability.

If you want to add more randomness consider changing:

function getItem() -- Return the item rarity string
	math.randomseed(tick()) -- Make math.random() more random 
	
	local p = math.random()

	local cumulativeProbability = 0

	for name, item in pairs(items) do
		cumulativeProbability += item[2]
		if p <= cumulativeProbability then
			return item[1]
		end
	end
end
2 Likes

Hey,

You are not using the Random object correctly.

‘’’
local max = 2147483647 – use a large integer
local seed = math.random(max)
local generator = Random.new(seed)
‘’’

1 Like

Here is a fixed optimized version of your code.


-- module def
local M = {}

-- item defs with Luau typing
type Item = {name: string, weight: number}

M.items: {Item} = {
	{name = "Common", weight = 2}, {name = "Uncommon", weight = 4}, {name = "Good", weight = 5}, {name = "Natural", weight = 10},
	{name = "Rare", weight = 16}, {name = "Divinus", weight = 32}, {name = "Crystalized", weight = 64}, {name = "Rage", weight = 128},
	{name = "Topaz", weight = 150}, {name = "Ruby", weight = 350}, {name = "Forbidden", weight = 404}, {name = "Emerald", weight = 500},
	{name = "Gilded", weight = 512}, {name = "Ink", weight = 700}, {name = "Jackpot", weight = 777}, {name = "Sapphire", weight = 800},
	{name = "Aquamarine", weight = 900}, {name = "Wind", weight = 912}, {name = "Diaboli", weight = 1004}, {name = "Precious", weight = 1024},
	{name = "Glock", weight = 1700}, {name = "Magnetic", weight = 2048}, {name = "Glacier", weight = 2304}, {name = "Sidereum", weight = 4096},
	{name = "Bleeding", weight = 4444}, {name = "Zolar", weight = 5000}, {name = "Pruner", weight = 6000}, {name = "Stard", weight = 6120},
	{name = "Broke", weight = 8245}, {name = "Venus", weight = 10000}, {name = "Marvs", weight = 12000}, {name = "Jupiter", weight = 15000},
	{name = "FootLettuce", weight = 20000}, {name = "Cursed", weight = 30000}, {name = "Astronomical", weight = 50000},
	{name = "Graete", weight = 75000}, {name = "Swung", weight = 100000}, {name = "Treaded", weight = 250000}, {name = "Broken", weight = 500000},
	{name = "Chakim", weight = 1000000}, {name = "Gunks", weight = 1500000}, {name = "Mello", weight = 5000000}, {name = "Bepsi", weight = 10000000},
	{name = "NevaGunks", weight = 50000000}, {name = "ALBRUKE", weight = 100000000}, {name = "Storm", weight = 600000000}, {name = "Bellins", weight = 1000000000},
	{name = "???", weight = 2000000000}
}

-- Function to draw a random item with weighted chance
function M.drawItem(): string
	-- Calculate the total weight
	local totalWeight = 0
	for _, item in ipairs(M.items) do
		totalWeight = totalWeight + item.weight
	end

	-- Generate a random number between 1 and totalWeight
	local randomNumber = math.random(1, totalWeight)

	-- Iterate through the items to find which item the random number corresponds to
	local accumulatedWeight = 0
	for _, item in ipairs(M.items) do
		accumulatedWeight = accumulatedWeight + item.weight
		if accumulatedWeight >= randomNumber then
			print(item.name) -- For prototype purposes, print the selected item
			return item.name -- Return the selected item
		end
	end
end

return M

Arrays are very fast compared to dictionaries. If you want to learn why, look up the differences and how they are stored in memory. Always use Arrays in use cases where you are updating or calculating.

1 Like

I got the error: ServerScriptService.modules.items & rarities:7: Expected identifier when parsing method name, got '{'

Perhaps you could use normal functions instead of type definitions? I’m not sure

Thanks but this wouldn’t really satisfy my needs because I need structure like this: {name = "Common", weight = 2} and I need it to display its rarity like most RNG games.

Great suggestion though! Thanks.

I struggled a bit to find the answer for you, but I believe what you’re looking for is inverted probability

Here’s the code, I tested and it works pretty well:

M = {}

-- Define the items with their weights
M.items = {
	{"Common", 2}, {"Uncommon", 4}, {"Good", 5}, {"Natural", 10},
	{"Rare", 16}, {"Divinus", 32}, {"Crystalized", 64}, {"Rage", 128},
	{"Topaz", 150}, {"Ruby", 350}, {"Forbidden", 404}, {"Emerald", 500},
	{"Gilded", 512}, {"Ink", 700}, {"Jackpot", 777}, {"Sapphire", 800},
	{"Aquamarine", 900}, {"Wind", 912}, {"Diaboli", 1004}, {"Precious", 1024},
	{"Glock", 1700}, {"Magnetic", 2048}, {"Glacier", 2304}, {"Sidereum", 4096},
	{"Bleeding", 4444}, {"Zolar", 5000}, {"Pruner", 6000}, {"Stard", 6120},
	{"Broke", 8245}, {"Venus", 10000}, {"Marvs", 12000}, {"Jupiter", 15000},
	{"FootLettuce", 20000}, {"Cursed", 30000}, {"Astronomical", 50000},
	{"Graete", 75000}, {"Swung", 100000}, {"Treaded", 250000}, {"Broken", 500000},
	{"Chakim", 1000000}, {"Gunks", 1500000}, {"Mello", 5000000}, {"Bepsi", 10000000},
	{"NevaGunks", 50000000}, {"ALBRUKE", 100000000}, {"Storm", 600000000}, {"Bellins", 1000000000},
	{"???", 2000000000}
}

function M.getRandomItem()	
	local items = M.items
	
	local total_inverse = 0
	
	local inverses = {}

	-- Compute the inverse of each number
	for i, item in pairs(items) do
		inverses[i] = 1 / item[2]
		total_inverse = total_inverse + inverses[i]
	end

	-- Normalize these inverses to form a probability distribution
	local probabilities = {}
	for i, inv in pairs(inverses) do
		probabilities[i] = {inv / total_inverse, items[i]}
	end

	-- Use the probability distribution to randomly select a number
	local rand = math.random()
	local cumulative_probability = 0
	for i, prob in pairs(probabilities) do
		cumulative_probability = cumulative_probability + prob[1]
		if rand <= cumulative_probability then
			return prob[2] -- Return item 
		end
	end
end

EDIT: Try to balance the weight to how you want, because the higher weigh items haven’t showed up during 1000 iterations, unless that’s what you want – try playing with difference weigh values and adjust accordingly

1 Like

It said: Module code did not return exactly one value

Any idea what this means?

Sorry, it works now, turns out I was just dumb. I just needed to add return M at the end :sweat_smile:.

1 Like

Here is a more optimized and fixed version

-- module def
local M = {}

-- item defs with Luau typing
type Item = {name: string, weight: number}

-- Private items table
local items: {Item} = table.freeze({
	{name = "Common", weight = 2}, {name = "Uncommon", weight = 4}, {name = "Good", weight = 5}, {name = "Natural", weight = 10},
	{name = "Rare", weight = 16}, {name = "Divinus", weight = 32}, {name = "Crystalized", weight = 64}, {name = "Rage", weight = 128},
	{name = "Topaz", weight = 150}, {name = "Ruby", weight = 350}, {name = "Forbidden", weight = 404}, {name = "Emerald", weight = 500},
	{name = "Gilded", weight = 512}, {name = "Ink", weight = 700}, {name = "Jackpot", weight = 777}, {name = "Sapphire", weight = 800},
	{name = "Aquamarine", weight = 900}, {name = "Wind", weight = 912}, {name = "Diaboli", weight = 1004}, {name = "Precious", weight = 1024},
	{name = "Glock", weight = 1700}, {name = "Magnetic", weight = 2048}, {name = "Glacier", weight = 2304}, {name = "Sidereum", weight = 4096},
	{name = "Bleeding", weight = 4444}, {name = "Zolar", weight = 5000}, {name = "Pruner", weight = 6000}, {name = "Stard", weight = 6120},
	{name = "Broke", weight = 8245}, {name = "Venus", weight = 10000}, {name = "Marvs", weight = 12000}, {name = "Jupiter", weight = 15000},
	{name = "FootLettuce", weight = 20000}, {name = "Cursed", weight = 30000}, {name = "Astronomical", weight = 50000},
	{name = "Graete", weight = 75000}, {name = "Swung", weight = 100000}, {name = "Treaded", weight = 250000}, {name = "Broken", weight = 500000},
	{name = "Chakim", weight = 1000000}, {name = "Gunks", weight = 1500000}, {name = "Mello", weight = 5000000}, {name = "Bepsi", weight = 10000000},
	{name = "NevaGunks", weight = 50000000}, {name = "ALBRUKE", weight = 100000000}, {name = "Storm", weight = 600000000}, {name = "Bellins", weight = 1000000000},
	{name = "???", weight = 2000000000}
})


-- Getter for items
function M.getItems(): {Item}
	return items
end

-- Cumulative weight table for memoization
local cumulativeWeights = {}
local totalWeight = 0

-- Function to initialize the cumulative weight table
local function initializeCumulativeWeights()
	for i, item in ipairs(items) do
		totalWeight = totalWeight + item.weight
		cumulativeWeights[i] = totalWeight
	end
end

-- Initialize the cumulative weight table
initializeCumulativeWeights()

-- Function to find the item corresponding to a random number within the weighted range
local function findItemByWeight(randomNumber: number): string
	for i, cumulativeWeight in ipairs(cumulativeWeights) do
		if cumulativeWeight >= randomNumber then
			return items[i].name
		end
	end
	error("No item found for the given random number")
end

-- Function to draw a random item with weighted chance
function M.drawItem(): string
	local randomNumber = math.random(1, totalWeight)
	local selectedItem = findItemByWeight(randomNumber)
	print(selectedItem) -- For prototype purposes, print the selected item
	return selectedItem
end

return M

I got another strange error: ServerScriptService.modules.items & rarities:57: invalid argument #2 to 'random' (interval is empty)

Total weights is less than 1 for some reason.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.