-- --{{{ print_r(t) --- @diagnostic disable-next-line unused-function local function print_r(t) local print_r_cache = {} --- @diagnostic disable-next-line unused-function local function sub_print_r(t, indent) if (print_r_cache[tostring(t)]) then print(indent .. "*" .. tostring(t)) else print_r_cache[tostring(t)] = true if (type(t) == "table") then local tLen = #t for i = 1, tLen do local val = t[i] if (type(val) == "table") then print(indent .. "#[" .. i .. "] => " .. tostring(t) .. " {") sub_print_r(val, indent .. string.rep(" ", string.len(i) + 8)) print(indent .. string.rep(" ", string.len(i) + 6) .. "}") elseif (type(val) == "string") then print(indent .. "#[" .. i .. '] => "' .. val .. '"') else print(indent .. "#[" .. i .. "] => " .. tostring(val)) end end for pos, val in pairs(t) do if type(pos) ~= "number" or math.floor(pos) ~= pos or (pos < 1 or pos > tLen) then if (type(val) == "table") then print(indent .. "[" .. pos .. "] => " .. tostring(t) .. " {") sub_print_r(val, indent .. string.rep(" ", string.len(pos) + 8)) print(indent .. string.rep(" ", string.len(pos) + 6) .. "}") elseif (type(val) == "string") then print(indent .. "[" .. pos .. '] => "' .. val .. '"') else print(indent .. "[" .. pos .. "] => " .. tostring(val)) end end end else print(indent .. tostring(t)) end end end if (type(t) == "table") then print(tostring(t) .. " {") sub_print_r(t, " ") print("}") else sub_print_r(t, " ") end print() end --}}} --{{{ table_count(t) --- @diagnostic disable-next-line unused-function local function table_count(t) local count = 0 for _ in pairs(t) do count = count + 1 end return count end --}}} --{{{ range(low, high, n) ---Create a table with integers in a given range ---@param low integer Lowest number ---@param high integer Highest number ---@param n integer Increment - the array will contain every nth number between [low..high] ---@return table local function range(low, high, n) local t = {} for i = low, high, n do table.insert(t, i) end return t end ---}}} -- {{{ sortMapByValueDesc(m) ---Sort the table m by value. ---@param m table ---@return table local function sortMapByValueDesc(m) local tmp = {} -- create a temporary table that is easier to sort for damage, occurrences in pairs(m) do local entry = { damage = damage, count = occurrences, } table.insert(tmp, entry) end table.sort(tmp, function(a, b) return a.count > b.count end) return tmp end --}}} -- {{{ arraySum(t) local function table_sum(t) local accumulator = 0 for _, v in pairs(t) do accumulator = accumulator + v end return accumulator end ---}}} --{{{ mostCommonDamageValues(instances, ratio) --- Calculate the most likely values ---@param instances table A map of [damage score] => [occurrences] ---@param ratio number Include most common damage until the sum of the probabilities of the damage scores are at least this number. ---@return table local function mostCommonDamageValues(instances, ratio) if ratio == nil then ratio = 0.25 end local sortedInstances = sortMapByValueDesc(instances) local totalOccurrences = table_sum(instances) local result = {} local occurrenceAccumulator = 0 for _, instance in pairs(sortedInstances) do occurrenceAccumulator = occurrenceAccumulator + instance.count table.insert(result, instance.damage) if occurrenceAccumulator / totalOccurrences > ratio then return result end end return result end --}}} -- {{{ calculateDamageD20(skill, base_damage) ---@param skill integer ---@param base_damage integer ---@return number average The average damage ---@return table typical The damage numbers that, together, are likely to occur more than 50% of the time. local function calculateDamageD20(skill, base_damage) local sum = 0 local n = 0 local tn = math.min(skill, 15) local outcomes = {} for rolled = 1, 20, 1 do local damage = 0 if rolled > tn then -- glancing blow damage = base_damage end if rolled == tn then -- crit damage = 2 * (skill + base_damage) end if rolled < tn then -- hit damage = rolled + base_damage end local key = tostring(damage) if outcomes[key] == nil then outcomes[key] = 1 else outcomes[key] = outcomes[key] + 1 end sum = sum + damage n = n + 1 end return sum / n, mostCommonDamageValues(outcomes, 0.5) end -- }}} local skill_min = 5 local skill_max = 15 local skill_inc = 1 local skills_table = range(skill_min, skill_max, skill_inc) local damage_min = 0 local damage_max = 10 local damage_inc = 3 local damages_table = range(damage_min, damage_max, damage_inc) io.write("skill\\damage", ";") for i, dmg in pairs(damages_table) do io.write(string.format("%d (avg);%d (typical)", dmg, dmg)) if i < #damages_table then io.write(";") end end print("") for _, skill in pairs(skills_table) do io.write(skill, ";") local tmp = {} for key, damage in ipairs(damages_table) do local avg, dmg = calculateDamageD20(skill, damage) tmp[key] = string.format("%.2f;%s", avg, table.concat(dmg, ", ")) end print(table.concat(tmp, ";")) end