# Module:Challenge

Documentation for this module may be created at Module:Challenge/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 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, min_value, min_challenge)
if step > 0 then
return math.max(diff - 5, min_value, math.floor(diff + (min_challenge - 60) / step))
else
return math.max(diff - 4, min_value, math.floor(diff + 40 / 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, min_rows)
if step > 0 then
return math.min(diff + 9 - min_rows, math.ceil(diff + 40 / step))
else
return math.min(diff + 9 - min_rows, math.ceil(diff - 60 / 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

--[[
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, 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 start, stop
if step > 0 then
start = get_min(diff, step, min_value, min_challenge)
stop = get_max(diff, step, diff - start)
else
stop = get_min(diff, step, min_value, min_challenge)
start = get_max(diff, step, 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(60 + 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.
]]
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 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 embedcontext = frame:callParserFunction('#var', 'embedcontext') ~= ''
return narrow_challenge(quality_link, diff, min_value, step, embedcontext)
end