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') | ||
-- | -- 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 | assert(n%2==0) | ||
local pool = {} | local pool={} 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 | ||
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 | ||
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 | ||
rounds[n-1+r]=rev | |||
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 | local total=END-START+1 | ||
local | local rounds=(#teams-1)*2 | ||
local interval = math.floor( | 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 | ||
| Line 89: | Line 77: | ||
-- 4) SIMULATE RESULTS | -- 4) SIMULATE RESULTS | ||
local function simulateDayResults(day) | local function simulateDayResults(day) | ||
local | local sched=generateSchedule() | ||
local dm = mapDays() | local dm=mapDays() | ||
local ridx = dm[day] | local ridx=dm[day] if not ridx then return {} end | ||
math.randomseed(day) | math.randomseed(day) | ||
local | local res={} | ||
for _, | for _,m in ipairs(sched[ridx]) do local h,a=m[1],m[2] | ||
local draw=0.1 local r=math.random() | |||
local | |||
local hg,ag | local hg,ag | ||
if r< | if r<draw then 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 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 | |||
local rem = (r- | |||
local winG = math.random(1,12) | |||
if | |||
end | end | ||
table.insert( | table.insert(res,{home=h,away=a,hg=hg,ag=ag}) | ||
end | end | ||
return | 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 | 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 label=dom.." "..month.." "..year.." PSSC" | ||
local | |||
for _, | local rows="" | ||
local h,a,hg,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= | local acol=(ag>hg and"green") or (ag<hg and"red") or "yellow" | ||
local awaySpan= | 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 | end | ||
return | -- remove trailing <br> | ||
rows=rows:sub(1,-5) | |||
return "|-\n| "..day.." || "..label.." || "..rows | |||
end | end | ||
-- 6) | -- 6) TODAY'S MATCHDAY | ||
function p.renderSchedule(frame) | function p.renderSchedule(frame) | ||
local curr = date.getCurrentDate() | local curr=date.getCurrentDate() | ||
local day | local day=tonumber(curr:match('^(%d+),')) or 0 | ||
local year = tonumber( | local year=tonumber(curr:match('.*, (%d+) PSSC')) or 0 | ||
if not mapDays()[day] then return "'''No matches scheduled today | if not mapDays()[day] then return "'''No matches scheduled today'''" end | ||
return | return '{| class="wikitable sortable"\n! Day !! Date !! Matches\n'..renderRound(day,year)..'\n|}' | ||
end | end | ||
-- 7) CURRENT STANDINGS | -- 7) CURRENT STANDINGS | ||
function p.renderStandings(frame) | function p.renderStandings(frame) | ||
local curr | local curr=date.getCurrentDate() | ||
local today = tonumber( | local today=tonumber(curr:match('^(%d+),')) or 0 | ||
local last | 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 | ||
for d=START,last do for _,res in ipairs(simulateDayResults(d)) do | |||
local hR,aR=records[res.home.code],records[res.away.code] | |||
for d=START,last 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 | ||
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 | |||
elseif res.hg<res.ag then | end end | ||
local list={} | |||
for _,rec in pairs(records) do rec.PD=rec.PF-rec.PA; table.insert(list,rec) end | |||
else | |||
local list = {} | |||
for _,rec in pairs(records) do | |||
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 | if a.PD~=b.PD then return a.PD>b.PD end | ||
if a.W~=b.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"\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 | ||
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 | end | ||
out=out..'|}' | |||
return | 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