Module:Challenge
From Fallen London Wiki (Staging)
Documentation for this module may be created at Module:Challenge/doc
-- Lua Sandbox of User:Asarta
local p = {}
local IL = require('Module:IL')
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, is_complex, additional_qualities)
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
else
title_row = title_row .. diff
end
if is_complex then
title_row = title_row .. ' + '
for i, quality in ipairs(additional_qualities) do
local quality = IL.IL_with_args(quality[1])
title_row = title_row .. quality
if i < #additional_qualities then
title_row = title_row .. ' + '
end
end
end
return title_row
end
local function make_row(stat, prob)
return stat .. ' - ' .. get_description(prob) .. ' (' .. prob .. '%)'
end
--[[
The lowest example stat in our table should be the highest of:
1. The highest stat that gives the minimum possible probability
2. The set minimum value for the stat
3. diff - 5 (for positive step) or diff - 4 (for negative step), to match the
expected behaviour for the default step of 10
]]
local function get_min(diff, step, scaler, min_value, min_challenge)
if step > 0 then
return math.max(diff - 5, min_value, math.floor(diff + (min_challenge - scaler) / step))
else
return math.max(diff - 4, min_value, math.floor(diff + (100 - scaler) / step))
end
end
--[[
The highest example stat in our table should be the lowest of:
1. The lowest stat that gives a 100% probability
2. The lowest stat on our table + 9, ensuring we use up to 10 rows total.
]]
local function get_max(diff, step, scaler, min_rows)
if step > 0 then
return math.min(diff + 9 - min_rows, math.ceil(diff + (100 - scaler) / step))
else
return math.min(diff + 9 - min_rows, math.ceil(diff - scaler / step))
end
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)
local min_challenge = step == 10 and 10 or 0
if (prob == min_challenge and step < 0) or (prob == 100 and step > 0) 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 = '<span style="font-size:larger"> || </span>'
end
return table.concat(rows, sep) .. '<br/>'
end
local function get_additonal_qualities(qualities)
local additional_qualities = {}
if qualities then
for quality in mw.text.gsplit(qualities, ';') do
table.insert(additional_qualities, quality)
end
end
for i, quality in ipairs(additional_qualities) do
additional_qualities[i] = mw.text.split(quality, ',')
end
return additional_qualities
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 @scaler success probability
@scaler: the percetage given at the supplied @diff (50 by default)
@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, scaler, min_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
title_row = title_row .. ' (' .. scaler .. '% base)'
-- 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 start, stop
if step > 0 then
start = get_min(diff, step, scaler, min_value, min_challenge)
stop = get_max(diff, step, scaler, diff - start)
else
stop = get_min(diff, step, scaler, min_value, min_challenge)
start = get_max(diff, step, scaler, diff - stop)
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(scaler + step * (stat - diff), 0, 100)
local stat_text = stat .. range_text(prob, stat, step, min_value, min_challenge)
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 step = tonumber(args.step or parent_args.step) or 10
local scaler = tonumber(args.scaler or parent_args.scaler) or 50
local embedcontext = frame:callParserFunction('#var', 'embedcontext') ~= ''
return narrow_challenge(quality_link, diff, scaler, min_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
function p.complex_challenge(frame)
local args = frame.args
local parent_args = frame:getParent().args
local challenge_type = args.challengeType or parent_args.challengeType
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 min_value = tonumber(args.minValue or parent_args.minValue) or 0
local step = tonumber(args.step or parent_args.step) or 10
local additional_qualities = args.additionalQualities or parent_args.additionalQualities
additional_qualities = get_additonal_qualities(additional_qualities)
local embedcontext = frame:callParserFunction('#var', 'embedcontext') ~= ''
local challenge = nil
if challenge_type == 'Broad' then
challenge = broad_challenge(quality_link, diff, scaler, embedcontext)
elseif challenge_type == 'Narrow' then
challenge = narrow_challenge(quality_link, diff, scaler, min_value, step, embedcontext)
end
local sep = '\n'
if embedcontext then
sep = '<span style="font-size:larger"> || </span>'
end
local challenge_rows = mw.text.split(challenge, sep)
local complex_title_row = make_title_row(challenge_type, quality_link, diff, embedcontext, true, additional_qualities)
challenge_rows[1] = complex_title_row
for i, quality in ipairs(additional_qualities) do
mw.logObject(quality)
local mod_quality_link = IL.IL_with_args(quality[1])
mw.log(mod_quality_link)
local mod_amount = quality[2]
table.insert(challenge_rows, 'Each point of ' .. mod_quality_link .. ' adds ' .. mod_amount .. ' points of ' .. quality_link .. '\n')
end
challenge_text = join_rows(challenge_rows, embedcontext)
return challenge_text
end
return p