Module:PillSeasonSchedule
From MicrasWiki
Documentation for this module may be created at Module:PillSeasonSchedule/doc
local p = {}
-- Import date helpers from Module:P
local date = require('Module:P')
-- 1) 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) DOUBLE ROUND-ROBIN SCHEDULE GENERATOR
function p.generateSchedule()
local n = #teams
assert(n % 2 == 0, "Need an even number of teams")
local pool, rounds = {}, {}
for i = 1, n do pool[i] = teams[i] end
local fixed = table.remove(pool, 1)
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 TOTAL_DAYS = END - START + 1
local TOTAL_ROUNDS = (#teams - 1) * 2
local function mapDays()
local interval, extra = math.floor(TOTAL_DAYS / TOTAL_ROUNDS), TOTAL_DAYS % TOTAL_ROUNDS
local m, day = {}, START
for i = 1, TOTAL_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 FOR A GIVEN DAY
local function simulateDayResults(day)
local schedule = p.generateSchedule()
local dm = mapDays()
local ridx = dm[day]
if not ridx then return {} end
local pairs = schedule[ridx]
math.randomseed(day)
local results = {}
for _, m in ipairs(pairs) do
local h, a = m[1], m[2]
local drawProb = 0.1
local r = math.random()
local hg, ag
if r < drawProb then
local g = math.random(0, 12)
hg, ag = g, g
else
local wh, wa = h.championships + 1, a.championships + 1
local probHome = wh / (wh + wa)
local homeWin = (r - drawProb) / (1 - drawProb) < probHome
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 = h, away = a, hg = hg, ag = ag})
end
return results
end
-- 5) RENDER TODAY'S MATCHDAY
function p.renderSchedule(frame)
local curr = date.getCurrentDate()
local day = tonumber(string.match(curr, '^(%d+),')) or 0
local year = tonumber(string.match(curr, '.*, (%d+) PSSC')) or 51
if not mapDays()[day] then return "'''No matches scheduled today.'''" end
local header = {'{| class="wikitable sortable"', '! Day !! Date !! Matches'}
table.insert(header, renderRound(day, year))
table.insert(header, '|}')
return table.concat(header, "\n")
end
-- 6) RENDER RANKINGS WITH PF, PA, PD
function p.renderStandings(frame)
local curr = date.getCurrentDate()
local today = tonumber(string.match(curr, '^(%d+),')) or 0
local last = math.min(today, END)
-- initialize records
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
-- accumulate results
for day = START, last do
for _, res in ipairs(simulateDayResults(day)) do
local hRec = records[res.home.code]
local aRec = records[res.away.code]
-- update PF/PA
hRec.PF = hRec.PF + res.hg
hRec.PA = hRec.PA + res.ag
aRec.PF = aRec.PF + res.ag
aRec.PA = aRec.PA + res.hg
-- update W/D/L and points
if res.hg > res.ag then
hRec.W = hRec.W + 1; hRec.Pts = hRec.Pts + 3
aRec.L = aRec.L + 1
elseif res.hg < res.ag then
aRec.W = aRec.W + 1; aRec.Pts = aRec.Pts + 3
hRec.L = hRec.L + 1
else
hRec.D = hRec.D + 1; hRec.Pts = hRec.Pts + 1
aRec.D = aRec.D + 1; aRec.Pts = aRec.Pts + 1
end
end
end
-- compute PD and collect
local list = {}
for _, rec in pairs(records) do
rec.PD = rec.PF - rec.PA
table.insert(list, rec)
end
-- sort by points, PD, wins, name
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)
-- render
local out = {'{| class="wikitable sortable"', '! Pos !! Team !! W !! D !! L !! Pts !! PF !! PA !! PD'}
for i, rec in ipairs(list) do
table.insert(out, string.format(
"|-%n| %d || %s %s || %d || %d || %d || %d || %d || %d || %d",
i, i,
rec.team.logo, rec.team.fullName,
rec.W, rec.D, rec.L, rec.Pts,
rec.PF, rec.PA, rec.PD
))
end
table.insert(out, '|}')
return table.concat(out, "\n")
end
return p