Module:PillSeasonSchedule

From MicrasWiki
Revision as of 20:58, 19 April 2025 by NewZimiaGov (talk | contribs)
Jump to navigationJump to search

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

-- Module:PillSeasonSchedule
local p = {}
local date = require('Module:BassaridianCalendar')

-- 1) TEAM DEFINITIONS (logos for schedule only)
local teams = {
    { code="Allegro Symphonara",      fullName="Allegro Symphonara",      logo="{{flagicon image|AllegrosymphonaraB.png|200px}}", championships=4 },
    { code="Ashguard Pyralis",        fullName="Ashguard Pyralis",        logo="{{flagicon image|AshguardpyralisB.png|200px}}", championships=1 },
    { code="Aurelia Auric",           fullName="Aurelia Auric",           logo="{{flagicon image|AureliaAuricB.png|200px}}", championships=2 },
    { code="Brightpath Aureum",       fullName="Brightpath Aureum",       logo="{{flagicon image|BrightpathaureumB.png|200px}}", championships=2 },
    { code="Celestial Sancta Lunaris",fullName="Celestial Sancta Lunaris", logo="{{flagicon image|CelestialsanctaB.png|200px}}", championships=1 },
    { code="Delphica Windborne",      fullName="Delphica Windborne",      logo="{{flagicon image|DelphicawindborneB.png|200px}}", championships=1 },
    { code="Emberstone Vaeringheim",  fullName="Emberstone Vaeringheim",  logo="{{flagicon image|EmberstonevaeringheimB.png|200px}}", championships=3 },
    { code="Flameborne Erythros",     fullName="Flameborne Erythros",     logo="{{flagicon image|FlameborneerythrosB.png|200px}}", championships=1 },
    { code="Forgeward Nexa",          fullName="Forgeward Nexa",          logo="{{flagicon image|ForgewardnexaB.png|200px}}", championships=0 },
    { code="Hearthkeeper Koinon",     fullName="Hearthkeeper Koinon",     logo="{{flagicon image|HearthkeeperkoinonB.png|200px}}", championships=0 },
    { code="Ironbough Sylvapolis",    fullName="Ironbough Sylvapolis",    logo="{{flagicon image|IronboughSylvapolisB.png|200px}}", championships=1 },
    { code="Redcliff Citadel",        fullName="Redcliff Citadel",        logo="{{flagicon image|RedcliffCitadel.png|200px}}", championships=1 },
    { code="Riftwarden Acheron",      fullName="Riftwarden Acheron",      logo="{{flagicon image|RiftwardenAcheronB.png|200px}}", championships=3 },
    { code="Saluria Skylight",        fullName="Saluria Skylight",        logo="{{flagicon image|SaluriaSkylightB.png|200px}}", championships=0 },
    { code="Sandveil Somniumpolis",   fullName="Sandveil Somniumpolis",   logo="{{flagicon image|SandVeilSomniumB.png|200px}}", championships=1 },
    { code="Serena Tidesong",         fullName="Serena Tidesong",         logo="{{flagicon image|Serena TidesongB.png|200px}}", championships=2 },
    { code="Steppe Luminaria",        fullName="Steppe Luminaria",        logo="{{flagicon image|SteppeLuminariaB.png|200px}}", championships=3 },
    { code="Strider Myrene",          fullName="Strider Myrene",          logo="{{flagicon image|StriderMyreneB.png|200px}}", championships=1 },
    { code="Vaeringheim Pillar",      fullName="Vaeringheim Pillar",      logo="{{flagicon image|VaeringheimPillarB.png|200px}}", championships=5 },
    { code="Imperial Delphica",       fullName="Imperial Delphica",       logo="{{flagicon image|ImperialDelphicaB.png|200px}}", championships=1 },
    { code="Ascendant Aetherium",     fullName="Ascendant Aetherium",     logo="{{flagicon image|AscendantAetheriumB.png|200px}}", championships=2 },
    { code="Sufriya Stormwake",       fullName="Sufriya Stormwake",       logo="{{flagicon image|SufriyaStormwake.png|200px}}", championships=1 },
    { code="Pillarion Club Suncliff", fullName="Pillarion Club Suncliff", logo="{{flagicon image|PCSuncliff.png|200px}}", championships=0 },
    { code="Jogi Regiment",           fullName="Jogi Regiment",           logo="{{flagicon image|JogiRegiment.png|200px}}", championships=0 },
    { code="Vine Fleet Mylecia",      fullName="Vine Fleet Mylecia",      logo="{{flagicon image|VineFleetMylecia.png|200px}}", championships=0 },
    { code="Port of Blore Heath",     fullName="Port of Blore Heath",     logo="{{flagicon image|PortBloreHeath.png|200px}}", championships=0 },
    { code="Free State Abeis",        fullName="Free State Abeis",        logo="{{flagicon image|FreeStateAbeis.png|200px}}", championships=0 },
    { code="Jezeraah City",           fullName="Jezeraah City",           logo="{{flagicon image|JezeraahCity.png|200px}}", championships=0 }
}

-- 2) SCHEDULE GENERATOR
local function generateSchedule()
    local n = #teams
    assert(n % 2 == 0)
    local pool = {}
    for i = 1, n do pool[i] = teams[i] end
    local fixed = table.remove(pool, 1)
    local rounds = {}
    for r = 1, n - 1 do
        local pairings = {}
        if r % 2 == 1 then
            table.insert(pairings, {fixed, pool[1]})
        else
            table.insert(pairings, {pool[1], fixed})
        end
        for i = 2, n/2 do
            local t1, t2 = pool[i], pool[#pool - (i - 2)]
            if i % 2 == 0 then
                table.insert(pairings, {t1, t2})
            else
                table.insert(pairings, {t2, t1})
            end
        end
        rounds[r] = pairings
        table.insert(pool, 1, table.remove(pool))
    end
    for r = 1, n - 1 do
        local rev = {}
        for _, m in ipairs(rounds[r]) do
            table.insert(rev, {m[2], m[1]})
        end
        rounds[n - 1 + r] = rev
    end
    return rounds
end

-- 3) MAP ROUNDS TO DAYS
local START, END = 62, 183
local function mapDays()
    local total = END - START + 1
    local count = (#teams - 1) * 2
    local interval = math.floor(total / count)
    local extra = total % count
    local m = {}
    local day = START
    for i = 1, count do
        m[day] = i
        day = day + interval + (extra > 0 and 1 or 0)
        if extra > 0 then extra = extra - 1 end
    end
    return m
end

-- 4) SIMULATE RESULTS FOR A GIVEN DAY
local function simulateDayResults(day)
    local sched = generateSchedule()
    local dm = mapDays()
    local ridx = dm[day]
    if not ridx then return {} end
    math.randomseed(day)
    local results = {}
    for _, match in ipairs(sched[ridx]) do
        local home, away = match[1], match[2]
        local drawProb = 0.1
        local r = math.random()
        local hg, ag
        if r < drawProb then
            hg = math.random(0, 12); ag = hg
        else
            local wh = home.championships + 1
            local wa = away.championships + 1
            local rem = (r - drawProb) / (1 - drawProb)
            local homeWin = rem < (wh / (wh + wa))
            local winG = math.random(1, 12)
            local loseG = math.random(0, math.min(winG - 1, 12))
            if homeWin then
                hg, ag = winG, loseG
            else
                hg, ag = loseG, winG
            end
        end
        table.insert(results, {home = home, away = away, hg = hg, ag = ag})
    end
    return results
end

-- 5) RENDER TODAY’S OR NEXT MATCHDAY
local function renderRound(day, year)
    local dm = mapDays()
    local target = day
    if not dm[target] then
        for d = day + 1, END do
            if dm[d] then
                target = d
                break
            end
        end
    end
    if not dm[target] then
        return "'''No more matches this season.'''"
    end

    local prefix = (target == day) and "" or "'''Next matchday:''' "
    local month, dom = date.getDateFromDay(target)
    local label = dom .. " " .. month .. " " .. year .. " PSSC"
    local content = {}
    for _, r in ipairs(simulateDayResults(target)) do
        local h, a, hg, ag = r.home, r.away, r.hg, r.ag
        local hcol = (hg > ag and "green") or (hg < ag and "red") or "yellow"
        local acol = (ag > hg and "green") or (ag < hg and "red") or "yellow"
        local hs = string.format("<span style='color:%s;'>'''%d'''</span>", hcol, hg)
        local as = string.format("<span style='color:%s;'>'''%d'''</span>", acol, ag)
        table.insert(content,
            string.format("%s %s %s vs %s %s %s",
                h.logo, hs, h.fullName,
                a.logo, as, a.fullName
            )
        )
    end

    return string.format("|-\n| %s%d || %s || %s",
        prefix, target, label, table.concat(content, "<br>")
    )
end

-- 6) INVOKE: TODAY’S SCHEDULE
function p.renderSchedule(frame)
    local curr = date.getCurrentDate()
    local day  = tonumber(curr:match('^(%d+),')) or 0
    local year = tonumber(curr:match(', (%d+) PSSC')) or 0
    return table.concat({
        '{| class="wikitable sortable"',
        '! Day !! Date !! Matches',
        renderRound(day, year),
        '|}'
    }, "\n")
end

-- 7) INVOKE: CURRENT STANDINGS (no logos)
function p.renderStandings(frame)
    local curr  = date.getCurrentDate()
    local today = tonumber(curr:match('^(%d+),')) or 0
    local last  = math.min(today, END)
    local recs  = {}
    for _, t in ipairs(teams) do
        recs[t.code] = {team = t, W=0, D=0, L=0, Pts=0, PF=0, PA=0}
    end
    for d = START, last do
        for _, r in ipairs(simulateDayResults(d)) do
            local hR, aR = recs[r.home.code], recs[r.away.code]
            hR.PF, hR.PA = hR.PF + r.hg, hR.PA + r.ag
            aR.PF, aR.PA = aR.PF + r.ag, aR.PA + r.hg
            if r.hg > r.ag then
                hR.W, hR.Pts = hR.W+1, hR.Pts+3; aR.L = aR.L+1
            elseif r.hg < r.ag then
                aR.W, aR.Pts = aR.W+1, aR.Pts+3; hR.L = hR.L+1
            else
                hR.D, hR.Pts = hR.D+1, hR.Pts+1
                aR.D, aR.Pts = aR.D+1, aR.Pts+1
            end
        end
    end
    local list = {}
    for _, v in pairs(recs) do
        v.PD = v.PF - v.PA
        table.insert(list, v)
    end
    table.sort(list, function(a,b)
        if a.Pts ~= b.Pts then return a.Pts > b.Pts end
        if a.PD  ~= b.PD  then return a.PD  > b.PD  end
        if a.W   ~= b.W   then return a.W   > b.W   end
        return a.team.fullName < b.team.fullName
    end)
    local out = {
        '{| class="wikitable sortable"',
        '! Pos !! Team !! W !! D !! L !! Pts !! PF !! PA !! PD'
    }
    for i, v in ipairs(list) do
        table.insert(out, string.format(
            "|-\n| %d || %s || %d || %d || %d || %d || %d || %d || %d",
            i, v.team.fullName, v.W, v.D, v.L, v.Pts, v.PF, v.PA, v.PD
        ))
    end
    table.insert(out, "|}")
    return table.concat(out, "\n")
end

return p