Module:Team bracket: Difference between revisions

Jump to navigation Jump to search
(Created page with "-- -- This module implements many bracket templates -- local p = {} local args = {} local rounds local legs = {} local compact local byes local hideSeeds local showSeeds loca...")
 
No edit summary
 
Line 5: Line 5:
local p = {}
local p = {}
local args = {}
local args = {}
local rows = {}
local mask = {}
local rounds
local rounds
local maxround
local legs = {}
local legs = {}
local compact
local compact
Line 17: Line 20:
local sepwidth
local sepwidth
local aggsep
local aggsep
local aggregate
local boldwinner
local boldwinner
local hideomittedscores
local hideomittedscores
Line 28: Line 32:
local function isblank(s)
local function isblank(s)
return (not s) or (s == '')
return (not s) or (s == '')
end
local function sumScores(s1, s2)
s1 = mw.ustring.gsub(s1 or '', '^[\'%s]*([%d%.]*).-$', '%1')
s2 = mw.ustring.gsub(s2 or '', '^[\'%s]*([%d%.]*).-$', '%1')
if s1 ~= '' and s2 ~= '' then
return tonumber(s1) + tonumber(s2)
end
return s1
end
end


local function scoreCompare(s1,s2,highwin)
local function scoreCompare(s1,s2,highwin)
s1 = mw.ustring.gsub(s1 or '', '^%s*([%d%.]*).-$', '%1')
local ps1 = mw.ustring.gsub(s1 or '', '^[\'%s]*([%d%.]*)[\'%s]*%([\'%s]*([%d%.]*)[\'%s]*%).-$', '%2')
s2 = mw.ustring.gsub(s2 or '', '^%s*([%d%.]*).-$', '%1')
local ps2 = mw.ustring.gsub(s2 or '', '^[\'%s]*([%d%.]*)[\'%s]*%([\'%s]*([%d%.]*)[\'%s]*%).-$', '%2')
s1 = mw.ustring.gsub(s1 or '', '^[\'%s]*([%d%.]*).-$', '%1')
s2 = mw.ustring.gsub(s2 or '', '^[\'%s]*([%d%.]*).-$', '%1')
if s1 ~= '' and s2 ~= '' then
if s1 ~= '' and s2 ~= '' then
Line 38: Line 53:
s2 = tonumber(s2)
s2 = tonumber(s2)
if s1 and s2 then
if s1 and s2 then
if (s1 == s2) then
ps1 = tonumber(ps1)
ps2 = tonumber(ps2)
if ps1 and ps2 then
s1 = ps1
s2 = ps2
end
end
if highwin then
if highwin then
return ((s1 > s2) and 1) or ((s1 < s2) and 2) or 0
return ((s1 > s2) and 1) or ((s1 < s2) and 2) or 0
Line 46: Line 69:
end
end
return 0
return 0
end
local function unboldParenthetical(s)
if s then
s = mw.ustring.gsub(s, '(%(%[%[[^%[%]]*%]%]%))', '<span style="font-weight:normal">%1</span>')
end
return s
end
end


Line 115: Line 145:
local function parseLegs(s)
local function parseLegs(s)
s = mw.text.split((s or '1') .. '/', '[%s]*/[%s]*')
s = mw.text.split((s or '1') .. '/', '[%s]*/[%s]*')
if aggregate == 'n' or aggregate == 'no' or aggregate == '0' then
aggregate = ''
end
local n = showThird and (rounds + 1) or (rounds)
local n = showThird and (rounds + 1) or (rounds)
local lastlegs = nil
for r=1,n do
for r=1,n do
if s[r] ~= nil and s[r] ~= '' and tonumber(s[r]) then
if s[r] ~= nil and s[r] ~= '' and tonumber(s[r]) then
legs[r] = tonumber(s[r])
legs[r] = tonumber(s[r])
elseif legs[r-1] then
elseif lastlegs then
legs[r] = legs[r-1]
legs[r] = lastlegs
else
else
legs[r] = 1
legs[r] = 1
end
lastlegs = legs[r]
if legs[r] > 1 and aggregate ~= '' then
legs[r] = legs[r] + 1
end
end
end
end
end
end


function getSeeds()
local function getSeeds()
local seeds = {1, 2}
local seeds = {1, 2}
local count = 2
local count = 2
Line 148: Line 186:
end
end


local function addBlank(row, width)
local function addBlank(i, css, rowspan, colspan)
local cell = row:tag('td')
local row = rows[i]
if width then
rowspan = rowspan or 1
cell:css('width', width)
local jmax = i + rowspan - 1
for j = i, jmax do
if rows[j] == nil then
rowspan = rowspan - 1
elseif row == nil then
row = rows[j]
end
end
local cell = row and row:tag('td') or mw.html.create('td')
if rowspan and rowspan > 1 then
cell:attr('rowspan', rowspan)
end
if colspan and colspan > 1 then
cell:attr('colspan', colspan)
end
if css then
cell:css(css)
end
end
return cell
return cell
Line 171: Line 225:
end
end


local function addHeading(row, r, text)
local function addHeading(row, r, text, pad)
pad = (pad == nil or pad < 0) and 0 or pad
row:tag('td')
row:tag('td')
:attr('colspan', tonumber(hideSeeds and '1' or '2') + legs[r])
:attr('colspan', tonumber(hideSeeds and '1' or '2') + legs[r] + pad)
:css('text-align', 'center')
:css('text-align', 'center')
:css('border', '1px solid #aaa')
:css('border', '1px solid #aaa')
Line 228: Line 283:
end
end
if mw.ustring.find(value, '[%s]*<[%s/]*[Bb][Rr][%s/]*>[%s ]*&[Nn][Bb][Ss][Pp];[%s]*') then
if mw.ustring.find(value, '[%s]*<[%s/]*[Bb][Rr][%s/]*>[%s ]*&[Nn][Bb][Ss][Pp];[%s]*') then
tcats = '[[Category:Pages using a team bracket with nbsp]]'
tcats = tcats .. '[[Category:Pages using a team bracket with nbsp]]'
end
end
return mw.ustring.gsub(value, '[%s]*<[%s/]*[Bb][Rr][%s/]*>[%s ]*&[Nn][Bb][Ss][Pp];[%s]*', '<br/>')
return mw.ustring.gsub(value, '[%s]*<[%s/]*[Bb][Rr][%s/]*>[%s ]*&[Nn][Bb][Ss][Pp];[%s]*', '<br/>')
Line 254: Line 309:
end
end


local function addPath(rows, index, round, top, left, w)
local function addPath(index, round, top, left, w)
local prop = top and 'border-bottom-width' or 'border-top-width'
local prop = top and 'border-bottom-width' or 'border-top-width'
if left and round == 1 then
if left and round == 1 then
if compact then
if compact then
addBlank(rows[index])
addBlank(index)
else
else
addBlank(rows[index]):css('height', '7px')
addBlank(index, {['height'] = '7px'})
addBlank(rows[index + 1]):css('height', '7px')
addBlank(index+1, {['height'] = '7px'})
end
end
return nil
return nil
else
else
local cell = addBlank(rows[index])
local cell = addBlank(index,  
:attr('rowspan', (not compact) and '2' or nil)
{['border-width'] = '0',
:css('border-width', '0')
['border-style'] = 'solid',
:css('border-style', 'solid')
['border-color'] = 'black'}, (not compact) and 2 or 1)
:css('border-color', 'black')
if left or round < maxround and not left then
if left or round < rounds and not left then
cell:css(prop, w or '2px')
cell:css(prop, w or '2px')
end
end
Line 277: Line 331:
end
end


local function renderTeam(row, round, team, top, otherbye)
local function renderTeam(row, round, team, top, otherbye, pad)
pad = (pad == nil or pad < 0) and 0 or pad
tcs = pad + 1
local seedCell
local seedCell
local seedArg = getTeamArg(round, 'seed', team)
local seedArg = getTeamArg(round, 'seed', team)
Line 301: Line 357:
if isblank(teamArg) then
if isblank(teamArg) then
teamArg = '&nbsp;'
teamArg = '&nbsp;'
elseif boldwinner ~= '' then
teamArg = unboldParenthetical(teamArg)
end
if not showSeed and (not hideSeeds) then
tcs = tcs + 1
end
end


Line 307: Line 369:
:css('padding', '0 2px')
:css('padding', '0 2px')
:attr('rowspan', (not compact) and '2' or nil)
:attr('rowspan', (not compact) and '2' or nil)
:attr('colspan', (tcs > 1) and tcs or nil)
:wikitext(teamArg)
:wikitext(teamArg)
:newline()
:newline()
addBorders(teamCell, top or otherbye, true, false)
addBorders(teamCell, top or otherbye, true, false)
if not showSeed and (not hideSeeds) then
teamCell:attr('colspan', '2')
end


local scoreCells = {}
local scoreCells = {}
local wins, otherwins = 0, 0
local wins, otherwins = 0, 0
local teamcolspan = 1
local sumscore, othersumscore = 0, 0
local teamcolspan = tcs
local hassum = false
for s = 1, legs[round] do
for s = 1, legs[round] do
local fw = nil
local fw = nil
local score1 = getScoreArg(round, team, s) or ((legs[round] == 1) and getScoreArg(round, team)) or nil
local agg = legs[round] > 1 and s == legs[round] and true or false
local score2 = getScoreArg(round, otherteam, s) or ((legs[round] == 1) and getScoreArg(round, otherteam)) or nil
local score1 = (agg and getScoreArg(round, team, 'agg') or nil) or
getScoreArg(round, team, s) or ((legs[round] == 1) and getScoreArg(round, team)) or nil
local score2 = (agg and getScoreArg(round, otherteam, 'agg') or nil) or
getScoreArg(round, otherteam, s) or ((legs[round] == 1) and getScoreArg(round, otherteam)) or nil
local showscore = true
local showscore = true
if score1 == nil and score2 == nil then
if agg and aggregate ~= '' and score1 == nil and hassum then
score1 = (aggregate == 'score') and sumscore
or ((aggregate == 'legs' or aggregate == 'sets') and wins)
or nil
end
if agg and aggregate ~= '' and score2 == nil and hassum then
score2 = (aggregate == 'score') and othersumscore
or ((aggregate == 'legs' or aggregate == 'sets') and otherwins)
or nil
end
if (score1 == nil or score1 == '') and (score2 == nil or score2 == '') then
if hideomittedscores > 0 and s >= hideomittedscores then
if hideomittedscores > 0 and s >= hideomittedscores then
teamcolspan = teamcolspan + 1
teamcolspan = teamcolspan + 1
showscore = false
showscore = false
end
end
else
hassum = true
end
end
if showscore then
if showscore then
if boldwinner ~= '' then
local winner = scoreCompare(score1, score2, boldwinner ~= 'low')
local winner = scoreCompare(score1, score2, boldwinner ~= 'low')
sumscore = sumScores(sumscore, score1)
if winner == 1 then
othersumscore = sumScores(othersumscore, score2)
fw = 'bold'
if winner == 1 then
wins = wins + 1
if boldwinner ~= '' or (agg and (aggregate == 'score' or aggregate == 'legs' or aggregate == 'sets')) then
elseif winner == 2 then
if agg and (aggregate == 'legs' or aggregate == 'sets') and (wins <= (legs[round] - 1)/2) then
otherwins = otherwins + 1
else
fw = 'bold'
end
end
end
if not (agg and (aggregate == 'score' or aggregate == 'legs' or aggregate == 'sets')) then wins = wins + 1 end
elseif winner == 2 then
if not (agg and (aggregate == 'score' or aggregate == 'legs' or aggregate == 'sets')) then otherwins = otherwins + 1 end
end
end
scoreCells[s] = row:tag('td')
scoreCells[s] = row:tag('td')
Line 351: Line 433:
teamCell:attr('colspan', teamcolspan)
teamCell:attr('colspan', teamcolspan)
end
end
if wins > otherwins then
if boldwinner ~= '' and wins > otherwins then
if seedCell then
if (aggregate == 'legs' or aggregate == 'sets') and (wins <= (legs[round] - 1)/2) then
seedCell:css('font-weight', 'bold')
else
end
if seedCell then
if teamCell then
seedCell:css('font-weight', 'bold')
teamCell:css('font-weight', 'bold')
end
if teamCell then
teamCell:css('font-weight', 'bold')
end
end
end
end
end
end
end


local function renderRound(rows, count, r)
local function renderRound(count, r)
local teams = math.pow(2, rounds - r + 1)
local teams = math.pow(2, rounds - r + 1)
local step = count / teams
local step = count / teams
Line 367: Line 452:
local topPair = true -- is top match-up in pair of match-ups
local topPair = true -- is top match-up in pair of match-ups
local team = 1
local team = 1
local group = 1


for i = 1, count, step do
for i = 1, count, step do
Line 374: Line 460:
local otherhideteam = false
local otherhideteam = false
local hideleftpath = false
local hideleftpath = false
local hiderightpath = false
if r <= byes then
if r <= byes then
hideteam = isHidden(r, team)
hideteam = isHidden(r, team)
Line 389: Line 476:
if compactFinal and (r == rounds) then
if compactFinal and (r == rounds) then
hideleftpath = true
hideleftpath = true
end
if (tonumber(args['RD' .. (r-1) .. '-RD' .. (r) .. '-path']) or 2) == 0 then
hideleftpath = true
end
if (tonumber(args['RD' .. (r) .. '-RD' .. (r + 1) .. '-path']) or 2) == 0 then
hiderightpath = true
end
end


Line 406: Line 499:
height = offset - offsetThird
height = offset - offsetThird
end
end
if compactFinal and (r == (rounds - 1)) then
if compactFinal and (r == (maxround - 1)) then
if team == 2 then
if team == 2 then
height = height - 3
height = height - 3
Line 413: Line 506:
height = height - 1
height = height - 1
offset = offset + 1
offset = offset + 1
rows[offset-3]:tag('td')
addBlank(offset-3, nil, 1, tonumber(hideSeeds and '2' or '3') + legs[r])
:attr('colspan', tonumber(hideSeeds and '2' or '3') + legs[r])
addBlank(offset-4)
rows[offset-4]:tag('td')
addHeading(rows[offset-4], r + 1, getRoundName(r+1), legs[r] - legs[r+1])
addHeading(rows[offset-4], r + 1, getRoundName(r+1))
local b = addBlank(offset-4, {
rows[offset-4]:tag('td')
['border-color'] = 'black',
:attr('rowspan', 2)
['border-style']= 'solid',
:css('border-color', 'black')
['border-width']= '0'}, 2)
:css('border-style', 'solid')
b:css('border-right-width', '2px')
:css('border-width', '0')
:css('border-right-width', '2px')
end
end
end
end
if height > 0 then
if height > 0 then
blank = addBlank(rows[offset])
local pad = 0
:attr('colspan', tonumber(hideSeeds and '3' or '4') + legs[r])
local betweenTeams = (topTeam == false and topPair == true) or (topTeam == true and topPair == false)
:attr('rowspan', height)
if compactFinal and (r == maxround - 1) then
:css('border-color', 'black')
betweenTeams = false
:css('border-style', 'solid')
end
:css('border-width', '0')
if compactFinal and (r == maxround - 1) and legs[r+1] > legs[r] then
pad = legs[r+1] - legs[r]
end
if compact and betweenTeams then
addBlank(offset, nil, height, 1)
if topPair then
blank = addBlank(offset, nil, 2*height, tonumber(hideSeeds and '1' or '2') + legs[r] + pad)
if args['RD' .. r .. '-group' .. group] then
blank:wikitext(args['RD' .. r .. '-group' .. group])
blank:css('text-align', 'center')
end
group = group + 1
end
blank = addBlank(offset,
{['border-width'] = '0',
['border-style'] = 'solid',
['border-color'] = 'black'},
height, 1)
else
blank = addBlank(offset,
{['border-width'] = '0',
['border-style'] = 'solid',
['border-color'] = 'black'},
height, tonumber(hideSeeds and '3' or '4') + legs[r] + pad)
end
end
end
-- add bracket
-- add bracket
local j = topTeam and i + step - (compact and 1 or 2) or i
local j = topTeam and i + step - (compact and 1 or 2) or i
-- add left path
-- add left path
addPath(rows, j, r, topTeam, true, hideleftpath and '0' or '2px')
addPath(j, r, topTeam, true, hideleftpath and '0' or '2px')
if hideteam then
if hideteam then
rows[j]:tag('td')
addBlank(j, nil, (not compact) and 2 or nil, tonumber(hideSeeds and '1' or '2') + legs[r])
:attr('colspan', tonumber(hideSeeds and '1' or '2') + legs[r])
elseif rows[j] then
:attr('rowspan', (not compact) and 2 or nil)
if compactFinal and (r == maxround) then
else
renderTeam(rows[j], r, team, topTeam, otherhideteam, legs[r-1] - legs[r])
renderTeam(rows[j], r, team, topTeam, otherhideteam)
elseif compactFinal and (r == maxround - 1) then
renderTeam(rows[j], r, team, topTeam, otherhideteam, legs[r+1] - legs[r])
else
renderTeam(rows[j], r, team, topTeam, otherhideteam)
end
end
end
local rightPath = addPath(rows, j, r, topTeam, false, hideteam and '0' or '2px')
local rightPath = addPath(j, r, topTeam, false, (hiderightpath or hideteam) and '0' or '2px')
if not topTeam then topPair = not topPair end
if not topTeam then topPair = not topPair end
if not topPair and r < rounds and (not hideteam) then
if not topPair and r < maxround and (not (hiderightpath or hideteam)) then
if blank then blank:css('border-right-width', '2px') end
if blank then blank:css('border-right-width', '2px') end
rightPath:css('border-right-width', '2px')
rightPath:css('border-right-width', '2px')
end
end
if compactFinal and (r == rounds) then
if compactFinal and (r == maxround) then
local prop = (team == 1) and 'border-bottom-width' or 'border-top-width'
local prop = (team == 1) and 'border-bottom-width' or 'border-top-width'
rightPath:css('border-right-width', '2px')
rightPath:css('border-right-width', '2px')
Line 460: Line 579:
end
end


local function renderGroups(rows, count, round)
local function renderGroups(count, round)
local roundFromLast = rounds - round + 1
local roundFromLast = rounds - round + 1
local groups = math.pow(2, roundFromLast - 2)
local groups = math.pow(2, roundFromLast - 2)
Line 467: Line 586:
local offset = 0
local offset = 0
local team = 0
local team = 0
local w = '2px'
local wdef = (tonumber(args['RD' .. (round) .. '-RD' .. (round + 1) .. '-path']) or 2) .. 'px'
local w = wdef


for r = 1,round do
for r = 1,round do
Line 474: Line 594:
for i = step / 2, count, step do
for i = step / 2, count, step do
local name = 'RD' .. round .. '-group' .. group
local name = 'RD' .. round .. '-group' .. group
addBlank(rows[i]):css('height', '7px')
addBlank(i, {['height'] = '7px'})
addBlank(rows[i + 1]):css('height', '7px')
addBlank(i+1, {['height'] = '7px'})
addBlank(rows[i])
addBlank(i, {['text-align'] = 'center'}, 2, offset-2)
:attr('rowspan', '2')
:attr('colspan', offset - 2)
:css('text-align', 'center')
:wikitext(args[name])
:wikitext(args[name])
:newline()
:newline()
if (round <= byes) then
if (round <= byes) then
team = i/(step/2)
team = i/(step/2)
w = isHidden(round, 2*team-1) and isHidden(round, 2*team) and '0' or '2px'
w = isHidden(round, 2*team-1) and isHidden(round, 2*team) and '0' or wdef
end
if (round < maxround) then
addBlank(i, {
['border-color'] = 'black',
['border-style'] = 'solid',
['border-width'] = '0 ' .. w .. ' 0 0'})
else
addBlank(i)
end
end
addBlank(rows[i])
:css('border-color', 'black')
:css('border-style', 'solid')
:css('border-width', '0 ' .. w .. ' 0 0')
if (round <= byes) then
if (round <= byes) then
team = team + 1
team = team + 1
w = isHidden(round, 2*team-1) and isHidden(round, 2*team) and '0' or '2px'
w = isHidden(round, 2*team-1) and isHidden(round, 2*team) and '0' or wdef
end
if (round < maxround) then
addBlank(i+1, {
['border-color'] = 'black',
['border-style'] = 'solid',
['border-width'] = '0 ' .. w .. ' 0 0'})
else
addBlank(i+1)
end
end
addBlank(rows[i+1])
:css('border-color', 'black')
:css('border-style', 'solid')
:css('border-width', '0 ' .. w ..' 0 0')
group = group + 1
group = group + 1
end
end
Line 509: Line 634:
offset = compact and 6 or 10
offset = compact and 6 or 10
if rounds < 2 then
if rounds < 2 then
offset = compact and 4 or 6
offset = compact and 4 or 7
end
end
end
end
Line 516: Line 641:
end
end


local function renderThird(rows, count)
local function renderThird(count)
local k = offsetThird
local k = offsetThird
local row = rows[k]
local row = rows[k]
local blank
local blank
--if (offsetThird < count) then
if rounds < 2 then
--blank = addBlank(row)
blank = addBlank(k-1, {['height'] = '7px'})
--blank:attr('colspan', tonumber(hideSeeds and '3' or '4') + legs[1])
end
--end
blank = addBlank(k, rounds < 2 and {['height'] = '7px'} or nil)
blank = addBlank(row)
addHeading(row, rounds + 1, args['3rd'] or 'Third place')
addHeading(row, rounds + 1, args['3rd'] or 'Third place')
if rounds < 2 then
for i = 1,(compact and 1 or 2) do
blank = addBlank(k+i, {['height'] = '7px'})
end
end
k = k + (compact and 2 or 3)
k = k + (compact and 2 or 3)
for i = 1,2 do
for i = 1,2 do
row = rows[k]
row = rows[k]
blank = addBlank(row)
blank = addBlank(k, rounds < 2 and {['height'] = '7px'} or nil)
renderTeam(row, rounds + 1, i, i == 1, false)
if row then
renderTeam(row, rounds + 1, i, i == 1, false)
end
if rounds < 2 and not compact then
blank = addBlank(k+1, {['height'] = '7px'})
end
k = k + (compact and 1 or 2)
k = k + (compact and 1 or 2)
end
end
local function maskRows(tbl, count, offsetcount)
local rmin = 1
local rmax = count
for i = rmin, rmax do
mask[i] = false
end
if showThird then
for i = offsetThird,(offsetThird+ (compact and 3 or 5)) do
rmax = (i > rmax) and i or rmax
mask[i] = true
end
end
for r = 1, maxround do
local teams = math.pow(2, rounds - r + 1)
local step = count / teams
local topTeam = true -- is top row in match-up
local team = 1
for i = 1, count, step do
local offset, height, blank
local hideteam = false
if r <= byes then
hideteam = isHidden(r, team)
end
if (r == 1) and (RD1seedmap[team] <= 0) then
hideteam = true
end
if not hideteam then
local j = topTeam and i + step - (compact and 1 or 2) or i
mask[j] = true
end
team = team + 1
topTeam = not topTeam
end
end
for r = 1, maxround do
local roundFromLast = rounds - r + 1
local groups = math.pow(2, roundFromLast - 2)
local step = count / groups
local group = 1
for i = step / 2, count, step do
if args['RD' .. r .. '-group' .. group] then
mask[i] = true
mask[i+1] = true
end
group = group + 1
end
end
local mmin, mmax = rmax, rmin
for i = rmin, rmax do
if mask[i] == true then
mmin = (i < mmin) and i or mmin
mmax = (i > mmax) and i or mmax
end
end
for i = mmin, mmax do
rows[i] = addTableRow(tbl)
end
end
end
end
Line 539: Line 734:
local count = math.pow(2, rounds) * (compact and 1 or 3)
local count = math.pow(2, rounds) * (compact and 1 or 3)
local offsetcount = 2 * (compact and 1 or 3) + (compact and 2 or 3)
local offsetcount = 2 * (compact and 1 or 3) + (compact and 2 or 3)
local rows = {}
offsetThird = getThirdOffset()
offsetThird = getThirdOffset()
for i = 1, count do
maskRows(tbl, count, offsetcount)
rows[i] = addTableRow(tbl)
end
if showThird then
if showThird then
for i = (count+1), (offsetcount + offsetThird) do
for i = (count+1), (offsetcount + offsetThird) do
rows[i] = addTableRow(tbl)
if (rounds > 1) then
if (rounds > 1) then
local blank = addBlank(rows[i])
local blank = addBlank(i, nil, 1, tonumber(hideSeeds and '3' or '4') + legs[1])
blank:attr('colspan', tonumber(hideSeeds and '3' or '4') + legs[1])
if compact and (rounds > 2) then
if compact and (rounds > 2) then
blank = addBlank(rows[i])
blank = addBlank(i, nil, 1, tonumber(hideSeeds and '3' or '4') + legs[2])
blank:attr('colspan', tonumber(hideSeeds and '3' or '4') + legs[1])
end
end
end
end
Line 560: Line 749:
-- fill rows with groups
-- fill rows with groups
for r = 1, rounds - 1 do
for r = 1, rounds - 1 do
renderGroups(rows, count, r)
renderGroups(count, r)
end
end
end
end
-- fill rows with bracket
-- fill rows with bracket
for r = 1, rounds do
for r = 1, maxround do
renderRound(rows, count, r)
renderRound(count, r)
end
end
if showThird then
if showThird then
renderThird(rows, count, compact)
renderThird(count, compact)
end
end
end
end
Line 575: Line 764:
local titleRow = addTableRow((not hideHeadings) and tbl or mw.html.create('table'))
local titleRow = addTableRow((not hideHeadings) and tbl or mw.html.create('table'))
local widthRow = addTableRow(tbl)
local widthRow = addTableRow(tbl)
for r = 1, (compactFinal and (rounds-1) or rounds) do
for r = 1, (compactFinal and (maxround-1) or maxround) do
addBlank(titleRow)
titleRow:tag('td')
addBlank(widthRow, r > 1 and '5px' or '1px')
widthRow:tag('td'):css('width', r > 1 and '5px' or '1px')
addHeading(titleRow, r, getRoundName(r) )
if compactFinal and r == (maxround-1) then
addHeading(titleRow, r, getRoundName(r), legs[r+1] - legs[r])
else
addHeading(titleRow, r, getRoundName(r) )
end
local seedCell
local seedCell
if (not hideSeeds) then
if (not hideSeeds) then
seedCell = addBlank(widthRow, getWidth('seed', '25px'))
seedCell = widthRow:tag('td'):css('width', getWidth('seed', '25px'))
end
end
local teamCell = addBlank(widthRow, getWidth('team', '150px'))
local teamCell = widthRow:tag('td'):css('width', getWidth('team', '150px'))
local scoreCells = {}
local scoreCells = {}
for s = 1, legs[r] do
local legsr = legs[r]
scoreCells[s] = addBlank(widthRow, getWidth('score', '25px'))
if compactFinal and r == (maxround-1) then
legsr = legs[r+1] > legs[r] and legs[r+1] or legs[r]
end
end
addBlank(titleRow)
for s = 1, legsr do
addBlank(widthRow, r < rounds and '5px' or '1px')
local score_width = '25px'
if aggregate and aggregate ~= '' and s > 1 and s == legsr then
score_width = getWidth('agg', getWidth('score', score_width))
else
score_width = getWidth('score', score_width)
end
scoreCells[s] = widthRow:tag('td'):css('width', score_width)
end
titleRow:tag('td')
widthRow:tag('td'):css('width', r < rounds and '5px' or '1px')


if compact then
if compact then
Line 608: Line 811:
parseArgs(frame)
parseArgs(frame)
rounds = tonumber(args.rounds) or 2
rounds = tonumber(args.rounds) or 2
maxround = tonumber(args.maxround) or rounds
local teams = math.pow(2, rounds)
local teams = math.pow(2, rounds)
compact = (args['compact'] and (args['compact'] == 'yes' or args['compact'] == 'y'))
compact = (args['compact'] and (args['compact'] == 'yes' or args['compact'] == 'y'))
compactFinal = ((rounds > 4) and compact and args['compact-final'] and (args['compact-final'] == 'yes' or args['compact-final'] == 'y'))
compactFinal = ((rounds > 4) and compact and args['compact-final'] and (args['compact-final'] == 'yes' or args['compact-final'] == 'y'))
sepwidth = tonumber(args['sepwidth'] or ((args.sets or args.legs) and 1) or (compact and 1) or 2) or 1
sepwidth = tonumber(args['sepwidth'] or ((args.sets or args.legs) and 1) or (compact and 1) or 2) or 1
aggsep = args['aggsep'] or nil
aggregate = (args['aggregate'] or ''):lower()
aggsep = args['aggsep'] or args['aggregate'] or nil
boldwinner = args['boldwinner'] or args['bold_winner'] or ''
boldwinner = args['boldwinner'] or args['bold_winner'] or ''
local autoSeeds = (args['autoseeds'] and (args['autoseeds'] == 'yes' or args['autoseeds'] == 'y'))
local autoSeeds = (args['autoseeds'] and (args['autoseeds'] == 'yes' or args['autoseeds'] == 'y'))
Line 669: Line 874:
renderHeadings(tbl)
renderHeadings(tbl)
renderTree(tbl)
renderTree(tbl)
if (args['wide'] and (args['wide'] == 'y' or args['wide'] == 'yes')) then
return '<div class="noresize" style="overflow:auto">' .. tostring(tbl) .. '</div>' .. tcats
end
return tostring(tbl) .. tcats
return tostring(tbl) .. tcats
end
end

Latest revision as of 04:04, 21 May 2023

Documentation for this module may be created at Module:Team bracket/doc

--
-- This module implements many bracket templates
--

local p = {}
local args = {}
local rows = {}
local mask = {}
local rounds
local maxround
local legs = {}
local compact
local byes
local hideSeeds
local showSeeds
local hideHeadings
local showThird
local offsetThird
local compactFinal
local sepwidth
local aggsep
local aggregate
local boldwinner
local hideomittedscores
local RD1seedmap = {}
local tcats = ''

local function isnotblank(s)
	return s and s ~= ''
end

local function isblank(s)
	return (not s) or (s == '')
end

local function sumScores(s1, s2)
	s1 = mw.ustring.gsub(s1 or '', '^[\'%s]*([%d%.]*).-$', '%1')
	s2 = mw.ustring.gsub(s2 or '', '^[\'%s]*([%d%.]*).-$', '%1')
	if s1 ~= '' and s2 ~= '' then
		return tonumber(s1) + tonumber(s2)
	end
	return s1
end

local function scoreCompare(s1,s2,highwin)
	local ps1 = mw.ustring.gsub(s1 or '', '^[\'%s]*([%d%.]*)[\'%s]*%([\'%s]*([%d%.]*)[\'%s]*%).-$', '%2')
	local ps2 = mw.ustring.gsub(s2 or '', '^[\'%s]*([%d%.]*)[\'%s]*%([\'%s]*([%d%.]*)[\'%s]*%).-$', '%2')
	s1 = mw.ustring.gsub(s1 or '', '^[\'%s]*([%d%.]*).-$', '%1')
	s2 = mw.ustring.gsub(s2 or '', '^[\'%s]*([%d%.]*).-$', '%1')
	
	if s1 ~= '' and s2 ~= '' then
		s1 = tonumber(s1)
		s2 = tonumber(s2)
		if s1 and s2 then
			if (s1 == s2) then
				ps1 = tonumber(ps1)
				ps2 = tonumber(ps2)
				if ps1 and ps2 then
					s1 = ps1
					s2 = ps2
				end
			end
			if highwin then
				return ((s1 > s2) and 1) or ((s1 < s2) and 2) or 0
			else
				return ((s2 > s1) and 1) or ((s2 < s1) and 2) or 0
			end
		end
	end
	return 0
end

local function unboldParenthetical(s)
	if s then
		s = mw.ustring.gsub(s, '(%(%[%[[^%[%]]*%]%]%))', '<span style="font-weight:normal">%1</span>')
	end
	return s
end

local function parseArgs(frame)
	local fargs = frame.args
	local pargs = frame:getParent().args;

	local r = tonumber(fargs.rounds or '') or tonumber(pargs.rounds or '') or 2
	local teams = math.pow(2, r)
	local rdstr = 'RD' .. tostring(r)
	local rdbstr = 'RD' .. tostring(r) .. 'b'
	local rdp1str = 'RD' .. tostring(r+1)

	for i=1,2 do
		local targs = (i == 1) and pargs or fargs
		for k,v in pairs(targs) do
			if type(k) == 'string' then
				if k:find('^[R3][Dr][d1-9]b?%-[a-z][a-z]*00*') then
					k = mw.ustring.gsub(k, '^([R3][Dr][d1-9]b?%-[a-z][a-z]*)00*', '%1')
					if (teams < 10) then 
						tcats = tcats .. '[[Category:Pages using a team bracket with deprecated syntax|P]]'
					end
				end
				if k:find('^' .. rdp1str) then
					k = mw.ustring.gsub(k, '^' .. rdp1str, '3rd')
					tcats = tcats .. '[[Category:Pages using a team bracket with deprecated syntax|3]]'
				elseif k:find('^' .. rdbstr) then
					k = mw.ustring.gsub(k, '^' .. rdbstr, '3rd')
				elseif k:find('^' .. rdstr .. '%-[a-z][a-z]*3') then
					k = mw.ustring.gsub(k, '^' .. rdstr .. '(%-[a-z][a-z]*)3', '3rd%11')
				elseif k:find('^' .. rdstr .. '%-[a-z][a-z]*4') then
					k = mw.ustring.gsub(k, '^' .. rdstr .. '(%-[a-z][a-z]*)4', '3rd%12')
				elseif  k:find('^Consol') then
					k = mw.ustring.gsub(k, '^Consol', '3rd')
					tcats = tcats .. '[[Category:Pages using a team bracket with deprecated syntax|3]]'
				elseif k:find('^group[0-9]') then
					tcats = tcats .. '[[Category:Pages using a team bracket with deprecated syntax|G]]'
				end
			end
			args[k] = v
		end
	end

	if (args['byes'] and (args['byes'] == 'yes' or args['byes'] == 'y')) then
		tcats = tcats .. '[[Category:Pages using a team bracket with deprecated syntax|B]]'
	end
end

local function parseSeedmap(s)
	s = mw.text.split((s or '0') .. '/', '[%s]*/[%s]*')
	local teams = math.pow(2, rounds)
	for r=1,teams do
		RD1seedmap[r] = 1
	end
	for r=1,#s do
		if tonumber(s[r] or 'x') then
			RD1seedmap[tonumber(s[r])] = 0
		end
	end
	local c = 1
	for r=1,teams do
		if RD1seedmap[r] > 0 then
			RD1seedmap[r] = c
			c = c + 1
		end
	end
end

local function parseLegs(s)
	s = mw.text.split((s or '1') .. '/', '[%s]*/[%s]*')
	if aggregate == 'n' or aggregate == 'no' or aggregate == '0' then
		aggregate = ''
	end
	local n = showThird and (rounds + 1) or (rounds)
	local lastlegs = nil
	for r=1,n do
		if s[r] ~= nil and s[r] ~= '' and tonumber(s[r]) then
			legs[r] = tonumber(s[r])
		elseif lastlegs then
			legs[r] = lastlegs
		else
			legs[r] = 1
		end
		lastlegs = legs[r]
		if legs[r] > 1 and aggregate ~= '' then
			legs[r] = legs[r] + 1
		end
	end
end

local function getSeeds()
	local seeds = {1, 2}
	local count = 2
	local before = false
	for r = 2, rounds do
		local max = math.pow(2, r)
		for i = 1, count do
			local pos = i * 2
			if before then pos = pos - 1 end
			table.insert(seeds, pos, max - seeds[i * 2 - 1] + 1)
			before = not before
		end
		count = count * 2
	end
	return seeds
end

local function addTableRow(tbl)
	return tbl:tag('tr')
end

local function addBlank(i, css, rowspan, colspan)
	local row = rows[i]
	rowspan = rowspan or 1
	local jmax = i + rowspan - 1
	for j = i, jmax do
		if rows[j] == nil then
			rowspan = rowspan - 1
		elseif row == nil then
			row = rows[j]
		end
	end
	local cell = row and row:tag('td') or mw.html.create('td')
	if rowspan and rowspan > 1 then
		cell:attr('rowspan', rowspan)
	end
	if colspan and colspan > 1 then
		cell:attr('colspan', colspan)
	end
	if css then
		cell:css(css)
	end
	return cell
end

local function addBorders(cell, topcell, seedorteam, extrasep)
	if sepwidth > 1 then topcell = true end
	if seedorteam then
		cell:css('border', '1px solid #aaa')
			:css('border-top-width', topcell and '1px' or '0')
	else
		cell:css('border-color', '#aaa')
			:css('border-style', 'solid')
			:css('border-top-width', topcell and '1px' or '0')
			:css('border-left-width', (extrasep and '1px') or ((sepwidth > 1) and '1px') or '0')
			:css('border-right-width', '1px')
			:css('border-bottom-width', '1px')
	end
end

local function addHeading(row, r, text, pad)
	pad = (pad == nil or pad < 0) and 0 or pad
	row:tag('td')
		:attr('colspan', tonumber(hideSeeds and '1' or '2') + legs[r] + pad)
		:css('text-align', 'center')
		:css('border', '1px solid #aaa')
		:css('background-color', args['RD-shade'] or '#f2f2f2')
		:wikitext(text)
		:newline()
end

local function getWidth(param, default)
	local arg = args[param .. '-width']
	if isblank(arg) then
		arg = default
	end
	if tonumber(arg) ~= nil then
		arg = arg .. 'px'
	end
	return arg
end

local function getTeamArgName(round, type, team)
	if round > rounds then
		return string.format('3rd-%s%d', type, team)
	else
		if (round == 1) then
			team = RD1seedmap[team]
			if team == 0 then
				return 'NIL'
			end
		end
		return string.format('RD%d-%s%d', round, type, team)
	end
end

local function getShadeArg(round, team, s)
	local argname = getTeamArgName(round, 'shade', team) .. (s and ('-' .. s) or '')
	local value = args[argname]
	if isblank(value) then
		return '#f9f9f9'
	end
	return value
end

local function getScoreArg(round, team, s)
	local argname = getTeamArgName(round, 'score', team) .. (s and ('-' .. s) or '')
	local value = args[argname]
	return value
end

local function getTeamArg(round, type, team)
	local argname = getTeamArgName(round, type, team)
	local value = args[argname]
	if isblank(value) then
		return ''
	end
	if mw.ustring.find(value, '[%s]*<[%s/]*[Bb][Rr][%s/]*>[%s ]*&[Nn][Bb][Ss][Pp];[%s]*') then
		tcats = tcats .. '[[Category:Pages using a team bracket with nbsp]]'
	end
	return mw.ustring.gsub(value, '[%s]*<[%s/]*[Bb][Rr][%s/]*>[%s ]*&[Nn][Bb][Ss][Pp];[%s]*', '<br/>')
end

local function isHidden(r, team)
	return isblank( getTeamArg(r, 'team', team) )
end

local function getRoundName(round)
	local name = args['RD' .. round]
	if isnotblank(name) then
		return name
	end
	local roundFromLast = rounds - round + 1
	if roundFromLast == 1 then
		return "Finals"
	elseif roundFromLast == 2 then
		return "Semifinals"
	elseif roundFromLast == 3 then
		return "Quarterfinals"
	else
		return "Round of " .. math.pow(2, roundFromLast)
	end
end

local function addPath(index, round, top, left, w)
	local prop = top and 'border-bottom-width' or 'border-top-width'
	if left and round == 1 then
		if compact then
			addBlank(index)
		else
			addBlank(index, {['height'] = '7px'})
			addBlank(index+1, {['height'] = '7px'})
		end
		return nil
	else
		local cell = addBlank(index, 
			{['border-width'] = '0',
			['border-style'] = 'solid',
			['border-color'] = 'black'}, (not compact) and 2 or 1)
		if left or round < maxround and not left then
			cell:css(prop, w or '2px')
		end
		return cell
	end
end

local function renderTeam(row, round, team, top, otherbye, pad)
	pad = (pad == nil or pad < 0) and 0 or pad
	tcs = pad + 1
	local seedCell
	local seedArg = getTeamArg(round, 'seed', team)
	-- seed value for the paired team
	local otherteam = team % 2 == 0 and team-1 or team+1
	local pairSeedArg = otherbye and '' 
		or getTeamArg(round, 'seed', otherteam)
	-- show seed if seed is defined for either or both
	local showSeed = showSeeds
		or isnotblank(seedArg)
		or isnotblank(pairSeedArg)
	if showSeed and (not hideSeeds) then
		seedCell = row:tag('td')
			:css('text-align', 'center')
			:css('background-color', '#f2f2f2')
			:attr('rowspan', (not compact) and '2' or nil)
			:wikitext(seedArg)
			:newline()
		addBorders(seedCell, top or otherbye, true, false)
	end

	local teamArg = getTeamArg(round, 'team', team)
	if isblank(teamArg) then
		teamArg = '&nbsp;'
	elseif boldwinner ~= '' then
		teamArg = unboldParenthetical(teamArg)
	end
	
	if not showSeed and (not hideSeeds) then
		tcs = tcs + 1
	end

	local teamCell = row:tag('td')
		:css('background-color', '#f9f9f9')
		:css('padding', '0 2px')
		:attr('rowspan', (not compact) and '2' or nil)
		:attr('colspan', (tcs > 1) and tcs or nil)
		:wikitext(teamArg)
		:newline()
	addBorders(teamCell, top or otherbye, true, false)

	local scoreCells = {}
	local wins, otherwins = 0, 0
	local sumscore, othersumscore = 0, 0
	local teamcolspan = tcs
	local hassum = false
	for s = 1, legs[round] do
		local fw = nil
		local agg = legs[round] > 1 and s == legs[round] and true or false
		local score1 = (agg and getScoreArg(round, team, 'agg') or nil) or 
			getScoreArg(round, team, s) or ((legs[round] == 1) and getScoreArg(round, team)) or nil
		local score2 = (agg and getScoreArg(round, otherteam, 'agg') or nil) or 
			getScoreArg(round, otherteam, s) or ((legs[round] == 1) and getScoreArg(round, otherteam)) or nil
		local showscore = true
		if agg and aggregate ~= '' and score1 == nil and hassum then
			score1 = (aggregate == 'score')	and sumscore 
				or ((aggregate == 'legs' or aggregate == 'sets') and wins)
				or nil
		end
		if agg and aggregate ~= '' and score2 == nil and hassum then
			score2 = (aggregate == 'score')	and othersumscore
				or ((aggregate == 'legs' or aggregate == 'sets') and otherwins)
				or nil
		end
		if (score1 == nil or score1 == '') and (score2 == nil or score2 == '') then
			if hideomittedscores > 0 and s >= hideomittedscores then
				teamcolspan = teamcolspan + 1
				showscore = false
			end
		else
			hassum = true
		end
		if showscore then
			local winner = scoreCompare(score1, score2, boldwinner ~= 'low')
			sumscore = sumScores(sumscore, score1)
			othersumscore = sumScores(othersumscore, score2)
			if winner == 1 then
				if boldwinner ~= '' or (agg and (aggregate == 'score' or aggregate == 'legs' or aggregate == 'sets')) then 
					if agg and (aggregate == 'legs' or aggregate == 'sets') and (wins <= (legs[round] - 1)/2) then
					else
						fw = 'bold'
					end
				end
				if not (agg and (aggregate == 'score' or aggregate == 'legs' or aggregate == 'sets')) then wins = wins + 1 end
			elseif winner == 2 then
				if not (agg and (aggregate == 'score' or aggregate == 'legs' or aggregate == 'sets')) then otherwins = otherwins + 1 end
			end
			scoreCells[s] = row:tag('td')
				:css('text-align', 'center')
				:css('background-color', getShadeArg(round, team, s))
				:css('font-weight', fw)
				:attr('rowspan', (not compact) and '2' or nil)
				:wikitext(score1)
				:newline()
			addBorders(scoreCells[s], top or otherbye, false, s > 1 and s == legs[round] and aggsep or nil)
		end
	end
	if teamcolspan > 1 then
		teamCell:attr('colspan', teamcolspan)
	end
	if boldwinner ~= '' and wins > otherwins then
		if (aggregate == 'legs' or aggregate == 'sets') and (wins <= (legs[round] - 1)/2) then
		else
			if seedCell then
				seedCell:css('font-weight', 'bold')
			end
			if teamCell then
				teamCell:css('font-weight', 'bold')
			end
		end
	end
end

local function renderRound(count, r)
	local teams = math.pow(2, rounds - r + 1)
	local step = count / teams
	local topTeam = true -- is top row in match-up
	local topPair = true -- is top match-up in pair of match-ups
	local team = 1
	local group = 1

	for i = 1, count, step do
		local offset, height, blank

		local hideteam = false
		local otherhideteam = false
		local hideleftpath = false
		local hiderightpath = false
		if r <= byes then
			hideteam = isHidden(r, team)
			otherhideteam = isHidden(r, team % 2 == 0 and team-1 or team+1)
		end
		if (r == 1) and (RD1seedmap[team] <= 0) then
				hideteam = true
		end
		if (r > 1) and (r <= (byes + 1)) then
			hideleftpath = isHidden(r-1, 2*team-1) and isHidden(r-1, 2*team)
		end
		if (r == 2) and (RD1seedmap[2*team-1] <= 0 and RD1seedmap[2*team] <= 0) then
			hideleftpath = true
		end
		if compactFinal and (r == rounds) then
			hideleftpath = true
		end
		if (tonumber(args['RD' .. (r-1) .. '-RD' .. (r) .. '-path']) or 2) == 0 then
			hideleftpath = true
		end
		if (tonumber(args['RD' .. (r) .. '-RD' .. (r + 1) .. '-path']) or 2) == 0 then
			hiderightpath = true
		end

		-- empty space above or below
		if compact then
			offset = topTeam and i or i + 1
			height = step - 1
		-- leave room for groups for teams other than first and last
		elseif team == 1 or team == teams then
			offset = topTeam and i or i + 2
			height = step - 2
		else
			offset = topTeam and i + 1 or i + 2
			height = step - 3
		end
		if showThird and (r == rounds) and (not topTeam) then
			height = offset - offsetThird
		end
		if compactFinal and (r == (maxround - 1)) then
			if team == 2 then
				height = height - 3
			end
			if team == 3 then
				height = height - 1
				offset = offset + 1
				addBlank(offset-3, nil, 1, tonumber(hideSeeds and '2' or '3') + legs[r])
				addBlank(offset-4)
				addHeading(rows[offset-4], r + 1, getRoundName(r+1), legs[r] - legs[r+1])
				local b = addBlank(offset-4, {
					['border-color'] = 'black',
					['border-style']= 'solid',
					['border-width']= '0'}, 2)
				b:css('border-right-width', '2px')
			end
		end
		if height > 0 then
			local pad = 0
			local betweenTeams = (topTeam == false and topPair == true) or (topTeam == true and topPair == false)
			if compactFinal and (r == maxround - 1) then
				betweenTeams = false
			end
			if compactFinal and (r == maxround - 1) and legs[r+1] > legs[r] then
				pad = legs[r+1] - legs[r]
			end
			if compact and betweenTeams then
				addBlank(offset, nil, height, 1)
				if topPair then
					blank = addBlank(offset, nil, 2*height, tonumber(hideSeeds and '1' or '2') + legs[r] + pad)
					if args['RD' .. r .. '-group' .. group] then
						blank:wikitext(args['RD' .. r .. '-group' .. group])
						blank:css('text-align', 'center')
					end
					group = group + 1
				end
				blank = addBlank(offset, 
				{['border-width'] = '0',
				['border-style'] = 'solid',
				['border-color'] = 'black'},
				height, 1)
			else
				blank = addBlank(offset, 
				{['border-width'] = '0',
				['border-style'] = 'solid',
				['border-color'] = 'black'},
				height, tonumber(hideSeeds and '3' or '4') + legs[r] + pad)
			end
		end
		-- add bracket
		local j = topTeam and i + step - (compact and 1 or 2) or i
		-- add left path
		addPath(j, r, topTeam, true, hideleftpath and '0' or '2px')
		if hideteam then
			addBlank(j, nil, (not compact) and 2 or nil, tonumber(hideSeeds and '1' or '2') + legs[r])
		elseif rows[j] then
			if compactFinal and (r == maxround) then
				renderTeam(rows[j], r, team, topTeam, otherhideteam, legs[r-1] - legs[r])
			elseif compactFinal and (r == maxround - 1) then
				renderTeam(rows[j], r, team, topTeam, otherhideteam, legs[r+1] - legs[r])
			else
				renderTeam(rows[j], r, team, topTeam, otherhideteam)
			end
		end
		local rightPath = addPath(j, r, topTeam, false, (hiderightpath or hideteam) and '0' or '2px')
		if not topTeam then topPair = not topPair end
		if not topPair and r < maxround and (not (hiderightpath or hideteam)) then
			if blank then blank:css('border-right-width', '2px') end
			rightPath:css('border-right-width', '2px')
		end
		if compactFinal and (r == maxround) then
			local prop = (team == 1) and 'border-bottom-width' or 'border-top-width'
			rightPath:css('border-right-width', '2px')
				:css(prop, '2px')
		end
		team = team + 1
		topTeam = not topTeam
	end
end

local function renderGroups(count, round)
	local roundFromLast = rounds - round + 1
	local groups = math.pow(2, roundFromLast - 2)
	local step = count / groups
	local group = 1
	local offset = 0
	local team = 0
	local wdef = (tonumber(args['RD' .. (round) .. '-RD' .. (round + 1) .. '-path']) or 2) .. 'px'
	local w = wdef

	for r = 1,round do
		offset = offset + (hideSeeds and 3 or 4) + legs[r]
	end
	for i = step / 2, count, step do
		local name = 'RD' .. round .. '-group' .. group
		addBlank(i, {['height'] = '7px'})
		addBlank(i+1, {['height'] = '7px'})
		addBlank(i, {['text-align'] = 'center'}, 2, offset-2)
			:wikitext(args[name])
			:newline()
		if (round <= byes) then
			team = i/(step/2)
			w = isHidden(round, 2*team-1) and isHidden(round, 2*team) and '0' or wdef
		end
		if (round < maxround) then
			addBlank(i, {
				['border-color'] = 'black',
				['border-style'] = 'solid', 
				['border-width'] = '0 ' .. w .. ' 0 0'})
		else
			addBlank(i)
		end
		if (round <= byes) then
			team = team + 1
			w = isHidden(round, 2*team-1) and isHidden(round, 2*team) and '0' or wdef
		end
		if (round < maxround) then
			addBlank(i+1, {
				['border-color'] = 'black',
				['border-style'] = 'solid', 
				['border-width'] = '0 ' .. w .. ' 0 0'})
		else
			addBlank(i+1)
		end
		group = group + 1
	end
end

local function getThirdOffset()
	local offset = (compact and 1 or 3) * (math.pow(2, rounds) - math.pow(2, rounds-3)) - (compact and 2 or 4)
	if rounds < 4 then
		offset = compact and 8 or 17
		if rounds < 3 then
			offset = compact and 6 or 10
			if rounds < 2 then
				offset = compact and 4 or 7
			end
		end
	end
	return offset
end

local function renderThird(count)
	local k = offsetThird
	local row = rows[k]
	local blank
	if rounds < 2 then
		blank = addBlank(k-1, {['height'] = '7px'})
	end
	blank = addBlank(k, rounds < 2 and {['height'] = '7px'} or nil)
	addHeading(row, rounds + 1, args['3rd'] or 'Third place')
	if rounds < 2 then
		for i = 1,(compact and 1 or 2) do
			blank = addBlank(k+i, {['height'] = '7px'})
		end
	end
	k = k + (compact and 2 or 3)
	for i = 1,2 do
		row = rows[k]
		blank = addBlank(k, rounds < 2 and {['height'] = '7px'} or nil)
		if row then
			renderTeam(row, rounds + 1, i, i == 1, false)
		end
		if rounds < 2 and not compact then
			blank = addBlank(k+1, {['height'] = '7px'})
		end
		k = k + (compact and 1 or 2)
	end
end

local function maskRows(tbl, count, offsetcount)
	local rmin = 1
	local rmax = count
	for i = rmin, rmax do
		mask[i] = false
	end
	if showThird then
		for i = offsetThird,(offsetThird+ (compact and 3 or 5)) do
			rmax = (i > rmax) and i or rmax
			mask[i] = true
		end
	end
	for r = 1, maxround do
		local teams = math.pow(2, rounds - r + 1)
		local step = count / teams
		local topTeam = true -- is top row in match-up
		local team = 1

		for i = 1, count, step do
			local offset, height, blank
			local hideteam = false
			if r <= byes then
				hideteam = isHidden(r, team)
			end
			if (r == 1) and (RD1seedmap[team] <= 0) then
				hideteam = true
			end
			if not hideteam then
				local j = topTeam and i + step - (compact and 1 or 2) or i
				mask[j] = true
			end
			team = team + 1
			topTeam = not topTeam
		end
	end
	
	for r = 1, maxround do
		local roundFromLast = rounds - r + 1
		local groups = math.pow(2, roundFromLast - 2)
		local step = count / groups
		local group = 1
		for i = step / 2, count, step do
			if args['RD' .. r .. '-group' .. group] then
				mask[i] = true
				mask[i+1] = true
			end
			group = group + 1
		end
	end
	local mmin, mmax = rmax, rmin
	for i = rmin, rmax do
		if mask[i] == true then
			mmin = (i < mmin) and i or mmin
			mmax = (i > mmax) and i or mmax
		end
	end
	for i = mmin, mmax do
		rows[i] = addTableRow(tbl)
	end
end

local function renderTree(tbl)
	-- create 3 or 1 rows for every team
	local count = math.pow(2, rounds) * (compact and 1 or 3)
	local offsetcount = 2 * (compact and 1 or 3) + (compact and 2 or 3)
	offsetThird = getThirdOffset()
	maskRows(tbl, count, offsetcount)
	if showThird then
		for i = (count+1), (offsetcount + offsetThird) do
			if (rounds > 1) then
				local blank = addBlank(i, nil, 1, tonumber(hideSeeds and '3' or '4') + legs[1])
				if compact and (rounds > 2) then
					blank = addBlank(i, nil, 1, tonumber(hideSeeds and '3' or '4') + legs[2])
				end
			end
		end
	end
	if not compact then
		-- fill rows with groups
		for r = 1, rounds - 1 do
			renderGroups(count, r)
		end
	end
	-- fill rows with bracket
	for r = 1, maxround do
		renderRound(count, r)
	end
	if showThird then
		renderThird(count, compact)
	end
end

local function renderHeadings(tbl)
	local titleRow = addTableRow((not hideHeadings) and tbl or mw.html.create('table'))
	local widthRow = addTableRow(tbl)
	for r = 1, (compactFinal and (maxround-1) or maxround) do
		titleRow:tag('td')
		widthRow:tag('td'):css('width', r > 1 and '5px' or '1px')
		if compactFinal and r == (maxround-1) then
			addHeading(titleRow, r, getRoundName(r), legs[r+1] - legs[r])
		else
			addHeading(titleRow, r, getRoundName(r) )
		end
		local seedCell
		if (not hideSeeds) then
			seedCell = widthRow:tag('td'):css('width', getWidth('seed', '25px'))
		end
		local teamCell = widthRow:tag('td'):css('width', getWidth('team', '150px'))
		local scoreCells = {}
		local legsr = legs[r]
		if compactFinal and r == (maxround-1) then
			legsr = legs[r+1] > legs[r] and legs[r+1] or legs[r]
		end
		for s = 1, legsr do
			local score_width = '25px'
			if aggregate and aggregate ~= '' and s > 1 and s == legsr then
				score_width = getWidth('agg', getWidth('score', score_width))
			else
				score_width = getWidth('score', score_width)
			end
			scoreCells[s] = widthRow:tag('td'):css('width', score_width)
		end
		titleRow:tag('td')
		widthRow:tag('td'):css('width', r < rounds and '5px' or '1px')

		if compact then
			teamCell:css('height', '7px')
		else
			if seedCell then
				seedCell:wikitext('&nbsp;')
			end
			teamCell:wikitext('&nbsp;')
			for s = 1, legs[r] do
				scoreCells[s]:wikitext('&nbsp;')
			end
		end
	end
end

function p.main(frame)
	parseArgs(frame)
	rounds = tonumber(args.rounds) or 2
	maxround = tonumber(args.maxround) or rounds
	local teams = math.pow(2, rounds)
	compact = (args['compact'] and (args['compact'] == 'yes' or args['compact'] == 'y'))
	compactFinal = ((rounds > 4) and compact and args['compact-final'] and (args['compact-final'] == 'yes' or args['compact-final'] == 'y'))
	sepwidth = tonumber(args['sepwidth'] or ((args.sets or args.legs) and 1) or (compact and 1) or 2) or 1
	aggregate = (args['aggregate'] or ''):lower()
	aggsep = args['aggsep'] or args['aggregate'] or nil
	boldwinner = args['boldwinner'] or args['bold_winner'] or ''
	local autoSeeds = (args['autoseeds'] and (args['autoseeds'] == 'yes' or args['autoseeds'] == 'y'))
	hideSeeds = (args['seeds'] and (args['seeds'] == 'no' or args['seeds'] == 'n'))
	showSeeds = (args['seeds'] and (args['seeds'] == 'yes' or args['seeds'] == 'y'))
	byes = (args['byes'] and (args['byes'] == 'yes' or args['byes'] == 'y') and 1) or (tonumber(args['byes'] or '0') or 0)
	hideomittedscores = (args['hideomittedscores'] and (args['hideomittedscores'] == 'yes' or args['hideomittedscores'] == 'y') and 1) or (tonumber(args['hideomittedscores'] or '0') or 0)
	hideHeadings = (args['headings'] and (args['headings'] == 'no' or args['headings'] == 'n'))
	showThird = isnotblank(args['3rd']) or isnotblank(args['3rd-team1']) or isnotblank(args['3rd-team2'])
	local align = (args['float'] or args['align'] or ''):lower()
	local clear = args['clear'] or 'none'
	parseSeedmap(args['RD1-omit'])
	parseLegs(args.sets or args.legs)
	
	if autoSeeds then
		-- set default seeds for round 1
		local seeds = getSeeds()
		for i = 1, table.getn(seeds) do
			local argname = getTeamArgName(1, 'seed', i)
			if not args[argname] then
				args[argname] = seeds[i]
			end
		end
	end

	-- create the table
	local tbl = mw.html.create('table')
		:css('border-style', 'none')
		:css('font-size', '90%')
		:css('border-collapse', 'separate')
		:css('border-spacing', '0')
		:attr('cellpadding', '0')

	if (args['nowrap'] and (args['nowrap'] == 'yes' or args['nowrap'] == 'y')) then
		tbl:css('white-space', 'nowrap')
	end
	
	if align == 'right' then
		tbl:css('float', 'right')
		if clear ~= 'none' and clear ~= 'no' and clear ~= 'n' then
			tbl:css('clear', 'right')
		end
		tbl:css('margin', '1em 0 1em 2em')
	elseif align == 'left' then
		tbl:css('float', 'left')
		if clear ~= 'none' and clear ~= 'no' and clear ~= 'n' then
			tbl:css('clear', 'left')
		end
		tbl:css('margin', '1em 2em 1em 0')
	elseif align == 'center' or align == 'centre' then
		tbl:css('margin', '1em auto')
	else
		tbl:css('margin', '1em 2em 1em 1em')
	end

	renderHeadings(tbl)
	renderTree(tbl)
	if (args['wide'] and (args['wide'] == 'y' or args['wide'] == 'yes')) then
		return '<div class="noresize" style="overflow:auto">' .. tostring(tbl) .. '</div>' .. tcats
	end
	return tostring(tbl) .. tcats
end

function p.teamBracket(frame)
	return p.main(frame)
end

return p