Module:LoRUtility

From League of Legends Wiki, LoL Wiki
Revision as of 07:41, 29 July 2020 by Onfealive (talk | contribs) (add Group By "type" and "none", in addition to the default "region")
Jump to navigation Jump to search

Documentation for this module may be created at Module:LoRUtility/doc

-- <pre>
-- Inherit from https://github.com/SwitchbladeBot/runeterra

local lib               = require('Module:Feature')
local MAX_KNOWN_VERSION = 2
local p                 = {}
local VarInt            = {}
local BitOperator       = {}
local Utility           = {}

local REGIONS = {
    {code = "DE", name = "Demacia"},
    {code = "FR", name = "Freljord"},
    {code = "IO", name = "Ionia"},
    {code = "NX", name = "Noxus"},
    {code = "PZ", name = "Piltover and Zaun"},
    {code = "SI", name = "Shadow Isles"},
    {code = "BW", name = "Bilgewater"}
}

function base32_decode(str)
    local base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
    
    local binary =
        str:gsub(
        ".",
        function(char)
            if char == "=" then
                return ""
            end
            local pos = string.find(base32Alphabet, char)
            pos = pos - 1
            return string.format("%05u", Utility.dec2bin(pos))
        end
    )

    local bytes = Utility.str_split(binary, 8)

    local result = {}
    for _, byte in pairs(bytes) do
        table.insert(result, tonumber(byte, 2))
    end

    table.remove(result, #result)
    return result
end

function p.deckListFromCode(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    
    local deckCode = args['code'] or nil
    
    local lorData = mw.loadData("Module:LoRData/data")
    local bytes = base32_decode(deckCode)
    local firstByte = bytes[1]
    local result = {}

    table.remove(bytes, 1)
    local version = BitOperator.AND(tonumber(firstByte), 0xF)

    if (version > MAX_KNOWN_VERSION) then
        return "The provided code requires a higher version of this library; please update."
    end

    for i = 3, 1, -1 do
        local numGroupOfs = tonumber(VarInt.Pop(bytes)) or 0
        for j = 1, numGroupOfs, 1 do
            local numOfsInThisGroup = tonumber(VarInt.Pop(bytes)) or 0
            local set = VarInt.Pop(bytes)
            local faction = VarInt.Pop(bytes)

            for k = 1, numOfsInThisGroup, 1 do
                local card = VarInt.Pop(bytes)
                
                if card ~= nil then
                    local setString = Utility.padLeft(tostring(set), 2, "0")
                    local factionData = REGIONS[faction + 1]
                    local cardString = Utility.padLeft(tostring(card), 3, "0")
    
                    local cardCode = setString .. factionData.code .. cardString
    
                    if (lorData[cardCode] == nil) then
                        return 'Card Code "' .. cardCode .. '" is not exist'
                    end
    
                    table.insert(
                        result,
                        {
                            code = cardCode,
                            count = tonumber(i),
                            region = factionData.name
                            
                        }
                    )
                end
            end
        end
    end

    while #bytes > 0 do
        local fourPlusCount = VarInt.Pop(bytes)
        local fourPlusSet = VarInt.Pop(bytes)
        local fourPlusFaction = VarInt.Pop(bytes)
        local fourPlusNumber = VarInt.Pop(bytes)

        if fourPlusFaction == nil then
            return 'Invalid Deck Code'
        end
        
        local fourPlusSetString = Utility.padLeft(tostring(fourPlusSet), 2, "0")
        local fourPlusFactionData = REGIONS[fourPlusFaction + 1]
        local fourPlusNumberString = Utility.padLeft(tostring(fourPlusNumber), 3, "0")

        local cardCode = fourPlusSetString .. fourPlusFactionData.code .. fourPlusNumberString

        if (lorData[cardCode] == nil) then
            return 'Card Code "' .. cardCode .. '" is not exist'
        end

        table.insert(
            result,
            {
                code = cardCode,
                count = tonumber(fourPlusCount),
                region = fourPlusFactionData.name
            }
        )
    end
    
    return result
end

function p.deckDecoder(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    
    local deckCode = args['code'] or nil
    
    if deckCode == nil then
        return 'Empty Code'
    end
    
    local groupBy = args['groupBy'] or 'region'
    local groupByValues = {'region', 'type', 'none'}
    if Utility.table_contains(groupByValues, groupBy) == false then
        return 'Only support Group By "region", "type" or none'
    end
    
    local lorData = mw.loadData("Module:LoRData/data")
    local result = {}
    
    local deckList = p.deckListFromCode{code=deckCode}
    if type(deckList) ~= 'table' then
        return deckList
    end
    
    for _, deckData in pairs(deckList) do
        local cardData = lorData[deckData.code]
        
        local cardType = cardData.type
        if cardType == "Unit" then
            if cardData.supertype ~= nil and cardData.supertype == "Champion" then
                cardType = "Champion"
            else
                cardType = "Follower"
            end
        end
        
        local groupByKey = ''
        if groupBy == 'region' then
            groupByKey = deckData.region
        elseif groupBy == 'type' then
            groupByKey = cardType .. 's'
        else
            groupByKey = 'List'
        end
        
        if result[groupByKey] == nil then
            result[groupByKey] = {}
        end
        
        table.insert(
            result[groupByKey],
            {
                code = deckData.code,
                count = deckData.count,
                name = cardData.name,
                type = cardType
            }
        )
    end

    local sortedKeys = {}
    for k in pairs(result) do
        table.insert(sortedKeys, k)
    end
    table.sort(sortedKeys)
    
    local template = ""
    local newline = "\n"
    template = template .. "<tabber>" .. newline
    template = template .. "Hide=" .. newline .. '|-|' .. newline
    for i, groupByKey in ipairs(sortedKeys) do
        if i ~= 1 then
            template = template .. "|-|" .. newline
        end
        template = template .. groupByKey .. "=" .. newline

        local regionData = result[groupByKey]
        table.sort(
            regionData,
            function(a, b)
                if (a.type == b.type) then
                    return a.name < b.name
                end
                return a.type < b.type
            end
        )

        local type = ""
        for _, cardData in ipairs(regionData) do
            if cardData.type ~= type then
                if (type ~= "") then
                    template = template .. "}}" .. newline
                end
                if groupBy == 'region' or groupBy == 'none' then
                    template = template .. ";" .. cardData.type .. "s" .. newline
                end
                template = template .. "{{column|3|" .. newline

                type = cardData.type
            end

            local cardCodeTemplate = "* {{LoR|" .. cardData.name
            if cardData.type ~= "Champion" then
                cardCodeTemplate = cardCodeTemplate .. "|code=" .. cardData.code
            end
            cardCodeTemplate = cardCodeTemplate .. "}}"
            if cardData.count > 1 then
                cardCodeTemplate = cardCodeTemplate .. " (x" .. cardData.count .. ")"
            end

            template = template .. cardCodeTemplate .. newline
        end

        template = template .. "}}" .. newline
    end
    template = template .. "</tabber>"

    return frame:preprocess(template)
end

-- VarInit
VarInt.AllButMSB = 0x7f
VarInt.JustMSB = 0x80

function VarInt.Pop(bytes)
    local result = 0
    local currentShift = 0
    local bytesPopped = 0

    for i = 1, #bytes do
        bytesPopped = bytesPopped + 1
        local current = BitOperator.AND(bytes[i], VarInt.AllButMSB)
        result = BitOperator.OR(result, BitOperator.RIGHT_SHIFT(current, currentShift))

        if BitOperator.AND(bytes[i], VarInt.JustMSB) ~= VarInt.JustMSB then
            Utility.table_splice(bytes, 1, bytesPopped)
            return result
        end

        currentShift = currentShift + 7
    end

    -- error('Invalid Deck Code: Byte array did not contain valid varints.')
    return nil
end

-- BitOperator
function BitOperator.OR(m, n)
    local tbl_m = Utility.to_bits(m)
    local tbl_n = Utility.to_bits(n)
    Utility.expand(tbl_m, tbl_n)

    local tbl = {}
    local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
    for i = 1, rslt do
        if (tbl_m[i] == 0 and tbl_n[i] == 0) then
            tbl[i] = 0
        else
            tbl[i] = 1
        end
    end

    return Utility.table2number(tbl)
end

function BitOperator.AND(m, n)
    local tbl_m = Utility.to_bits(m)
    local tbl_n = Utility.to_bits(n)
    Utility.expand(tbl_m, tbl_n)

    local tbl = {}
    local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
    for i = 1, rslt do
        if (tbl_m[i] == 0 or tbl_n[i] == 0) then
            tbl[i] = 0
        else
            tbl[i] = 1
        end
    end

    return Utility.table2number(tbl)
end

function BitOperator.RIGHT_SHIFT(n, bits)
    Utility.check_int(n)

    local high_bit = 0
    if (n < 0) then
        -- negative
        n = BitOperator.NOT(math.abs(n)) + 1
        high_bit = 2147483648 -- 0x80000000
    end

    for i = 1, bits do
        n = n / 2
        n = BitOperator.OR(math.floor(n), high_bit)
    end
    return math.floor(n)
end

function BitOperator.NOT(n)
    local tbl = Utility.to_bits(n)
    local size = math.max(table.getn(tbl), 32)
    for i = 1, size do
        if (tbl[i] == 1) then
            tbl[i] = 0
        else
            tbl[i] = 1
        end
    end
    return Utility.table2number(tbl)
end

function BitOperator.LEFT_SHIFT(n, bits)
    Utility.check_int(n)

    if (n < 0) then
        -- negative
        n = BitOperator.NOT(math.abs(n)) + 1
    end

    for i = 1, bits do
        n = n * 2
    end
    return BitOperator.AND(n, 4294967295) -- 0xFFFFFFFF
end

-- Utility
function Utility.str_split(str, size)
    local result = {}
    for i = 1, #str, size do
        table.insert(result, str:sub(i, i + size - 1))
    end
    return result
end

function Utility.dec2bin(num)
    local result = ""
    repeat
        local halved = num / 2
        local int, frac = math.modf(halved)
        num = int
        result = math.ceil(frac) .. result
    until num == 0
    return result
end

function Utility.padRight(str, length, char)
    while #str % length ~= 0 do
        str = str .. char
    end
    return str
end

function Utility.padLeft(str, length, char)
    while #str % length ~= 0 do
        str = char .. str
    end
    return str
end

function Utility.table2number(tbl)
    local n = table.getn(tbl)

    local rslt = 0
    local power = 1
    for i = 1, n do
        rslt = rslt + tbl[i] * power
        power = power * 2
    end

    return rslt
end

function Utility.to_bits(n)
    Utility.check_int(n)
    if (n < 0) then
        -- negative
        return Utility.to_bits(bit.bnot(math.abs(n)) + 1)
    end
    -- to bits table
    local tbl = {}
    local cnt = 1
    while (n > 0) do
        local last = math.mod(n, 2)
        if (last == 1) then
            tbl[cnt] = 1
        else
            tbl[cnt] = 0
        end
        n = (n - last) / 2
        cnt = cnt + 1
    end

    return tbl
end

function Utility.check_int(n)
    -- checking not float
    if (n - math.floor(n) > 0) then
        error("trying to use bitwise operation on non-integer!")
    end
end

function Utility.expand(tbl_m, tbl_n)
    local big = {}
    local small = {}
    if (table.getn(tbl_m) > table.getn(tbl_n)) then
        big = tbl_m
        small = tbl_n
    else
        big = tbl_n
        small = tbl_m
    end
    -- expand small
    for i = table.getn(small) + 1, table.getn(big) do
        small[i] = 0
    end
end

function Utility.table_splice(tab, idx, n, ...) --> removed
    local values = {...}
    local init_tab_size = #tab

    local removed = {}
    if n > 0 then
        for i = idx, (idx + n - 1) do
            table.insert(removed, tab[i])
            tab[i] = nil
        end
    end

    local tail = {}
    for i = (idx + n), init_tab_size do
        table.insert(tail, tab[i])
        tab[i] = nil
    end

    local i = idx
    for _, v in ipairs(values) do
        tab[i] = v
        i = i + 1
    end

    i = idx + #values
    for _, v in ipairs(tail) do
        tab[i] = v
        i = i + 1
    end

    return removed
end

function Utility.table_contains(table, val)
   for i=1,#table do
      if table[i] == val then 
         return true
      end
   end
   return false
end

return p
-- </pre>
-- [[Category:Lua]]