Módulo:Drop rate calculator

De RuneScape Wiki
Ir para: navegação, pesquisa
Documentação do módulo
Esta documentação é transcluída de Predefinição:Sem documentação/doc. [editar] [atualizar]
Este módulo não possui nenhuma documentação. Por favor, considere adicionar uma documentação em Módulo:Drop rate calculator/doc. [editar]
Módulo:Drop rate calculator requer Módulo:Chart data.
Módulo:Drop rate calculator requer Módulo:Enum.
Módulo:Drop rate calculator requer Módulo:Yesno.

-- <nowiki>
local yesno = require( 'Módulo:Yesno' )
local chart = require( 'Módulo:Chart data' )
local enum = require( 'Módulo:Enum' )
local min = math.min
local max = math.max
local log = math.log
local floor = math.floor
local ceil = math.ceil
local abs = math.abs

local p = {}

local function formatNum( num )
	local x = num
	if num > 99 then
		x = 100 - num
	end
    local mag = min( max( -floor( math.log10( x ) ) + 1, 1 ), 12 )
    return (string.format( '%.' .. mag .. 'f', num  ):gsub( '0+$', '' ):gsub(  '%.$', '.0' ))
end

local function interPol( x, x1, y1, x2, y2 )
    return (x - x1) * (y2 - y1) / (x2 - x1) + y1
end

local function getKillCountProb( kills, arr )
    local i = enum.find_index( arr, function(item) return item.x > kills end )
    return interPol( kills, arr[i-1].x, arr[i-1].y, arr[i].x, arr[i].y )
end

-- sum(r^n, n=0..t-1)
local function powerSeries1( r, t )
    return (r^t - 1) / (r - 1)
end

-- sum(n*r^n, n=0..t-1)
local function powerSeries2( r, t )
    return ((t - 1) * r^(t + 1) - t * r^t + r) / (r - 1)^2
end

local function getKillCountForGivenChance( chance, droprate, threshold )
    chance = chance / 100

    if threshold then
        local c = 0
        local m = 1
        local kc = 0

        while true do
            local newC = 1 - (1 - c) * (1 - m * droprate)^threshold

            if newC >= chance then
                kc = kc + log( (1 - chance) / (1 - c) ) / log( (1 - m * droprate) )
                return ceil( kc )
            else
                kc = kc + threshold
                c = newC
            end

            m = m + 1
        end
    else
        return ceil( log( 1 - chance ) / log( 1 - droprate ) )
    end
end

function p.main( frame )
    return p._main( frame:getParent().args )
end

function p._main( args )
    local kills = tonumber( args.kills ) or 1
    local droprate = 1 / (tonumber( args.droprate ) or 1)
    local hasThreshold = yesno( args.hasThreshold )
    local threshold = tonumber( args.threshold ) or 1000
    local probArr = { {x=0, y=0} }
    local probArrNoThreshold = { {x=0, y=0} }
    local arrLen = 1
    local stepSize = max( floor( kills / 250 ), 1 )
    local killCountProb = 0

    for i = stepSize, 2 * kills, stepSize do
        local probNoThreshold = probArrNoThreshold[arrLen].y
        probNoThreshold = 100 - (100 - probNoThreshold) * (1 - droprate)^stepSize
        probArrNoThreshold[arrLen + 1] = { x=i, y=probNoThreshold }

        if hasThreshold then
            local probThreshold = probArr[arrLen].y
            local interStep = 0
            local _i = i - stepSize

            repeat
                interStep = min( i - _i, threshold )
                _i = _i + interStep
                local multiplier = ceil( _i / threshold )
                if threshold > 1 and _i > threshold and (_i - 1) % threshold < interStep then -- the multiplier changed in between i and i + stepSize
                    local subStep = ((_i - 1) % threshold) + 1
                    probThreshold = 100 - (100 - probThreshold) * (1 - min( (multiplier - 1) * droprate, 1 ))^(interStep - subStep)
                    probThreshold = 100 - (100 - probThreshold) * (1 - min( multiplier * droprate, 1 ))^subStep
                else
                    probThreshold = 100 - (100 - probThreshold) * (1 - min( multiplier * droprate, 1 ))^interStep
                end
            until _i >= i

            probArr[arrLen + 1] = { x=i, y=probThreshold }
        end

        arrLen = arrLen + 1
    end

    if hasThreshold then
        killCountProb = getKillCountProb( kills, probArr )
    else
        killCountProb = getKillCountProb( kills, probArrNoThreshold )
    end

    local averageKC = 0
    if hasThreshold then
        -- Calculate the weigthed average over the probabilty distribution from 0 to inf (we can stop sooner as values become insignificant small)
        -- => Calculate the sum of the prodcuct of each kill count value with the chance to obtain it at that kill count
        --
        -- l = start of each threshold bin. If threshold = 1000 then l = 1, 1001, 2001, ...
        -- c = combined succes and fail chance from previous threshold bins. In bin one c = droprate, bin two c = 2 * droprate * (1 - droprate)^threshold,
        -- bin three c = 3 * droprate * (1 - droprate)^threshold * (1 - 2 * droprate)^threshold, ...
        -- r = (1 - multiplier * droprate)
        --
        -- Each threshold bin can then be written as
        -- l*c + (l+1)*c*r + (l+2)*c*r^2 + (l+3)*c*r^3 + ... + (l + threshold - 1)*c*r^(threshold - 1)
        -- => c*(l + l*r + r + l*r^2 + 2*r^2 + l*r^3 + 3*r^3 + ...)
        -- => c*(l*(1 + r + r^2 + r^3 + ...) + r + 2r^2 + 3r^3 + ...)
        -- (1 + r + r^2 + r^3 + ...) and (r + 2r^2 + 3r^3 + ...) are powerseries
        local c = droprate
        local m = 1
        local prev = 0
        local initialChange = 0
        repeat
            prev = averageKC
            local r = 1 - min( m * droprate, 1 )
            local l = (m - 1) * threshold + 1
            averageKC = averageKC + c * (l * powerSeries1( r, threshold ) + powerSeries2( r, threshold ))
            c = c * r^threshold * (1 + 1 / m)
            m = m + 1
            
            if initialChange == 0 then
            	initialChange = abs( prev - averageKC )
        	end
        until abs( prev - averageKC ) < min( initialChange / 1000, 0.01 )
    else
        averageKC = 1 / droprate
    end
    averageKC = floor( averageKC + 0.5 )

    local plot = chart.newChart{ type='scatter' }
        :setTitle( 'Chance de pelo menos um objeto largado vs contagem de mortes' )
        :setDimensions( '40vw', '40vh', '400px', '400px', true )
        :setXLabel( 'Número de abates' )
        :setYLabel( 'Chance de pelo menos um objeto largado' )
        :showLegend( false )

    plot:newDataSet{
        data = probArrNoThreshold,
        pointRadius = 0,
        label = 'Sem limite'
    }

    plot:newDataSet{
        data = { {x=0, y=killCountProb}, {x=kills, y=killCountProb}, {x=kills, y=0} },
        pointRadius = 0,
        borderDash = {10, 10},
        borderWidth = 1,
        lineTension = 0,
        label = '- - -'
    }

    if hasThreshold then
        plot:newDataSet{
            data = probArr,
            pointRadius = 0,
            label = 'Sem limite',
        }

        plot:showLegend( true )
    end

    local dropRateAtKillCount = min( droprate * (hasThreshold and ceil( kills / threshold ) or 1), 1 )

    return string.format( 'Taxa de obtenção de objeto largado em %d abates: 1/%s ou %s%% <br> Chance de obter objeto largado em %d abates: %s%%' ..
        string.rep( '\n* %s em %d abates', 3 ) .. '\nMédia de abates %d%s',
        kills,
        floor( (1 / dropRateAtKillCount) + 0.5 ),
        formatNum( dropRateAtKillCount * 100 ),
        kills,
        formatNum( killCountProb ),
        '50%',
        getKillCountForGivenChance( 50, droprate, hasThreshold and threshold ),
        '90%',
        getKillCountForGivenChance( 90, droprate, hasThreshold and threshold ),
        '99%',
        getKillCountForGivenChance( 99, droprate, hasThreshold and threshold ),
        averageKC,
        tostring( plot )
    )
end

return p
-- </nowiki>