134 lines
4.2 KiB
Lua
134 lines
4.2 KiB
Lua
--[========[Additional mathematical functions]========]
|
|
print("Loading mathx module...")
|
|
|
|
---Pick a random choice from a list of values
|
|
---@param possible_values string|table Either a comma-separated list of values (which can contain integer ranges like 2-7)
|
|
---or an array of possible values (which can also contain integer ranges as subtables with {lo, hi} elements)
|
|
---@param random_func? fun(a:integer,b:integer):number
|
|
---@return number|string|nil
|
|
function mathx.random_choice(possible_values, random_func)
|
|
random_func = random_func or mathx.random
|
|
assert(type(possible_values) == "table" or type(possible_values) == "string",
|
|
string.format("mathx.random_choice expects a string or table as parameter, got %s instead",
|
|
type(possible_values)))
|
|
|
|
local items = {}
|
|
local num_choices = 0
|
|
|
|
if type(possible_values) == "string" then
|
|
-- split on commas
|
|
for _,word in ipairs(possible_values:quoted_split()) do
|
|
-- does the word contain two dots? If yes, that's a range
|
|
local dots_start, dots_end = word:find("%.%.")
|
|
if dots_start then
|
|
-- split on the dots if so and cast to numbers
|
|
local low = tonumber(word:sub(1, dots_start-1))
|
|
local high = tonumber(word:sub(dots_end+1))
|
|
-- perhaps someone passed a string as part of the range, intercept the issue
|
|
if not (low and high) then
|
|
wesnoth.interface.add_chat_message("Malformed range: " .. word)
|
|
table.insert(items, word)
|
|
num_choices = num_choices + 1
|
|
else
|
|
if low > high then
|
|
-- low is greater than high, swap them
|
|
low, high = high, low
|
|
end
|
|
|
|
-- if both ends represent the same number, then just use that number
|
|
if low == high then
|
|
table.insert(items, low)
|
|
num_choices = num_choices + 1
|
|
else
|
|
-- insert a table representing the range
|
|
table.insert(items, {low, high})
|
|
-- how many items does the range contain? Increase difference by 1 because we include both ends
|
|
num_choices = num_choices + (high - low) + 1
|
|
end
|
|
end
|
|
else
|
|
-- handle as a string
|
|
table.insert(items, word)
|
|
num_choices = num_choices + 1
|
|
end
|
|
end
|
|
else
|
|
num_choices = #possible_values
|
|
items = possible_values
|
|
-- We need to parse ranges separately anyway
|
|
for i, val in ipairs(possible_values) do
|
|
if type(val) == "table" then
|
|
assert(#val == 2 and type(val[1]) == "number" and type(val[2]) == "number", "Malformed range for mathx.random_choice")
|
|
if val[1] > val[2] then
|
|
val = {val[2], val[1]}
|
|
end
|
|
num_choices = num_choices + (val[2] - val[1])
|
|
end
|
|
end
|
|
end
|
|
|
|
local idx = random_func(1, num_choices)
|
|
|
|
for i, item in ipairs(items) do
|
|
if type(item) == "table" then -- that's a range
|
|
local elems = item[2] - item[1] + 1 -- amount of elements in the range, both ends included
|
|
if elems >= idx then
|
|
return item[1] + elems - idx
|
|
else
|
|
idx = idx - elems
|
|
end
|
|
else -- that's a single element
|
|
idx = idx - 1
|
|
if idx == 0 then
|
|
return item
|
|
end
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
---Randomize the order of an array
|
|
---@param t any[]
|
|
---@param random_func? fun(a:number,b:number):number
|
|
function mathx.shuffle(t, random_func)
|
|
random_func = random_func or mathx.random
|
|
-- since tables are passed by reference, this is an in-place shuffle
|
|
-- it uses the Fisher-Yates algorithm, also known as Knuth shuffle
|
|
assert(type(t) == "table", string.format("mathx.shuffle expects a table as parameter, got %s instead", type(t)))
|
|
local length = #t
|
|
for index = length, 2, -1 do
|
|
local random = random_func(1, index)
|
|
t[index], t[random] = t[random], t[index]
|
|
end
|
|
end
|
|
|
|
---Compute a linear interpolation
|
|
---@param lo number
|
|
---@param hi number
|
|
---@param alpha number
|
|
---@return number
|
|
function mathx.lerp(lo, hi, alpha)
|
|
return lo + alpha * (hi - lo)
|
|
end
|
|
|
|
---Choose an element from a list based on a ratio.
|
|
---@generic T
|
|
---@param list T[]
|
|
---@param alpha number
|
|
---@return T
|
|
function mathx.lerp_index(list, alpha)
|
|
if #list == 0 then return nil end
|
|
return list[mathx.round(mathx.lerp(1, #list, alpha))]
|
|
end
|
|
|
|
---Clamp a number into a specified range
|
|
---@param val number
|
|
---@param lo number
|
|
---@param hi number
|
|
---@return number
|
|
function mathx.clamp(val, lo, hi)
|
|
return math.min(hi, math.max(lo, val))
|
|
end
|
|
|
|
wesnoth.random = wesnoth.deprecate_api('wesnoth.random', 'mathx.random', 1, nil, mathx.random)
|