Module:Sandbox/Mzs42/GeneralTest
From Fallen London Wiki (Staging)
Documentation for this module may be created at Module:Sandbox/Mzs42/GeneralTest/doc
local p = {}
local function get_description(prob)
if prob <= 10 then
return 'almost impossible'
elseif prob <= 30 then
return 'high-risk'
elseif prob <= 40 then
return 'tough'
elseif prob <= 50 then
return 'very chancy'
elseif prob <= 60 then
return 'chancy'
elseif prob <= 70 then
return 'modest'
elseif prob <= 80 then
return 'very modest'
elseif prob <= 90 then
return 'low-risk'
else
return 'straightforward'
end
end
local function sign(num)
if num == 0 then
return num
end
return math.floor(num / math.abs(num))
end
local function clamp(num, lo, hi)
return math.min(hi, math.max(num, lo))
end
local function broad_target_to_stat(target, diff, scaler)
return math.ceil(target / scaler * diff)
end
local function broad_percentage(stat, diff, scaler)
return math.min(100, math.floor(scaler * stat / diff))
end
local function make_title_row(challenge_type, quality_link, diff, embedcontext)
local title_row
if challenge_type == 'Broad' then
title_row = "[[Broad difficulty|Broad]], '''" .. quality_link .. "''' "
elseif challenge_type == 'Narrow' then
title_row = "[[Narrow difficulty|Narrow]], '''" .. quality_link .. "''' "
end
if diff == nil then
if embedcontext then
return title_row .. 'Unknown Difficulty Level'
else
return title_row .. 'Unknown Difficulty Level\n[[Category:Diff]]'
end
end
return title_row .. diff
end
local function make_row(stat, prob)
return stat .. ' - ' .. get_description(prob) .. ' (' .. prob .. '%)'
end
--[[
The highest of:
1. The highest stat that gives the low stat boundary probability
(minimum_challenge if step > 0 or 100 if step < 0)
2. The set minimum value for the stat
No matter how we adjust the range of stats to display, this is the lowest stat
we could usefully show.
]]
local function get_stat_floor(diff, step, min_value, min_challenge)
local boundary_prob
if step > 0 then
boundary_prob = math.floor(diff + (min_challenge - 60) / step)
else
boundary_prob = math.floor(diff + 40 / step)
end
return math.max(min_value, boundary_prob)
end
--[[
The lowest example stat in our table should be the highest of:
1. The stat floor
2. The highest stat on our table - 9, ensuring we use up to 10 rows total.
]]
local function get_low(high_stat, stat_floor)
return math.max(high_stat - 9, stat_floor)
end
--[[
The highest example stat in our table should be the lowest of:
1. The lowest stat that gives the high stat boundary probability
(100 if stat > 0 or 0 if stat < 0)
2. The set maximum value for the stat
3. The lowest stat on our table + 9, ensuring we use up to 10 rows total.
]]
local function get_high(diff, step, max_value, low_stat)
local boundary_prob
if step > 0 then
boundary_prob = math.ceil(diff + 40 / step)
else
boundary_prob = math.ceil(diff - 60 / step)
end
return math.min(low_stat + 9, max_value, boundary_prob)
end
local function insert_row(rows, stat_text, prob, embedcontext)
if embedcontext then
table.insert(rows, "''" .. make_row(stat_text, prob) .. "''")
else
table.insert(rows, '*' .. make_row(stat_text, prob))
end
end
local function range_text(prob, stat, step, min_value, min_challenge, max_value)
if ((prob == min_challenge and step < 0) or (prob == 100 and step > 0)) and
stat < max_value then
return ' and above'
elseif (prob == min_challenge or prob == 100) and stat > min_value then
return ' and below'
end
return ''
end
local function join_rows(rows, embedcontext)
local sep = '\n'
if embedcontext then
sep = '<big> || </big>'
end
return table.concat(rows, sep) .. '<br/>'
end
--[[
Return the challenge information for a Narrow Difficulty challenge.
Input parameters:
@quality_link: the text to show for the quality name (typically an expanded {{IL}})
@diff: the quality level that gives a 60% success probability
@min_value: the minimum achievable quality level when attempting this challenge
@step: the flat amount by which the success probability changes for each
quality level. Negative step is allowed, but 0 is not.
@embedcontext: true if being called from an embedded context where the compact
and category-free table should be returned
The function returns a wiki code string describing the challenge. If diff is not
nil, this includes a list of example values.
]]
function narrow_challenge(quality_link, diff, min_value, max_value, step, embedcontext)
local title_row = make_title_row('Narrow', quality_link, diff, embedcontext)
if step < 0 then
title_row = 'Inverted ' .. title_row
end
if diff == nil then
return title_row
end
-- normal narrow diffs bottom out at 10% difficulty
-- but ones with different step go to 0%
local min_challenge = step == 10 and 10 or 0
local stat_floor = get_stat_floor(diff, step, min_value, min_challenge)
if max_value == nil then
max_value = diff + 10
end
local start, stop
if step > 0 then
-- default assumption: 5 values under diff, diff, 4 values over diff
start = get_low(diff + 4, stat_floor)
stop = get_high(diff, step, max_value, start)
-- revise start based on actual high value rather than assumption
start = get_low(stop, stat_floor)
else
-- default assumption: 4 values under diff, diff, 5 values over diff
stop = get_low(diff + 5, stat_floor)
start = get_high(diff, step, max_value, stop)
-- revise start based on actual high value rather than assumption
stop = get_low(start, stat_floor)
end
local step_sign = sign(step)
local rows = {}
table.insert(rows, title_row)
for stat = start, stop, step_sign do
if stat >= min_value then
local prob = clamp(60 + step * (stat - diff), 0, 100)
local stat_text = stat .. range_text(prob, stat, step, min_value, min_challenge, max_value)
insert_row(rows, stat_text, prob, embedcontext)
end
end
if start > stop and step > 1 then
insert_row(rows, min_value .. ' and above', 100, embedcontext)
elseif stop > start and step < 1 then
insert_row(rows, min_value .. ' and above', 0, embedcontext)
end
return join_rows(rows, embedcontext)
end
--[[
Return the challenge information for a Broad Difficulty challenge.
Input parameters:
@quality_link: the text to show for the quality name (typically an expanded {{IL}})
@diff: the quality level that gives a scalar% success probability
@scaler: the probability scaler. currently always 60
@embedcontext: true if being called from an embedded context where the compact
and category-free table should be returned
The function returns a wiki code string describing the challenge. If diff is not
nil, this includes a list of example values.
]]
function broad_challenge(quality_link, diff, scaler, embedcontext)
local title_row = make_title_row('Broad', quality_link, diff, embedcontext)
if diff == nil then
return title_row
end
local rows = {}
table.insert(rows, title_row)
local guaranteed = broad_target_to_stat(100, diff, scaler)
-- sixty_p only needs to be calculated if scaler is not 60, but it's easy
-- enough so why not
local sixty_p = broad_target_to_stat(60, diff, scaler)
if guaranteed < sixty_p + 5 then
-- More useful tables for low diff values
local start = math.max(1, guaranteed - 6)
for i=start,guaranteed do
insert_row(rows, i, broad_percentage(i, diff, scaler), embedcontext)
end
else
for i, target_prob in ipairs({41, 51, 61, 71, 81, 91, 100}) do
local stat = broad_target_to_stat(target_prob, diff, scaler)
-- For sufficiently high diff, actual_prob will equal target_prob
-- but for lower diffs, actual_prob is less confusing
local actual_prob = broad_percentage(stat, diff, scaler)
insert_row(rows, stat, actual_prob, embedcontext)
end
end
return join_rows(rows, embedcontext)
end
function p.narrow_challenge(frame)
local args = frame.args
local parent_args = frame:getParent().args
local quality_link = args.qualityLink or parent_args.qualityLink
local diff = tonumber(args.diff or parent_args.diff)
local min_value = tonumber(args.minValue or parent_args.minValue) or 0
local max_value = tonumber(args.maxValue or parent_args.maxValue)
local step = tonumber(args.step or parent_args.step) or 10
local embedcontext = frame:callParserFunction('#var', 'embedcontext') ~= ''
return narrow_challenge(quality_link, diff, min_value, max_value, step, embedcontext)
end
function p.broad_challenge(frame)
local args = frame.args
local parent_args = frame:getParent().args
local quality_link = args.qualityLink or parent_args.qualityLink
local diff = tonumber(args.diff or parent_args.diff)
local scaler = tonumber(args.scaler or parent_args.scaler) or 60
local embedcontext = frame:callParserFunction('#var', 'embedcontext') ~= ''
return broad_challenge(quality_link, diff, scaler, embedcontext)
end
return p