Module:PillSeasonSchedule: Difference between revisions

From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs)
No edit summary
NewZimiaGov (talk | contribs)
No edit summary
Line 3: Line 3:
local date = require('Module:BassaridianCalendar')
local date = require('Module:BassaridianCalendar')


-- 1) TEAM DEFINITIONS
-- TEAM DEFINITIONS
local teams = {
local teams = {
     { code="Allegro Symphonara",      fullName="Allegro Symphonara",      logo="{{flagicon image|AllegrosymphonaraB.png|200px}}", championships=4 },
     { code="Allegro Symphonara",      fullName="Allegro Symphonara",      logo="{{flagicon image|AllegrosymphonaraB.png|200px}}", championships=4 },
Line 37: Line 37:
-- 2) SCHEDULE GENERATOR
-- 2) SCHEDULE GENERATOR
local function generateSchedule()
local function generateSchedule()
     local n = #teams
     local n=#teams
     assert(n % 2 == 0, "Need even number of teams")
     assert(n%2==0)
     local pool = {}
     local pool={} for i=1,n do pool[i]=teams[i] end
    for i=1,n do pool[i] = teams[i] end
     local fixed=table.remove(pool,1)
     local fixed = table.remove(pool,1)
     local rounds={}
     local rounds = {}
     for r=1,n-1 do
     for r=1,n-1 do
         local pairings = {}
         local pairings={}
         if r % 2 == 1 then
         if r%2==1 then table.insert(pairings,{fixed,pool[1]}) else table.insert(pairings,{pool[1],fixed}) end
            table.insert(pairings, {fixed, pool[1]})
        else
            table.insert(pairings, {pool[1], fixed})
        end
         for i=2,n/2 do
         for i=2,n/2 do
             local t1,t2 = pool[i], pool[#pool-(i-2)]
             local t1,t2=pool[i],pool[#pool-(i-2)]
             if i % 2 == 0 then
             if i%2==0 then table.insert(pairings,{t1,t2}) else table.insert(pairings,{t2,t1}) end
                table.insert(pairings,{t1,t2})
            else
                table.insert(pairings,{t2,t1})
            end
         end
         end
         rounds[r] = pairings
         rounds[r]=pairings
         table.insert(pool,1,table.remove(pool))
         table.insert(pool,1,table.remove(pool))
     end
     end
     for r=1,n-1 do
     for r=1,n-1 do
         local rev = {}
         local rev={}
         for _,m in ipairs(rounds[r]) do
         for _,m in ipairs(rounds[r]) do table.insert(rev,{m[2],m[1]}) end
            table.insert(rev,{m[2],m[1]})
         rounds[n-1+r]=rev
        end
         rounds[n-1+r] = rev
     end
     end
     return rounds
     return rounds
Line 74: Line 63:
local START,END=62,183
local START,END=62,183
local function mapDays()
local function mapDays()
     local totalDays = END-START+1
     local total=END-START+1
     local totalRounds = (#teams-1)*2
     local rounds=(#teams-1)*2
     local interval = math.floor(totalDays/totalRounds)
     local interval,extra=math.floor(total/rounds),total%rounds
    local extra = totalDays%totalRounds
     local m,day={},START
     local m,day={},START
     for i=1,totalRounds do
     for i=1,rounds do
         m[day] = i
         m[day]=i
         day = day + interval + (extra>0 and 1 or 0)
         day=day+interval+(extra>0 and 1 or 0)
         if extra>0 then extra = extra - 1 end
         if extra>0 then extra=extra-1 end
     end
     end
     return m
     return m
Line 89: Line 77:
-- 4) SIMULATE RESULTS
-- 4) SIMULATE RESULTS
local function simulateDayResults(day)
local function simulateDayResults(day)
     local schedule = generateSchedule()
     local sched=generateSchedule()
     local dm = mapDays()
     local dm=mapDays()
     local ridx = dm[day]
     local ridx=dm[day] if not ridx then return {} end
    if not ridx then return {} end
     math.randomseed(day)
     math.randomseed(day)
     local results = {}
     local res={}
     for _,match in ipairs(schedule[ridx]) do
     for _,m in ipairs(sched[ridx]) do local h,a=m[1],m[2]
        local home,away = match[1],match[2]
         local draw=0.1 local r=math.random()
         local drawProb = 0.1
        local r = math.random()
         local hg,ag
         local hg,ag
         if r<drawProb then
         if r<draw then hg=math.random(0,12); ag=hg
            hg = math.random(0,12); ag = hg
         else local wh,wa=h.championships+1,a.championships+1
         else
             local rem=(r-draw)/(1-draw)
            local wh = home.championships+1
             local winG,loseG=math.random(1,12),math.random(0,math.min(winG-1,12))
            local wa = away.championships+1
             if rem<wh/(wh+wa) then hg,ag=winG,loseG else hg,ag=loseG,winG end
             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
         end
         table.insert(results,{home=home,away=away,hg=hg,ag=ag})
         table.insert(res,{home=h,away=a,hg=hg,ag=ag})
     end
     end
     return results
     return res
end
end


-- 5) RENDER ROUND
-- 5) RENDER ROUND
local function renderRound(day,year)
local function renderRound(day,year)
     local dm = mapDays()
     local dm=mapDays()
     if not dm[day] then return "'''No matches scheduled today.'''" end
     if not dm[day] then return "'''No matches scheduled today'''" end
     local month,dom = date.getDateFromDay(day)
     local month,dom=date.getDateFromDay(day)
     local dateLabel = string.format("%d %s %d PSSC",dom,month,year)
     local label=dom.." "..month.." "..year.." PSSC"
     local lines = {}
 
     for _,res in ipairs(simulateDayResults(day)) do
     local rows=""
         local h,a,hg,ag=res.home,res.away,res.hg,res.ag
     for _,r in ipairs(simulateDayResults(day)) do
         local hcol = (hg>ag and "green") or (hg<ag and "red") or "yellow"
         local h,a,hg,ag=r.home,r.away,r.hg,r.ag
         local acol = (ag>hg and "green") or (ag<hg and "red") or "yellow"
         local hcol=(hg>ag and"green") or (hg<ag and"red") or "yellow"
         local homeSpan=string.format("<span style='color:%s;'>'''%d'''</span>",hcol,hg)
         local acol=(ag>hg and"green") or (ag<hg and"red") or "yellow"
         local awaySpan=string.format("<span style='color:%s;'>'''%d'''</span>",acol,ag)
         local homeSpan="<span style='color:"..hcol..";'>'''"..hg.."'''</span>"
         table.insert(lines,string.format("%s %s %s vs %s %s %s",
         local awaySpan="<span style='color:"..acol..";'>'''"..ag.."'''</span>"
            h.logo,homeSpan,h.fullName,
         rows=rows..h.logo.." "..homeSpan.." "..h.fullName.." vs "..a.logo.." "..awaySpan.." "..a.fullName.."<br>"
            a.logo,awaySpan,a.fullName
        ))
     end
     end
     return string.format("|-\n| %d || %s || %s\n", day, dateLabel, table.concat(lines,"<br>"))
    -- remove trailing <br>
    rows=rows:sub(1,-5)
     return "|-\n| "..day.." || "..label.." || "..rows
end
end


-- 6) TODAY’S MATCHDAY
-- 6) TODAY'S MATCHDAY
function p.renderSchedule(frame)
function p.renderSchedule(frame)
     local curr = date.getCurrentDate()
     local curr=date.getCurrentDate()
     local day = tonumber(string.match(curr,"^(%d+),")) or 0
     local day=tonumber(curr:match('^(%d+),')) or 0
     local year = tonumber(string.match(curr,", (%d+) PSSC")) or 0
     local year=tonumber(curr:match('.*, (%d+) PSSC')) or 0
     if not mapDays()[day] then return "'''No matches scheduled today.'''" end
     if not mapDays()[day] then return "'''No matches scheduled today'''" end
     return table.concat({
     return '{| class="wikitable sortable"\n! Day !! Date !! Matches\n'..renderRound(day,year)..'\n|}'
        '{| class="wikitable sortable"',
        '! Day !! Date !! Matches',
        renderRound(day,year),
        '|}'
    },"\n")
end
end


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


return p
return p

Revision as of 19:14, 19 April 2025

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

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

-- TEAM DEFINITIONS
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|Steppe LuminariaB.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 rounds=(#teams-1)*2
    local interval,extra=math.floor(total/rounds),total%rounds
    local m,day={},START
    for i=1,rounds 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
local function simulateDayResults(day)
    local sched=generateSchedule()
    local dm=mapDays()
    local ridx=dm[day] if not ridx then return {} end
    math.randomseed(day)
    local res={}
    for _,m in ipairs(sched[ridx]) do local h,a=m[1],m[2]
        local draw=0.1 local r=math.random()
        local hg,ag
        if r<draw then hg=math.random(0,12); ag=hg
        else local wh,wa=h.championships+1,a.championships+1
            local rem=(r-draw)/(1-draw)
            local winG,loseG=math.random(1,12),math.random(0,math.min(winG-1,12))
            if rem<wh/(wh+wa) then hg,ag=winG,loseG else hg,ag=loseG,winG end
        end
        table.insert(res,{home=h,away=a,hg=hg,ag=ag})
    end
    return res
end

-- 5) RENDER ROUND
local function renderRound(day,year)
    local dm=mapDays()
    if not dm[day] then return "'''No matches scheduled today'''" end
    local month,dom=date.getDateFromDay(day)
    local label=dom.." "..month.." "..year.." PSSC"

    local rows=""
    for _,r in ipairs(simulateDayResults(day)) 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 homeSpan="<span style='color:"..hcol..";'>'''"..hg.."'''</span>"
        local awaySpan="<span style='color:"..acol..";'>'''"..ag.."'''</span>"
        rows=rows..h.logo.." "..homeSpan.." "..h.fullName.." vs "..a.logo.." "..awaySpan.." "..a.fullName.."<br>"
    end
    -- remove trailing <br>
    rows=rows:sub(1,-5)
    return "|-\n| "..day.." || "..label.." || "..rows
end

-- 6) TODAY'S MATCHDAY
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
    if not mapDays()[day] then return "'''No matches scheduled today'''" end
    return '{| class="wikitable sortable"\n! Day !! Date !! Matches\n'..renderRound(day,year)..'\n|}'
end

-- 7) CURRENT STANDINGS
function p.renderStandings(frame)
    local curr=date.getCurrentDate()
    local today=tonumber(curr:match('^(%d+),')) or 0
    local last=math.min(today,END)
    local records={}
    for _,t in ipairs(teams) do records[t.code]={team=t,W=0,D=0,L=0,Pts=0,PF=0,PA=0} end
    for d=START,last do for _,res in ipairs(simulateDayResults(d)) do
            local hR,aR=records[res.home.code],records[res.away.code]
            hR.PF, hR.PA = hR.PF+res.hg, hR.PA+res.ag
            aR.PF, aR.PA = aR.PF+res.ag, aR.PA+res.hg
            if res.hg>res.ag then hR.W,hR.Pts=hR.W+1,hR.Pts+3; aR.L=aR.L+1
            elseif res.hg<res.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 _,rec in pairs(records) do rec.PD=rec.PF-rec.PA; table.insert(list,rec) 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"\n! Pos !! Team !! W !! D !! L !! Pts !! PF !! PA !! PD\n'
    for i,rec in ipairs(list) do
        out=out..'|-\n| '..i..' || '..rec.team.logo..' '..rec.team.fullName..
             ' || '..rec.W..' || '..rec.D..' || '..rec.L..' || '..rec.Pts..
             ' || '..rec.PF..' || '..rec.PA..' || '..rec.PD..'\n'
    end
    out=out..'|}'
    return out
end

return p