Documentation for this module may be created at Module:Bracket/doc
local getArgs = require('Module:Arguments').getArgs
local html = require('Module:MatchHTML')
local Bracket = {}
local VariablesLua = mw.ext.VariablesLua
function Bracket.main(frame)
local args = getArgs(frame)
if args.teams == nil then return 'Have to enter a "teams" value as a Bracket template paramater' end
-- set const values
local roundW = args.roundW or 180
-- create all the html parent elements
local container = mw.html.create('div'):addClass('bracket')
if args.lteams then container:addClass('de') end
local wrapper = mw.html.create('div'):addClass('bracket-wrapper')
local ubheader = mw.html.create('div'):addClass('bracket-header')
local ubrounds = mw.html.create('div'):addClass('bracket-rounds')
local lbheader = mw.html.create('div'):addClass('bracket-header')
local lbrounds = mw.html.create('div'):addClass('bracket-rounds')
container:node(wrapper)
-- get max amount of matches for the first round
local maxUbroundAmount = args.rounds or math.ceil(math.log(args.teams) / math.log(2))
local ubroundAmount = 0
local lbroundAmount = 0
local upper = args.upper ~= nil and mw.text.jsonDecode(args.upper) or {}
local lower = args.lower ~= nil and mw.text.jsonDecode(args.lower) or {}
local lteams = tonumber(args.lteams)
local matchAmount = (2^maxUbroundAmount)
local upperMatches = args.teams and args.teams / 2 or 0
local lowerMatches = lteams and lteams / 2 or 0
local dropping = upperMatches
local upperRounds = {}
local lowerRounds = {}
-- Go through upper bracket to see how many rounds and matches it has
while tonumber(matchAmount) > 1 do
ubroundAmount = ubroundAmount + 1
local round = upper['R' .. ubroundAmount] ~= nil and mw.text.jsonDecode(upper['R' .. ubroundAmount]) or nil
local roundTitle = (round and round.title) or lteams and 'Upper round ' .. ubroundAmount or 'Round ' .. ubroundAmount
-- if round.matches is lower than previous round divided by 2
if (round and round.matches) and round.matches * 2 < matchAmount then
matchAmount = matchAmount / 2
else
matchAmount = (round and round.matches) or matchAmount / 2
end
upperRounds[ubroundAmount] = {
matches = tonumber(matchAmount),
title = roundTitle
}
end
-- If lower bracket exists then populate lowerRounds and adjust upperRounds
if lteams ~= nil then
local ubRound = 1
if lteams > 0 then ubRound = 0 end
local lbRound = 1
local lower = args.lower ~= nil and mw.text.jsonDecode(args.lower) or {}
local upperMatches = upperRounds[1].matches
local lowerMatches = lteams > 0 and lteams / 2 or upperMatches / 2
if args.ladvance then lowerMatches = lowerMatches + args.ladvance / 2 end
-- Start loop
while #upperRounds >= ubRound and lowerMatches >= 1 do
upperMatches = (upperRounds[ubRound] and upperRounds[ubRound].matches) or 0
local nextUpperMatches = upperRounds[ubRound + 1] and upperRounds[ubRound + 1].matches or 0
local round = lower['R' .. lbRound] ~= nil and mw.text.jsonDecode(lower['R' .. lbRound]) or {}
local roundTitle = round.title or upperMatches ~= 1 and 'Lower round ' .. lbRound or 'Lower Bracket Final'
-- If amount of matches is manually entered, then overwrite
if round.matches then lowerMatches = round.matches end
-- If lowerMatches is exactly 2 times lower than upperMatches
if lowerMatches * 2 == upperMatches then
table.insert(lowerRounds, Bracket.newRound(lowerMatches, roundTitle))
ubRound = ubRound + 1
-- If lowerMatches is same as upperMatches
elseif lowerMatches == upperMatches then
table.insert(lowerRounds, Bracket.newRound(lowerMatches, roundTitle))
if upperMatches ~= 1 then
table.insert(upperRounds, ubRound + 1, {matches = 0})
nextUpperMatches = 0
end
ubRound = ubRound + 1
-- Else if upperMatches is lower
elseif upperMatches < lowerMatches then
table.insert(lowerRounds, Bracket.newRound(lowerMatches, roundTitle))
end
lowerMatches = upperMatches == 0 and lowerMatches or nextUpperMatches / 2 + lowerMatches / 2
if lbRound == 1 and args.ladvance then lowerMatches = lowerMatches / 2 end
lbRound = lbRound + 1
if upperMatches == 0 then ubRound = ubRound + 1 end
end
end
-- If lower and upper bracket both start with same amount of teams, then shift upper bracket one column to the right
if args.teams == args.lteams then
table.insert(upperRounds, 1, {matches = 0})
end
-- If ladvance is set then add empty round to UB round 2
if args.ladvance then table.insert(upperRounds, 2, {matches = 0}) end
-- draw upper bracket
for i = 1, #upperRounds do
local roundNode = mw.html.create('div'):addClass('bracket-round')
local round = upperRounds[i]
local argsRound = upper['R' .. i] ~= nil and mw.text.jsonDecode(upper['R' .. i]) or nil
local matches = round.matches
-- Add round header to container
ubheader:node(mw.html.create('div'):wikitext(round.title))
for j = 1, matches do
local match = argsRound and argsRound['M' .. j] or nil
-- if no existing match html, then create it from scratch
if match == nil then
roundNode:node(mw.html.create('div'):addClass('match'):node(html.team('')):node(html.team('')))
-- roundNode:node(mw.html.create('div'):addClass('match'):node(team1):node(team2):node(html.team('')):node(html.team('')))
else
roundNode:node(match)
end
end
--see if next rounds matchAmount is equal to or greater than current rounds
local nextRound = upperRounds[i + 1] or {}
if tonumber(matches) <= tonumber(nextRound.matches or 0) then
roundNode:addClass('round-offset')
else
if tonumber(matches) ~= 1 or lteams ~= nil then
roundNode:addClass('conn-r')
end
end
-- If previous round was empty
if upperRounds[i - 1] and upperRounds[i - 1].matches == 0 then
if i ~= 2 then
roundNode:addClass('long')
else
roundNode:addClass('first')
end
end
ubrounds:node(roundNode)
end
-- draw lower bracket
if args.lteams then
for i = 1, #lowerRounds do
local roundNode = mw.html.create('div'):addClass('bracket-round')
local round = lowerRounds[i]
local argsRound = lower['R' .. i] ~= nil and mw.text.jsonDecode(lower['R' .. i]) or nil
local matches = round.matches
-- Add round header to container
lbheader:node(mw.html.create('div'):wikitext(round.title))
for j = 1, matches do
local match = argsRound and argsRound['M' .. j] or nil
-- if no existing match html, then create it from scratch
if match == nil then
roundNode:node(mw.html.create('div'):addClass('match'):node(html.team('')):node(html.team('')))
else
roundNode:node(match)
end
end
--see if next rounds matchAmount is equal to or greater than current rounds
local nextRound = lowerRounds[i + 1] or {}
if tonumber(matches) <= tonumber(nextRound.matches or 0) then
roundNode:addClass('round-offset')
else
if tonumber(matches) ~= 1 or lteams ~= nil then
roundNode:addClass('conn-r')
end
end
-- Add adjusting classes for connectors if next round is round offset
if lowerRounds[i + 1] and lowerRounds[i + 2] and lowerRounds[i + 1].matches == lowerRounds[i + 1].matches then
roundNode:addClass('conn-adj')
end
-- If previous round was empty
if lowerRounds[i - 1] and lowerRounds[i - 1].matches == 0 then roundNode:addClass('long') end
if i == #lowerRounds then roundNode:addClass('last') end
lbrounds:node(roundNode)
end
-- Add grand final
if teams > 2 then
local maxMatches = 0
for _, obj in ipairs(upperRounds) do
if obj.matches and obj.matches > maxMatches then
maxMatches = obj.matches
end
end
local grandFinal = mw.html.create('div'):addClass('grand-final'):css('width', roundW .. 'px'):css('height', (maxMatches * 56 + (maxMatches - 1) * 10 + 26 + 10) .. 'px')
grandFinal:node(mw.html.create('div'):addClass('bracket-header'):node(mw.html.create('div'):wikitext('Grand Final')))
if args.final == nil then
grandFinal:node(mw.html.create('div'):addClass('match'):node(html.team('')):node(html.team('')))
else
grandFinal:node(mw.html.create('div'):addClass('grand-final-round'):node(match))
end
container:node(grandFinal)
end
end
local maxRounds = #upperRounds <= #lowerRounds and #lowerRounds or #upperRounds
ubheader:css('grid-template-columns', 'repeat(' .. maxRounds .. ', ' .. roundW .. 'px)')
ubrounds:css('grid-template-columns', 'repeat(' .. maxRounds .. ', ' .. roundW .. 'px)')
lbheader:css('grid-template-columns', 'repeat(' .. maxRounds .. ', ' .. roundW .. 'px)')
lbrounds:css('grid-template-columns', 'repeat(' .. maxRounds .. ', ' .. roundW .. 'px)')
wrapper:node(ubheader):node(ubrounds):node(lbheader):node(lbrounds)
return container
end
function Bracket.newRound(matches, title)
return {
matches = matches,
title = title
}
end
return Bracket