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 1: | Line 1: | ||
-- Module:PillSeasonSchedule | -- Module:PillSeasonSchedule | ||
local p = {} | local p = {} | ||
local date = require('Module:BassaridianCalendar') | local date = require('Module:BassaridianCalendar') | ||
-- 1) TEAM DEFINITIONS | -- 1) 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 }, | ||
{ code = "Ashguard Pyralis", fullName = "Ashguard Pyralis", logo = "{{flagicon image|AshguardpyralisB.png|200px}}", championships = 1 }, | { 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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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 } | { code="Jezeraah City", fullName="Jezeraah City", logo="{{flagicon image|JezeraahCity.png|200px}}", championships=0 } | ||
} | } | ||
-- 2) | -- 2) SCHEDULE GENERATOR | ||
function | local function generateSchedule() | ||
local n = #teams | local n = #teams | ||
assert(n % 2 == 0, "Need | assert(n % 2 == 0, "Need even number of teams") | ||
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) | ||
for r = 1, n - 1 do | local rounds = {} | ||
for r=1,n-1 do | |||
local pairings = {} | local pairings = {} | ||
if r % 2 == 1 then | if r % 2 == 1 then | ||
| Line 51: | Line 50: | ||
table.insert(pairings, {pool[1], fixed}) | table.insert(pairings, {pool[1], fixed}) | ||
end | 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}) | table.insert(pairings, {t1, t2}) | ||
| Line 62: | Line 61: | ||
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]}) | table.insert(rev, {m[2], m[1]}) | ||
end | end | ||
rounds[n - 1 + r] = rev | rounds[n-1+r] = rev | ||
end | end | ||
return rounds | return rounds | ||
end | end | ||
-- 3) MAP | -- 3) MAP ROUNDS TO DAYS | ||
local START, END = 62, 183 | local START, END = 62, 183 | ||
local function mapDays() | local function mapDays() | ||
local totalDays = END - START + 1 | local totalDays = END - START + 1 | ||
local totalRounds = (#teams - 1) * 2 | local totalRounds = (#teams - 1) * 2 | ||
local interval | local interval = math.floor(totalDays / totalRounds) | ||
local m | local extra = totalDays % totalRounds | ||
for i = 1, totalRounds do | local m = {} | ||
local day = START | |||
for i=1,totalRounds 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) | ||
| Line 87: | Line 88: | ||
end | end | ||
-- 4) SIMULATE RESULTS | -- 4) SIMULATE RESULTS | ||
local function simulateDayResults(day) | local function simulateDayResults(day) | ||
local | local schedule = generateSchedule() | ||
local dm = mapDays() | local dm = mapDays() | ||
local ridx = dm[day] | local ridx = dm[day] | ||
| Line 95: | Line 96: | ||
math.randomseed(day) | math.randomseed(day) | ||
local results = {} | local results = {} | ||
for _, | for _, match in ipairs(schedule[ridx]) do | ||
local | local home, away = match[1], match[2] | ||
local drawProb = 0.1 | local drawProb = 0.1 | ||
local r = math.random() | local r = math.random() | ||
local hg, ag | local hg, ag | ||
if r < drawProb then | if r < drawProb then | ||
hg = math.random(0,12) | |||
ag = hg | |||
else | else | ||
local wh | local wh = home.championships + 1 | ||
local | local wa = away.championships + 1 | ||
local | local rem = (r - drawProb) / (1 - drawProb) | ||
local winG = math.random(1, 12) | local homeWin = rem < (wh / (wh + wa)) | ||
local loseG = math.random(0, math.min(winG - 1, 12)) | local winG = math.random(1,12) | ||
if homeWin then hg, ag = winG, loseG else hg, ag = loseG, winG end | 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 = | table.insert(results, {home=home, away=away, hg=hg, ag=ag}) | ||
end | end | ||
return results | return results | ||
end | end | ||
-- 5) RENDER | -- 5) RENDER ONE 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 | local dateLabel = string.format("%d %s %d PSSC", dom, month, year) | ||
local lines = {} | local lines = {} | ||
for _, res in ipairs(simulateDayResults(day)) do | for _, res in ipairs(simulateDayResults(day)) do | ||
local | local home, away, hg, ag = res.home, res.away, res.hg, res.ag | ||
local hcol = hg > ag and "green" or hg < ag and "red" or "yellow" | 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 acol = ag>hg and "green" or ag<hg and "red" or "yellow" | ||
local homeSpan = string.format("<span style='color:%s;'>'''%d'''</span>", hcol, hg) | |||
local awaySpan = string.format("<span style='color:%s;'>'''%d'''</span>", acol, ag) | |||
table.insert(lines, string.format("%s %s %s vs %s %s %s", | |||
home.logo, homeSpan, home.fullName, | |||
away.logo, awaySpan, away.fullName | |||
)) | )) | ||
end | end | ||
return string.format("|- | return string.format("|-\n| %d || %s || %s\n", day, dateLabel, table.concat(lines,"<br>")) | ||
end | end | ||
-- 6) ENTRY | -- 6) ENTRY: 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, | local day = tonumber(string.match(curr, "^(%d+),")) or 0 | ||
local year = tonumber(string.match(curr, | local year = tonumber(string.match(curr, ".*, (%d+) PSSC")) or 0 | ||
local dm = mapDays() | |||
if not dm[day] then return "'''No matches scheduled today.'''" end | |||
return table.concat({ | return table.concat({ | ||
'{| class="wikitable sortable"', | '{| class="wikitable sortable"', | ||
| Line 150: | Line 158: | ||
end | end | ||
-- 7) ENTRY | -- 7) ENTRY: CURRENT STANDINGS | ||
function p.renderStandings(frame) | function p.renderStandings(frame) | ||
local curr = date.getCurrentDate() | local curr = date.getCurrentDate() | ||
local today = tonumber(string.match(curr, | local today = tonumber(string.match(curr, "^(%d+),")) or 0 | ||
local last = math.min(today, END) | local last = math.min(today, END) | ||
local records = {} | local records = {} | ||
for _, | for _, team in ipairs(teams) do | ||
records[ | records[team.code] = {team=team, W=0, D=0, L=0, Pts=0, PF=0, PA=0} | ||
end | end | ||
for d=START,last do | |||
for | for _, res in ipairs(simulateDayResults(d)) do | ||
for _, res in ipairs(simulateDayResults( | local hR = records[res.home.code] | ||
local hR | local aR = records[res.away.code] | ||
hR.PF = hR.PF + res.hg | hR.PF = hR.PF + res.hg | ||
aR.PF = aR.PF + res.ag | hR.PA = hR.PA + res.ag | ||
aR.PF = aR.PF + res.ag | |||
aR.PA = aR.PA + res.hg | |||
if res.hg > res.ag then | if res.hg > res.ag then | ||
hR.W = hR.W + 1; hR.Pts = hR.Pts + 3; aR.L = aR.L + 1 | hR.W = hR.W + 1; hR.Pts = hR.Pts + 3; aR.L = aR.L + 1 | ||
| Line 176: | Line 185: | ||
end | end | ||
end | end | ||
local list = {} | local list = {} | ||
for _, rec in pairs(records) do | for _, rec in pairs(records) do | ||
| Line 182: | Line 190: | ||
table.insert(list, rec) | table.insert(list, rec) | ||
end | 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 | if a.PD~=b.PD then return a.PD>b.PD end | ||
if a.W | 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 = { | |||
local out = {'{| class="wikitable sortable"', '! Pos !! Team !! W !! D !! L !! Pts !! PF !! PA !! PD'} | '{| class="wikitable sortable"', | ||
'! Pos !! Team !! W !! D !! L !! Pts !! PF !! PA !! PD' | |||
} | |||
for i, rec in ipairs(list) do | for i, rec in ipairs(list) do | ||
table.insert(out, string.format( | table.insert(out, string.format("|-\n| %d || %s %s || %d || %d || %d || %d || %d || %d || %d", | ||
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 | rec.PF, rec.PA, rec.PD | ||
)) | )) | ||
end | end | ||
table.insert(out, | table.insert(out, "|}") | ||
return table.concat(out, "\n") | return table.concat(out, "\n") | ||
end | end | ||
return p | return p | ||
Revision as of 19:10, 19 April 2025
Documentation for this module may be created at Module:PillSeasonSchedule/doc
-- Module:PillSeasonSchedule
local p = {}
local date = require('Module:BassaridianCalendar')
-- 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) SCHEDULE GENERATOR
local function generateSchedule()
local n = #teams
assert(n % 2 == 0, "Need even number of teams")
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 totalDays = END - START + 1
local totalRounds = (#teams - 1) * 2
local interval = math.floor(totalDays / totalRounds)
local extra = totalDays % totalRounds
local m = {}
local day = START
for i=1,totalRounds 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 schedule = generateSchedule()
local dm = mapDays()
local ridx = dm[day]
if not ridx then return {} end
math.randomseed(day)
local results = {}
for _, match in ipairs(schedule[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 ONE 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 dateLabel = string.format("%d %s %d PSSC", dom, month, year)
local lines = {}
for _, res in ipairs(simulateDayResults(day)) do
local home, away, hg, ag = res.home, res.away, res.hg, res.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 = string.format("<span style='color:%s;'>'''%d'''</span>", hcol, hg)
local awaySpan = string.format("<span style='color:%s;'>'''%d'''</span>", acol, ag)
table.insert(lines, string.format("%s %s %s vs %s %s %s",
home.logo, homeSpan, home.fullName,
away.logo, awaySpan, away.fullName
))
end
return string.format("|-\n| %d || %s || %s\n", day, dateLabel, table.concat(lines,"<br>"))
end
-- 6) ENTRY: 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 0
local dm = mapDays()
if not dm[day] then return "'''No matches scheduled today.'''" end
return table.concat({
'{| class="wikitable sortable"',
'! Day !! Date !! Matches',
renderRound(day, year),
'|}'
}, "\n")
end
-- 7) ENTRY: CURRENT STANDINGS
function p.renderStandings(frame)
local curr = date.getCurrentDate()
local today = tonumber(string.match(curr, "^(%d+),")) or 0
local last = math.min(today, END)
local records = {}
for _, team in ipairs(teams) do
records[team.code] = {team=team, 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 = records[res.home.code]
local aR = records[res.away.code]
hR.PF = hR.PF + res.hg
hR.PA = hR.PA + res.ag
aR.PF = aR.PF + res.ag
aR.PA = aR.PA + res.hg
if res.hg > res.ag then
hR.W = hR.W + 1; hR.Pts = hR.Pts + 3; aR.L = aR.L + 1
elseif res.hg < res.ag then
aR.W = aR.W + 1; aR.Pts = aR.Pts + 3; hR.L = hR.L + 1
else
hR.D = hR.D + 1; hR.Pts = hR.Pts + 1
aR.D = aR.D + 1; aR.Pts = 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"',
'! 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, 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