I have a WeightedRandom
module you can use that has useful built-in functions. You would have to manually give items individual luckInfluence
s (default of 0). All it does is weight + (weight * (luckInfluence * luckFactor))
.
WeightedRandom.lua
--!strict
local WeightedRandom = {}
export type WeightedRandom<I> = typeof(setmetatable({} :: WeightedRandomClass<I>, {} :: WeightedRandomMetatable))
export type WeightedRandomClass<I> = {
Random: Random,
GetItems: (self: WeightedRandom<I>) -> {[I]: Item},
AddItem: (self: WeightedRandom<I>, item: I, weight: number, luckInfluence: number?) -> (),
RemoveItem: (self: WeightedRandom<I>, item: I) -> (),
GetWeight: (self: WeightedRandom<I>, item: I, luckFactor: number?) -> number?,
GetWeights: (self: WeightedRandom<I>, luckFactor: number?) -> {[I]: number},
GetTotalWeight: (self: WeightedRandom<I>, luckFactor: number?) -> number,
GetProbability: (self: WeightedRandom<I>, item: I, luckFactor: number?) -> number?,
GetProbabilities: (self: WeightedRandom<I>, luckFactor: number?) -> {[I]: number},
Next: (self: WeightedRandom<I>, luckFactor: number?) -> I
}
export type WeightedRandomMetatable = {
__index: (self: any, index: any) -> any,
__newindex: (self: any, index: any, value: any) -> (),
__iter: (self: any) -> (),
__tostring: (self: any) -> string
}
export type Item = {
Weight: number,
LuckInfluence: number
}
local function clone<T>(t: T, deep: boolean?): T
t = table.clone(t :: any) :: any
if deep == true then
for index: any, value: any in t :: any do
if typeof(value) == "table" then
(t :: any)[index] = clone(value, true)
end
end
end
return t
end
function WeightedRandom.new<I>(random: Random?): WeightedRandom<I>
local items: {[I]: Item} = {}
local weightedRandomClass: WeightedRandomClass<I> = {
Random = random or Random.new(),
GetItems = function(weightedRandom: WeightedRandom<I>): {[I]: Item}
return clone(items, true)
end,
AddItem = function(weightedRandom: WeightedRandom<I>, item: I, weight: number, luckInfluence: number?): ()
items[item] = {
Weight = weight,
LuckInfluence = if luckInfluence ~= nil then luckInfluence else 0
}
end,
RemoveItem = function(weightedRandom: WeightedRandom<I>, item: I): ()
items[item] = nil
end,
GetWeight = function(weightedRandom: WeightedRandom<I>, item: I, luckFactor: number?): number?
if items[item] ~= nil then
return items[item].Weight + (items[item].Weight * (items[item].LuckInfluence * (if luckFactor ~= nil then luckFactor else 0)))
else
return nil
end
end,
GetWeights = function(weightedRandom: WeightedRandom<I>, luckFactor: number?): {[I]: number}
local weights: {[I]: number} = {}
for item: I in items do
weights[item] = weightedRandom:GetWeight(item, luckFactor) :: number
end
return weights
end,
GetTotalWeight = function(weightedRandom: WeightedRandom<I>, luckFactor: number?): number
local totalWeight: number = 0
for _: I, weight: number in weightedRandom:GetWeights(luckFactor) do
totalWeight += weight
end
return totalWeight
end,
GetProbability = function(weightedRandom: WeightedRandom<I>, item: I, luckFactor: number?): number?
if items[item] ~= nil then
return (weightedRandom:GetWeight(item, luckFactor) :: number) / weightedRandom:GetTotalWeight(luckFactor)
else
return nil
end
end,
GetProbabilities = function(weightedRandom: WeightedRandom<I>, luckFactor: number?): {[I]: number}
local probabilities: {[I]: number} = {}
for item: I in weightedRandom:GetWeights(luckFactor) do
probabilities[item] = weightedRandom:GetProbability(item, luckFactor) :: number
end
return probabilities
end,
Next = function(weightedRandom: WeightedRandom<I>, luckFactor: number?): I
local weightedItems: {[I]: number} = weightedRandom:GetWeights(luckFactor)
local totalWeight: number = weightedRandom:GetTotalWeight(luckFactor)
local randomWeight: number = weightedRandom.Random:NextNumber() * totalWeight
local lastItem: I = nil
for item: I, weight: number in weightedItems do
randomWeight -= weight
lastItem = item
if randomWeight <= 0 then
return item
end
end
return lastItem
end,
}
local weightedRandomMetatable: WeightedRandomMetatable = table.freeze({
__index = function(self: any, index: any): any
return weightedRandomClass[index]
end,
__newindex = function(self: any, index: any, value: any): ()
if index == "Random" then
if typeof(value) ~= "Random" then
error(`invalid value for index '{index}' (Random expected, got {typeof(value)})`, 2)
end
else
error(`{index} cannot be assigned to`, 2)
end
weightedRandomClass[index] = value
end,
__iter = function(self: any): ()
error(`attempted to iterate over a WeightedRandom`, 2)
end,
__tostring = function(self: any): string
return `WeightedRandom`
end
})
return table.freeze(setmetatable({}, weightedRandomMetatable)) :: any
end
return WeightedRandom
Usage:
local WeightedRandom = require(Library.Modules.WeightedRandom)
-- if you want to use type declarations
type WeightedRandom<I> = WeightedRandom.WeightedRandom<I>
local world1Pets: WeightedRandom<string> = WeightedRandom.new()
-- AddItem(item: I, weight: number, luckInfluence: number?)
world1Pets:AddItem("Common", 50, 0)
world1Pets:AddItem("Uncommon", 30, 0)
world1Pets:AddItem("Rare", 10, 0)
world1Pets:AddItem("Epic", 5, 1)
world1Pets:AddItem("Legendary", 3, 1)
world1Pets:AddItem("Mythical", 1.5, 2)
world1Pets:AddItem("Secret", 0.5, 4)
-- Next(luckFactor: number?): I
print(`Chosen pet with 0 luckFactor: {world1Pets:Next()}`)
-- GetProbabilities(luckFactor: number?): {[I]: number}
print(world1Pets:GetProbabilities(10))