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: | ||
local p = {} | local p = {} | ||
-- Import | -- Import calendar helpers from Module:P | ||
local date = require('Module:P') | local date = require('Module:P') | ||
| Line 36: | Line 36: | ||
} | } | ||
-- 2) | -- 2) SCHEDULE GENERATOR | ||
function p.generateSchedule() | function p.generateSchedule() | ||
local n = #teams | local n, rounds = #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 | for r=1,n-1 do | ||
local pairings = {} | local pairings = {} | ||
if r % 2 == 1 then table.insert(pairings, {fixed, pool[1]}) else table.insert(pairings, {pool[1], fixed}) end | 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 | 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 table.insert(pairings, {t1, t2}) else table.insert(pairings, {t2, t1}) end | if i%2==0 then 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 table.insert(rev, {m[2], m[1]}) end | for _,m in ipairs(rounds[r]) do table.insert(rev,{m[2],m[1]}) end | ||
rounds[n - 1 + r] = rev | rounds[n-1+r]=rev | ||
end | end | ||
return rounds | return rounds | ||
| Line 62: | Line 62: | ||
-- 3) MAP ROUNDS TO DAYS | -- 3) MAP ROUNDS TO DAYS | ||
local START, END = 62, 183 | local START,END=62,183 | ||
local function mapDays() | local function mapDays() | ||
local interval, extra = math.floor( | local total, rounds = END-START+1, (#teams-1)*2 | ||
local interval, extra = math.floor(total/rounds), total%rounds | |||
local m, day = {}, START | local m, day = {}, START | ||
for i = 1, | 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 | ||
end | end | ||
-- 4) SIMULATE RESULTS FOR | -- 4) SIMULATE RESULTS FOR DAY | ||
local function simulateDayResults(day) | local function simulateDayResults(day) | ||
local schedule = p.generateSchedule() | local schedule, dm = p.generateSchedule(), 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 | local res={} | ||
for _, m in ipairs( | for _,m in ipairs(schedule[ridx]) do | ||
local h, a = m[1], m[2] | local h,a=m[1],m[2] | ||
local | local draw=0.1 | ||
local r = math.random() | local r=math.random() | ||
local hg, ag | local hg,ag | ||
if r < | if r<draw then local g=math.random(0,12); hg,ag=g,g | ||
else | else | ||
local wh, wa = h.championships + 1, a.championships + 1 | local wh,wa=h.championships+1,a.championships+1 | ||
local probHome = wh / (wh + wa) | local probHome=wh/(wh+wa) | ||
local homeWin = (r - | local homeWin=(r-draw)/(1-draw)<probHome | ||
local winG = math.random(1, 12) | local winG,loseG=math.random(1,12),math.random(0,math.min(winG-1,12)) | ||
if homeWin then hg,ag=winG,loseG else hg,ag=loseG,winG end | |||
if homeWin then hg, ag = winG, loseG else hg, ag = loseG, winG end | |||
end | end | ||
table.insert( | table.insert(res,{home=h,away=a,hg=hg,ag=ag}) | ||
end | end | ||
return | return res | ||
end | end | ||
-- 5) RENDER | -- 5) RENDER ROUND ROW | ||
local function renderRound(day,year) | |||
local dm=mapDays() | |||
if not dm[day] then return "'''No matches scheduled today.'''" end | |||
local results=simulateDayResults(day) | |||
local month,dom = date.getDateFromDay(day) | |||
local label=string.format("%d %s %d PSSC",dom,month,year) | |||
local lines={} | |||
for _,m in ipairs(results) do | |||
local h,a,hg,ag=m.home,m.away,m.hg,m.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" | |||
table.insert(lines,string.format( | |||
"%s <span style=\"color:%s;\">'''%d'''</span> %s vs %s <span style=\"color:%s;\">'''%d'''</span> %s", | |||
h.logo,hcol,hg,h.fullName,a.logo,acol,ag,a.fullName | |||
)) | |||
end | |||
return string.format("|-%n| %d || %s || %s\n",day,label,table.concat(lines,'<br>')) | |||
end | |||
-- 6) RENDER SCHEDULE | |||
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(string.match(curr,'^(%d+),')) or 0 | ||
local year = tonumber(string.match(curr, '.*, (%d+) PSSC')) or 51 | local year=tonumber(string.match(curr,'.*, (%d+) PSSC')) or 51 | ||
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 | end | ||
-- | -- 7) RENDER 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(string.match(curr,'^(%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} | ||
records[t.code] = {team = t, W = 0, D = 0, L = 0, Pts = 0, PF = 0, PA = 0} | |||
end | end | ||
for day=START,last do | |||
for day = START, last do | for _,res in ipairs(simulateDayResults(day)) do | ||
for _, res in ipairs(simulateDayResults(day)) do | local hR,aR=records[res.home.code],records[res.away.code] | ||
local | 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 | |||
if res.hg > res.ag then | |||
elseif res.hg < res.ag then | |||
else | else | ||
hR.D,hR.Pts=hR.D+1,hR.Pts+1 | |||
aR.D,aR.Pts=aR.D+1,aR.Pts+1 | |||
end | end | ||
end | end | ||
end | end | ||
local list={} | |||
local list = {} | for _,r in pairs(records) do r.PD=r.PF-r.PA;table.insert(list,r) end | ||
for _, | 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 | |||
table.sort(list, function(a, b) | |||
if a.Pts ~= b.Pts then return a.Pts > b.Pts end | |||
if a.PD | |||
if a.W | |||
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"', '! Pos !! Team !! W !! D !! L !! Pts !! PF !! PA !! PD'} | for i,r in ipairs(list) do | ||
for i, | table.insert(out,string.format( | ||
table.insert(out, string.format( | |||
"|-%n| %d || %s %s || %d || %d || %d || %d || %d || %d || %d", | "|-%n| %d || %s %s || %d || %d || %d || %d || %d || %d || %d", | ||
i, i, | i,i,r.team.logo,r.team.fullName,r.W,r.D,r.L,r.Pts,r.PF,r.PA,r.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:02, 19 April 2025
Documentation for this module may be created at Module:PillSeasonSchedule/doc
local p = {}
-- Import calendar 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) SCHEDULE GENERATOR
function p.generateSchedule()
local n, rounds = #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)
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, rounds = END-START+1, (#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 FOR DAY
local function simulateDayResults(day)
local schedule, dm = p.generateSchedule(), mapDays()
local ridx = dm[day]
if not ridx then return {} end
math.randomseed(day)
local res={}
for _,m in ipairs(schedule[ridx]) do
local h,a=m[1],m[2]
local draw=0.1
local r=math.random()
local hg,ag
if r<draw 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-draw)/(1-draw)<probHome
local winG,loseG=math.random(1,12),math.random(0,math.min(winG-1,12))
if homeWin 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 ROW
local function renderRound(day,year)
local dm=mapDays()
if not dm[day] then return "'''No matches scheduled today.'''" end
local results=simulateDayResults(day)
local month,dom = date.getDateFromDay(day)
local label=string.format("%d %s %d PSSC",dom,month,year)
local lines={}
for _,m in ipairs(results) do
local h,a,hg,ag=m.home,m.away,m.hg,m.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"
table.insert(lines,string.format(
"%s <span style=\"color:%s;\">'''%d'''</span> %s vs %s <span style=\"color:%s;\">'''%d'''</span> %s",
h.logo,hcol,hg,h.fullName,a.logo,acol,ag,a.fullName
))
end
return string.format("|-%n| %d || %s || %s\n",day,label,table.concat(lines,'<br>'))
end
-- 6) RENDER SCHEDULE
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
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) RENDER 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 _,t in ipairs(teams) do
records[t.code]={team=t,W=0,D=0,L=0,Pts=0,PF=0,PA=0}
end
for day=START,last do
for _,res in ipairs(simulateDayResults(day)) 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 _,r in pairs(records) do r.PD=r.PF-r.PA;table.insert(list,r) 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,r in ipairs(list) do
table.insert(out,string.format(
"|-%n| %d || %s %s || %d || %d || %d || %d || %d || %d || %d",
i,i,r.team.logo,r.team.fullName,r.W,r.D,r.L,r.Pts,r.PF,r.PA,r.PD
))
end
table.insert(out,'|}')
return table.concat(out,"\n")
end
return p