Module:PillSeasonSchedule
From MicrasWiki
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