Module:Collection log calculator: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
m (1 revision imported) |
(No difference)
|
Latest revision as of 21:22, 4 November 2021
Documentation for this module may be created at Module:Collection log calculator/doc
-- <nowiki>
local arr = require 'Module:Array'
local chart = require 'Module:Chart data'
local yesno = require 'Module:Yesno'
local prob = require 'Module:Probability'
local iter = require 'Module:Iterator'
local spairs = iter.spairs
local opairs = iter.opairs
local formCalc = require 'Module:Form calculator'._main
local split = mw.text.split
local floor = math.floor
local ceil = math.ceil
local min = math.min
local max = math.max
local log10 = math.log10
local p = {}
local encodeTable = {
[' '] = '_',
["'"] = '~a~',
[':'] = '~b~',
['('] = '~c~',
[')'] = '~d~',
['?'] = '~e~'
}
local function encodeName( name )
return (name:gsub( "[ ':&%(%)%?]", encodeTable ))
end
local decodeTable = {
['_'] = ' ',
['~a~'] = "'",
['~b~'] = ':',
['~c~'] = '(',
['~d~'] = ')',
['~e~'] = '?'
}
local function decodeName( name )
return (name:gsub( "_", decodeTable ):gsub( "~%w~", decodeTable ))
end
local function findStartParamIndex( config )
local i = 1
while config['param' .. i] ~= nil do
i = i + 1
end
return i
end
local function sortedParamNames( t )
local sorted = {}
for k, v in pairs(t) do
local id = type(v) == 'string' and v or k
local group
if type(v) == 'table' then group = v.group or v.order end
table.insert( sorted, { key=k, id=id, group=group } )
end
local function sortFunc( lhs, rhs )
if lhs.group == rhs.group then
return lhs.id < rhs.id
elseif lhs.group and rhs.group then
return lhs.group < rhs.group
else
return lhs.group ~= nil
end
end
table.sort( sorted, sortFunc )
local i = 0
return function()
i = i + 1
if sorted[i] then
return sorted[i].id, t[sorted[i].key]
end
end
end
function p.createConfig( frame )
local settings = require( 'Module:Collection log calculator/' .. frame.args.type .. '/Data' )
local conf = settings['config template']
local pi = findStartParamIndex( conf )
local function addParam( id, args )
conf['param'..pi] = id
conf['label'..pi] = args.label
conf['type'..pi] = args.type
conf['default'..pi] = args.default
conf['range'..pi] = args.range
conf['toggles'..pi] = args.toggles
conf['help'..pi] = args.help
pi = pi + 1
end
for name, data in opairs( settings.collectionData ) do
local id = encodeName( name )
table.insert( conf.range2, name )
table.insert( conf.toggles2, name .. '=' .. id )
local group = {}
addParam( id, { label=name, type='group', range=group } )
for param, paramData in sortedParamNames( data.params or {} ) do
local paramId = encodeName( id..'#'..param )
table.insert( group, paramId )
paramData.label = paramData.label or param
if paramData.toggles ~= nil then
local sections = split( paramData.toggles, ';', true )
for i, section in ipairs( sections ) do
local list = split( section, '=', true )
local tog = split( list[#list], ',', true )
list[#list] = arr.map( tog, function(x) return id..'#'..encodeName(x) end )
list[#list] = table.concat( list[#list], ',' )
sections[i] = table.concat( list, '=' )
end
paramData.toggles = table.concat( sections, ';' )
end
if paramData.type == 'group' then
local range = split( paramData.range, ',', true )
range = arr.map( range, function(x) return id..'#'..encodeName(x) end )
paramData.range = table.concat( range, ',' )
end
addParam( paramId, paramData )
end
for dropName, v in sortedParamNames( data.drops ) do
local paramName = encodeName( id..'#'..dropName )
local image = type( v ) == 'table' and v.image or dropName
local label = string.format( '[[File:%s.png]] %s', image, dropName )
table.insert( group, paramName )
addParam( paramName, { label=label, type='check', default='false' } )
end
end
conf.toggles2 = table.concat( conf.toggles2, ';' )
conf.default2 = conf.range2[1]
conf.range2 = table.concat( conf.range2, ',' )
for k, v in pairs( conf ) do
if type( v ) == 'table' then
conf[k] = table.concat( v, ',' )
end
end
return formCalc( conf )
end
-- print( p.createConfig{type='Boss'} )
function p.calc( frame )
local args = frame.args
return p._calc( args )
end
function p._calc( args )
local settings = require( 'Module:Collection log calculator/' .. args.type .. '/Data' )
local handlers = require( 'Module:Collection log calculator/' .. args.type .. '/Handlers' )
handlers.default = p.defaultHandler
local filteredArgs = {
currentKc = tonumber( args.currentKc or 0 ),
colName = args.colName,
colData = settings.collectionData[args.colName],
type = args.type
}
for k, v in pairs( args ) do
if string.find( k, '^'..encodeName( args.colName )..'#' ) then
local argName = decodeName( k:match( '#(.+)$' ) )
if v == 'true' or v == 'false' then
v = yesno( v )
end
filteredArgs[argName] = v
end
end
return handlers[filteredArgs.colData.handler]( filteredArgs )
end
local function generateOutput( chanceArrays, note, type )
if #chanceArrays == 0 then
return 'Your collection log is already complete.'
end
local combinedChance = chanceArrays:reduce( function(x, acc) return x * acc end )
local diff = (combinedChance .. {combinedChance[#combinedChance]}) - ({0} .. combinedChance)
local average = diff:reduce( function(x, acc, i) return acc + x * i end )
average = floor( average + 0.5 )
local killcountFor50 = 1
local killcountFor90 = 1
local killcountFor99 = 1
local killcountFor9999 = 1
for i = 1, #combinedChance do
if combinedChance[i] < 0.5 then
killcountFor50 = i + 1
end
if combinedChance[i] < 0.9 then
killcountFor90 = i + 1
end
if combinedChance[i] < 0.99 then
killcountFor99 = i + 1
end
if combinedChance[i] < 0.9999 then
killcountFor9999 = i + 1
else
break
end
end
killcountFor9999 = max( killcountFor9999, 10 )
local stepsize = ceil( killcountFor9999 / 1000 )
local xAxis = arr.range( 1, killcountFor9999, stepsize )
local yAxis = combinedChance:slice( 1, killcountFor9999 ):take_every( stepsize ) * 100
table.insert( xAxis, 1, 0 )
table.insert( yAxis, 1, 0 )
local action = type == 'Clue' and 'clue' or 'kill'
local plot = chart.newChart{ type='scatter' }
:setTitle( 'Chance vs ' .. action .. ' count' )
:setXLabel( (type ~= 'Clue' and 'Additional ' or '') .. action .. ' count' )
:setYLabel( 'Chance [%]' )
:showLegend( false )
:setDimensions('80vw', '70vh')
plot:newDataSet{
data = chart.convertToXYFormat( yAxis, xAxis ),
pointRadius = 0,
}
return string.format( "%sAverage %s count: %d\n* 50%% at %d %ss\n* 90%% at %d %ss\n* 99%% at %d %ss\n%s",
(note ~= nil) and ('Note: ' .. note .. '<br>') or '',
action,
average,
killcountFor50,
action,
killcountFor90,
action,
killcountFor99,
action,
tostring( plot )
)
end
function p.defaultHandler( args, chanceArrays )
chanceArrays = chanceArrays or arr{}
local colData = args.colData
local droprateCounts = {}
for item, data in spairs( colData.drops ) do
if data.droprate and args[item] == false then
local droprate = data.droprate
if args.Hardmode == true then
droprate = data.HMDroprate or droprate
end
if data.threshold then
table.insert( chanceArrays, prob.withThresholdArr( droprate, data.threshold, args.currentKc, colData.genRange ) )
else
droprateCounts[droprate] = (droprateCounts[droprate] or 0) + 1
end
end
end
for droprate, count in pairs( droprateCounts ) do
table.insert( chanceArrays, prob.noThresholdArr( droprate, colData.genRange )^count )
end
return generateOutput( chanceArrays, colData.note, args.type )
end
return p
-- </nowiki>