Difference between revisions of "Module:TeamBracket-Tennis"

From blackwiki
Jump to navigation Jump to search
blackwiki>Nakon
m (Protected Module:TeamBracket-Compact-Tennis: High-risk Lua module ([Edit=Allow only template editors and admins] (indefinite) [Move=Allow only template editors and admins] (indefinite)))
blackwiki>Frietjes
(starting a modified version of Module:TeamBracket for tennis, most likely has bugs)
Line 1: Line 1:
--
 
-- This module will implement {{16TeamBracket-Compact-Tennis5}}
 
 
--
 
--
+
-- This module will implement {{8TeamBracket-Tennis5}}
 +
--
 +
 
 
local p = {}
 
local p = {}
 
local args
 
local args
Line 8: Line 8:
 
local sets
 
local sets
 
local padding
 
local padding
 +
local showSeeds
  
 
local function getArgs(frame)
 
local function getArgs(frame)
 
local parent = frame:getParent();
 
local parent = frame:getParent();
 
local args = parent.args;
 
local args = parent.args;
for k,v in pairs(frame.args) do  
+
for k,v in pairs(frame.args) do
 
args[k] = v
 
args[k] = v
 
end
 
end
return parent.args;
+
return args;
 +
end
 +
 
 +
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
 
end
  
Line 24: Line 42:
 
function addBlank(row, width)
 
function addBlank(row, width)
 
local cell = row:tag('td')
 
local cell = row:tag('td')
:css('border-width', '0')
 
:css('border-style', 'solid')
 
:css('border-color', '#aaa')
 
 
if width then
 
if width then
 
cell:css('width', width)
 
cell:css('width', width)
Line 35: Line 50:
 
function addPath(rows, index, round, top, left)
 
function addPath(rows, index, round, top, left)
 
local prop = top and 'border-bottom-width' or 'border-top-width'
 
local prop = top and 'border-bottom-width' or 'border-top-width'
local cell = addBlank(rows[index]):css('border-color', 'black')
 
 
if left and round == 1 then
 
if left and round == 1 then
 +
addBlank(rows[index]):css('height', '7px')
 +
addBlank(rows[index + 1]):css('height', '7px')
 +
return nil
 +
else
 +
local cell = addBlank(rows[index])
 +
:attr('rowspan', '2')
 +
:css('border-width', '0')
 +
:css('border-style', 'solid')
 +
:css('border-color', 'black')
 +
if left or round < rounds and not left then
 +
cell:css(prop, '2px')
 +
end
 +
return cell
 +
end
 +
end
 +
 +
function addCompactPath(rows, index, round, top, left)
 +
local prop = top and 'border-bottom-width' or 'border-top-width'
 +
if left and round == 1 then
 +
addBlank(rows[index])
 
return nil
 
return nil
 
else
 
else
 +
local cell = addBlank(rows[index])
 +
:css('border-width', '0')
 +
:css('border-style', 'solid')
 +
:css('border-color', 'black')
 
if left or round < rounds and not left then
 
if left or round < rounds and not left then
 
cell:css(prop, '2px')
 
cell:css(prop, '2px')
Line 58: Line 96:
  
 
function getTeamArg(round, type, team)
 
function getTeamArg(round, type, team)
local argname = string.format('RD%d-%s' .. padding, round, type, team)
+
local argname = getTeamArgName(round, type, team)
 
local value = args[argname]
 
local value = args[argname]
 
if not value or string.len(value) == 0 then
 
if not value or string.len(value) == 0 then
Line 66: Line 104:
 
end
 
end
  
function getScore(round, team, set)
+
function getTeamArgName(round, type, team)
local argname = string.format('RD%d-score' .. padding ..'-%d', round, team, set)
+
return string.format('RD%d-%s' .. padding, round, type, team)
return args[argname]
 
 
end
 
end
  
Line 87: Line 124:
 
end
 
end
 
end
 
end
+
 
function renderTeam(row, round, team, double)
+
function renderTeam(row, round, team, top, compact)
 +
local seedCell
 
local seedArg = getTeamArg(round, 'seed', team)
 
local seedArg = getTeamArg(round, 'seed', team)
local seedCell = addBlank(row)
+
-- seed value for the paired team
:css('text-align', 'center')
+
local pairSeedArg = getTeamArg(round, 'seed',
:css('background-color', '#f2f2f2')
+
team % 2 == 0 and team - 1 or team + 1)
:css('border-width', '1px')
+
-- show seed if seed is defined for either or both
:css('border-right-width', '2px')
+
local showSeed = seedArg and string.len(seedArg) > 0
:css('border-bottom-width', double and '2px' or '1px')
+
or pairSeedArg and string.len(pairSeedArg) > 0
:wikitext(seedArg)
+
if showSeed and showSeeds then
:newline()
+
seedCell = row:tag('td')
 +
:css('text-align', 'center')
 +
:css('background-color', '#f2f2f2')
 +
:css('border-color', '#aaa')
 +
:css('border-style', 'solid')
 +
:css('border-top-width', '1px')
 +
:css('border-left-width', '1px')
 +
:css('border-right-width', '1px')
 +
:css('border-bottom-width', '0')
 +
:wikitext(seedArg)
 +
:newline()
 +
end
  
 
local teamArg = getTeamArg(round, 'team', team)
 
local teamArg = getTeamArg(round, 'team', team)
Line 103: Line 152:
 
teamArg = '&nbsp;'
 
teamArg = '&nbsp;'
 
end
 
end
local teamCell = addBlank(row)
+
local teamCell = row:tag('td')
 
:css('background-color', '#f9f9f9')
 
:css('background-color', '#f9f9f9')
:css('border-width', '1px')
+
:css('border-color', '#aaa')
:css('border-bottom-width', double and '2px' or '1px')
+
:css('border-style', 'solid')
 +
:css('border-top-width', '1px')
 +
:css('border-left-width', '1px')
 +
:css('border-right-width', '0')
 +
:css('border-bottom-width', '0')
 
:css('padding', '0 2px')
 
:css('padding', '0 2px')
 
:wikitext(teamArg)
 
:wikitext(teamArg)
 +
:newline()
 +
if not showSeed and showSeeds then
 +
teamCell:attr('colspan', '2')
 +
end
 +
 +
local scoreCell = row:tag('td')
 +
:css('text-align', 'center')
 +
:css('border-color', '#aaa')
 +
:css('border-style', 'solid')
 +
:css('border-top-width', '1px')
 +
:css('border-left-width', '1px')
 +
:css('border-right-width', '1px')
 +
:css('border-bottom-width', '0')
 +
:css('background-color', '#f9f9f9')
 +
:wikitext(getTeamArg(round, 'score', team))
 
:newline()
 
:newline()
  
for s = 1, sets do
+
if not compact then
addBlank(row)
+
if seedCell then
:css('text-align', 'center')
+
seedCell:attr('rowspan', '2')
:css('border-width', '1px')
+
:css('border-bottom-width', '1px')
:css('background-color', '#f9f9f9')
+
end
:css('border-bottom-width', double and '2px' or '1px')
+
scoreCell:attr('rowspan', '2')
:wikitext(getScore(round, team, s))
+
:css('border-bottom-width', '1px')
:newline()
+
teamCell:attr('rowspan', '2')
 +
:css('border-right-width', '1px')
 +
:css('border-bottom-width', '1px')
 +
else
 +
if not top then
 +
if seedCell then
 +
seedCell:css('border-bottom-width', '1px')
 +
end
 +
teamCell:css('border-bottom-width', '1px')
 +
scoreCell:css('border-bottom-width', '1px')
 +
end
 
end
 
end
 
end
 
end
  
function renderRound(rows, count, r)
+
function renderRound(rows, 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
local top = true
+
local topTeam = true -- is top row in match-up
local open = false
+
local topPair = true -- is top match-up in pair of match-ups
 
local team = 1
 
local team = 1
 
for i = 1, count, step do
 
for i = 1, count, step do
if not top then open = not open end
+
local offset, height, blank
local offset = top and i or i + 1
+
-- leave room for groups for teams other than first and last
local height = step - 1
+
if 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 height > 0 then
 +
blank = addBlank(rows[offset])
 +
:attr('colspan', tonumber(showSeeds and '4' or '3') + sets)
 +
:attr('rowspan', height)
 +
:css('border-color', 'black')
 +
:css('border-style', 'solid')
 +
:css('border-width', '0')
 +
end
 
-- add bracket
 
-- add bracket
local j = top and i + height or i
+
local j = topTeam and i + step - 2 or i
local left = addPath(rows, j, r, top, true)
+
-- add left path
renderTeam(rows[j], r, team, r == 1 and team < teams and not top)
+
addPath(rows, j, r, topTeam, true)
local right = addPath(rows, j, r, top, false)
+
renderTeam(rows[j], r, team, topTeam, false)
if open and r < rounds then
+
local rightPath = addPath(rows, j, r, topTeam, false)
right:css('border-right-width', '2px')
+
if not topTeam then topPair = not topPair end
 +
if not topPair and r < rounds then
 +
if blank then blank:css('border-right-width', '2px') end
 +
rightPath:css('border-right-width', '2px')
 
end
 
end
-- add blank
+
team = team + 1
for j = 0, height - 1 do
+
topTeam = not topTeam
blank = addBlank(rows[offset + j])
+
end
 +
end
 +
 
 +
function renderCompactRound(rows, 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
 +
 
 +
for i = 1, count, step do
 +
local offset, height, blank
 +
-- empty space above or below
 +
local offset = topTeam and i or i + 1
 +
local height = step - 1
 +
 
 +
if height > 0 then
 +
blank = addBlank(rows[offset])
 +
:attr('colspan', tonumber(showSeeds and '4' or '3') + sets)
 
:css('border-color', 'black')
 
:css('border-color', 'black')
:attr('colspan', 4 + sets)
+
:css('border-style', 'solid')
if open and r < rounds then
+
:css('border-width', '0')
blank:css('border-right-width', '2px')
+
:attr('rowspan', height)
end
+
end
 +
-- add bracket
 +
local j = topTeam and i + step - 1 or i
 +
-- add left path
 +
addCompactPath(rows, j, r, topTeam, true)
 +
renderTeam(rows[j], r, team, topTeam, true)
 +
local rightPath = addCompactPath(rows, j, r, topTeam, false)
 +
if not topTeam then topPair = not topPair end
 +
if not topPair and r < rounds then
 +
if blank then blank:css('border-right-width', '2px') end
 +
rightPath:css('border-right-width', '2px')
 
end
 
end
-- increment
 
 
team = team + 1
 
team = team + 1
top = not top
+
topTeam = not topTeam
 
end
 
end
end
+
end
 +
 
 +
function renderGroups(rows, count, round)
 +
local roundFromLast = rounds - round + 1
 +
local groups = math.pow(2, roundFromLast - 2)
 +
local step = count / groups
 +
local group = 1
 +
for i = step / 2, count, step do
 +
local name = 'RD' .. round .. '-group' .. group
 +
addBlank(rows[i]):css('height', '7px')
 +
addBlank(rows[i + 1]):css('height', '7px')
 +
addBlank(rows[i])
 +
:attr('rowspan', '2')
 +
:attr('colspan', ((showSeeds and 4 or 3) + sets) * round - 1)
 +
:css('text-align', 'center')
 +
:css('border-color', 'black')
 +
:css('border-style', 'solid')
 +
:css('border-width', '0 2px 0 0')
 +
:wikitext(args[name])
 +
:newline()
 +
group = group + 1
 +
end
 +
end
  
function renderTree(tbl)
+
function renderTree(tbl, compact)
local count = math.pow(2, rounds)
+
-- create 3 or 1 rows for every team
 +
local count = math.pow(2, rounds) * ((compact and 0 or 2) + sets)
 
local rows = {}
 
local rows = {}
 
for i = 1, count do
 
for i = 1, count do
 
rows[i] = addTableRow(tbl)
 
rows[i] = addTableRow(tbl)
 
end
 
end
 +
if not compact then
 +
-- fill rows with groups
 +
for r = 1, rounds - 1 do
 +
renderGroups(rows, count, r)
 +
end
 +
end
 +
-- fill rows with bracket
 
for r = 1, rounds do
 
for r = 1, rounds do
renderRound(rows, count, r)
+
if compact then
 +
renderCompactRound(rows, count, r)
 +
else
 +
renderRound(rows, count, r)
 +
end
 
end
 
end
 
end
 
end
  
function renderHeading(tbl)
+
function renderHeading(tbl, compact)
 
local titleRow = addTableRow(tbl)
 
local titleRow = addTableRow(tbl)
 
local widthRow = addTableRow(tbl)
 
local widthRow = addTableRow(tbl)
Line 172: Line 328:
 
addBlank(titleRow)
 
addBlank(titleRow)
 
addBlank(widthRow, r > 1 and '5px' or nil)
 
addBlank(widthRow, r > 1 and '5px' or nil)
:css('height', '7px')
+
titleRow:tag('td')
addBlank(titleRow)
+
:attr('colspan', tonumber(showSeeds and '2' or '1') + sets)
:attr('colspan', 2 + sets)
 
 
:css('text-align', 'center')
 
:css('text-align', 'center')
:css('border-width', '1px')
+
:css('border', '1px solid #aaa')
 
:css('background-color', '#f2f2f2')
 
:css('background-color', '#f2f2f2')
 
:wikitext(getRoundName(r))
 
:wikitext(getRoundName(r))
 
+
:newline()
addBlank(widthRow, getWidth('seed', '25px'))
+
local seedCell
:css('height', '7px')
+
if showSeeds then
addBlank(widthRow, getWidth('team', '150px'))
+
seedCell = addBlank(widthRow, getWidth('seed', '25px'))
:css('height', '7px')
+
end
 +
local teamCell = addBlank(widthRow, getWidth('team', '150px'))
 +
local scoreCells
 
for s = 1, sets do
 
for s = 1, sets do
addBlank(widthRow, getWidth('score', '12px'))
+
scoreCell[s] = addBlank(widthRow, getWidth('score', '12px'))
:css('height', '7px')
 
 
end
 
end
 
 
addBlank(titleRow)
 
addBlank(titleRow)
 
addBlank(widthRow, r < rounds and '5px' or nil)
 
addBlank(widthRow, r < rounds and '5px' or nil)
:css('height', '7px')
+
 
 +
if compact then
 +
teamCell:css('height', '7px')
 +
else
 +
if seedCell then
 +
seedCell:wikitext('&nbsp;')
 +
end
 +
teamCell:wikitext('&nbsp;')
 +
for s = 1, sets do
 +
scoreCell[s]:wikitext('&nbsp;')
 +
end
 +
end
 
end
 
end
 
end
 
end
Line 197: Line 363:
 
function p.teamBracket(frame)
 
function p.teamBracket(frame)
 
args = getArgs(frame)
 
args = getArgs(frame)
rounds = tonumber(args.rounds) or 4
+
rounds = tonumber(args.rounds) or 2
sets = tonumber(args.sets) or 5
+
sets = tonumber(args.sets or 5) or 5
 
local teams = math.pow(2, rounds)
 
local teams = math.pow(2, rounds)
 
padding = '%0' .. (teams < 10 and 1 or 2) .. 'd'
 
padding = '%0' .. (teams < 10 and 1 or 2) .. 'd'
 +
showSeeds = true
 +
if args['seeds'] and args['seeds'] == 'no' then
 +
showSeeds = false
 +
end
 +
 +
-- set default seeds for round 1
 +
local seeds = getSeeds()
 +
local argname
 +
for i = 1, table.getn(seeds) do
 +
argname = getTeamArgName(1, 'seed', i)
 +
if not args[argname] then
 +
args[argname] = seeds[i]
 +
end
 +
end
  
 
local tbl = mw.html.create('table')
 
local tbl = mw.html.create('table')
Line 206: Line 386:
 
:css('font-size', '90%')
 
:css('font-size', '90%')
 
:css('margin', '1em 2em 1em 1em')
 
:css('margin', '1em 2em 1em 1em')
:css('border-collapse', 'collapse')
+
:css('border-collapse', 'separate')
 
:css('border-spacing', '0')
 
:css('border-spacing', '0')
:attr('cellpadding', '0')
+
 
+
local compact = false
renderHeading(tbl)
+
if args['compact'] and args['compact'] == 'yes' then
renderTree(tbl)
+
compact = true
 +
end
 +
 
 +
if compact then
 +
tbl:css('font-size', '90%'):attr('cellpadding', '0')
 +
end
 +
 
 +
renderHeading(tbl, compact)
 +
renderTree(tbl, compact)
 
return tostring(tbl)
 
return tostring(tbl)
 
end
 
end
+
 
 
return p
 
return p

Revision as of 14:48, 13 April 2017

This template implements a variant of Module:TeamBracket for tennis tournaments. It is a generic visual representation of the tournament bracket. For common usage, use the templates in Category:Tennis tournament bracket templates.

Usage

{{#invoke: TeamBracket-Tennis | teamBracket
| rounds       =
| sets         =
| seeds        = <!-- yes / no / or blank -->
| compact      = <!-- yes / no / or blank -->
| nowrap       = <!-- yes / no / or blank -->

| seed-width   =
| team-width   =
| score-width  =

| RD1          =
| RD1-seed1    =
| RD1-team1    =
| RD1-score1-1 =
| RD1-score1-2 =
| RD1-score1-3 =
}}

Parameter list

The parameters are as follows:

seed-width – the width of the cells for seeds.
team-width – the width of the cells for team names.
score-width – the width of the cells for scores.
compact – set to yes for compact format
seeds – set to no to omit seed cells, and yes to always show seed cells
nowrap – set to yes to prevent lines from wrapping
byes – set to 1, 2, ... to specify the maximum round number with byes
headings – set to no to omit the column headings
RDnThe name of round n.
RDn-seedmThe seed of team m in round n.
RDn-teammThe name of team m in round n.
RDn-scorem-sThe score of set s for team m in round n.
RDn-groupmLabel for the mth group in round n.

Examples

{{#invoke: TeamBracket-Tennis | teamBracket
| rounds    = 2
| sets      = 3

| RD1-seed1 = '''1'''
| RD1-seed2 = 4
| RD1-team1 = '''John'''
| RD1-team2 = Declan
| RD1-score1-1 = '''6'''
| RD1-score2-1 = 0
| RD1-score1-2 = 3
| RD1-score2-2 = '''6'''
| RD1-score1-3 = '''7'''
| RD1-score2-3 = 6
| RD1-seed3 = 2
| RD1-seed4 = 3
| RD1-team3 = Barry
| RD1-team4 = Steve

| RD2-seed1 = 1
| RD2-team1 = John
}}

Lua error at line 344: attempt to index global 'scoreCell' (a nil value).

Templates using this module


--
-- This module will implement {{8TeamBracket-Tennis5}}
--

local p = {}
local args
local rounds
local sets
local padding
local showSeeds

local function getArgs(frame)
	local parent = frame:getParent();
	local args = parent.args;
	for k,v in pairs(frame.args) do
		args[k] = v
	end
	return args;
end

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

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

function addBlank(row, width)
	local cell = row:tag('td')
	if width then
		cell:css('width', width)
	end
	return cell
end

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

function addCompactPath(rows, index, round, top, left)
	local prop = top and 'border-bottom-width' or 'border-top-width'
	if left and round == 1 then
		addBlank(rows[index])
		return nil
	else
		local cell = addBlank(rows[index])
			:css('border-width', '0')
			:css('border-style', 'solid')
			:css('border-color', 'black')
		if left or round < rounds and not left then
			cell:css(prop, '2px')
		end
		return cell
	end
end

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

function getTeamArg(round, type, team)
	local argname = getTeamArgName(round, type, team)
	local value = args[argname]
	if not value or string.len(value) == 0 then
		return ''
	end
	return string.gsub(value, " *<[Bb][Rr] */?> *&nbsp; *", "<br/>")
end

function getTeamArgName(round, type, team)
	return string.format('RD%d-%s' .. padding, round, type, team)
end

function getRoundName(round)
	local name = args['RD' .. round]
	if name and string.len(name) > 0 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

function renderTeam(row, round, team, top, compact)
	local seedCell
	local seedArg = getTeamArg(round, 'seed', team)
	-- seed value for the paired team
	local pairSeedArg = getTeamArg(round, 'seed',
		team % 2 == 0 and team - 1 or team + 1)
	-- show seed if seed is defined for either or both
	local showSeed = seedArg and string.len(seedArg) > 0
		or pairSeedArg and string.len(pairSeedArg) > 0
	if showSeed and showSeeds then
		seedCell = row:tag('td')
			:css('text-align', 'center')
			:css('background-color', '#f2f2f2')
			:css('border-color', '#aaa')
			:css('border-style', 'solid')
			:css('border-top-width', '1px')
			:css('border-left-width', '1px')
			:css('border-right-width', '1px')
			:css('border-bottom-width', '0')
			:wikitext(seedArg)
			:newline()
	end

	local teamArg = getTeamArg(round, 'team', team)
	if not teamArg or string.len(teamArg) == 0 then
		teamArg = '&nbsp;'
	end
	local teamCell = row:tag('td')
		:css('background-color', '#f9f9f9')
		:css('border-color', '#aaa')
		:css('border-style', 'solid')
		:css('border-top-width', '1px')
		:css('border-left-width', '1px')
		:css('border-right-width', '0')
		:css('border-bottom-width', '0')
		:css('padding', '0 2px')
		:wikitext(teamArg)
		:newline()
	if not showSeed and showSeeds then
		teamCell:attr('colspan', '2')
	end

	local scoreCell = row:tag('td')
		:css('text-align', 'center')
		:css('border-color', '#aaa')
		:css('border-style', 'solid')
		:css('border-top-width', '1px')
		:css('border-left-width', '1px')
		:css('border-right-width', '1px')
		:css('border-bottom-width', '0')
		:css('background-color', '#f9f9f9')
		:wikitext(getTeamArg(round, 'score', team))
		:newline()

	if not compact then
		if seedCell then
			seedCell:attr('rowspan', '2')
				:css('border-bottom-width', '1px')
		end
		scoreCell:attr('rowspan', '2')
			:css('border-bottom-width', '1px')
		teamCell:attr('rowspan', '2')
			:css('border-right-width', '1px')
			:css('border-bottom-width', '1px')
	else
		if not top then
			if seedCell then
				seedCell:css('border-bottom-width', '1px')
			end
			teamCell:css('border-bottom-width', '1px')
			scoreCell:css('border-bottom-width', '1px')
		end
	end
end

function renderRound(rows, 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
	for i = 1, count, step do
		local offset, height, blank
		-- leave room for groups for teams other than first and last
		if 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 height > 0 then
			blank = addBlank(rows[offset])
				:attr('colspan', tonumber(showSeeds and '4' or '3') + sets)
				:attr('rowspan', height)
				:css('border-color', 'black')
				:css('border-style', 'solid')
				:css('border-width', '0')
		end
		-- add bracket
		local j = topTeam and i + step - 2 or i
		-- add left path
		addPath(rows, j, r, topTeam, true)
		renderTeam(rows[j], r, team, topTeam, false)
		local rightPath = addPath(rows, j, r, topTeam, false)
		if not topTeam then topPair = not topPair end
		if not topPair and r < rounds then
			if blank then blank:css('border-right-width', '2px') end
			rightPath:css('border-right-width', '2px')
		end
		team = team + 1
		topTeam = not topTeam
	end
end

function renderCompactRound(rows, 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

	for i = 1, count, step do
		local offset, height, blank
		-- empty space above or below
		local offset = topTeam and i or i + 1
		local height = step - 1

		if height > 0 then
			blank = addBlank(rows[offset])
				:attr('colspan', tonumber(showSeeds and '4' or '3') + sets)
				:css('border-color', 'black')
				:css('border-style', 'solid')
				:css('border-width', '0')
				:attr('rowspan', height)
		end
		-- add bracket
		local j = topTeam and i + step - 1 or i
		-- add left path
		addCompactPath(rows, j, r, topTeam, true)
		renderTeam(rows[j], r, team, topTeam, true)
		local rightPath = addCompactPath(rows, j, r, topTeam, false)
		if not topTeam then topPair = not topPair end
		if not topPair and r < rounds then
			if blank then blank:css('border-right-width', '2px') end
			rightPath:css('border-right-width', '2px')
		end
		team = team + 1
		topTeam = not topTeam
	end
end

function renderGroups(rows, count, round)
	local roundFromLast = rounds - round + 1
	local groups = math.pow(2, roundFromLast - 2)
	local step = count / groups
	local group = 1
	for i = step / 2, count, step do
		local name = 'RD' .. round .. '-group' .. group
		addBlank(rows[i]):css('height', '7px')
		addBlank(rows[i + 1]):css('height', '7px')
		addBlank(rows[i])
			:attr('rowspan', '2')
			:attr('colspan', ((showSeeds and 4 or 3) + sets) * round - 1)
			:css('text-align', 'center')
			:css('border-color', 'black')
			:css('border-style', 'solid')
			:css('border-width', '0 2px 0 0')
			:wikitext(args[name])
			:newline()
		group = group + 1
	end
end

function renderTree(tbl, compact)
	-- create 3 or 1 rows for every team
	local count = math.pow(2, rounds) * ((compact and 0 or 2) + sets)
	local rows = {}
	for i = 1, count do
		rows[i] = addTableRow(tbl)
	end
	if not compact then
		-- fill rows with groups
		for r = 1, rounds - 1 do
			renderGroups(rows, count, r)
		end
	end
	-- fill rows with bracket
	for r = 1, rounds do
		if compact then
			renderCompactRound(rows, count, r)
		else
			renderRound(rows, count, r)
		end
	end
end

function renderHeading(tbl, compact)
	local titleRow = addTableRow(tbl)
	local widthRow = addTableRow(tbl)
	for r = 1, rounds do
		addBlank(titleRow)
		addBlank(widthRow, r > 1 and '5px' or nil)
		titleRow:tag('td')
			:attr('colspan', tonumber(showSeeds and '2' or '1') + sets)
			:css('text-align', 'center')
			:css('border', '1px solid #aaa')
			:css('background-color', '#f2f2f2')
			:wikitext(getRoundName(r))
			:newline()
		local seedCell
		if showSeeds then
			seedCell = addBlank(widthRow, getWidth('seed', '25px'))
		end
		local teamCell = addBlank(widthRow, getWidth('team', '150px'))
		local scoreCells
		for s = 1, sets do
			scoreCell[s] = addBlank(widthRow, getWidth('score', '12px'))
		end
		addBlank(titleRow)
		addBlank(widthRow, r < rounds and '5px' or nil)

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

function p.teamBracket(frame)
	args = getArgs(frame)
	rounds = tonumber(args.rounds) or 2
	sets = tonumber(args.sets or 5) or 5
	local teams = math.pow(2, rounds)
	padding = '%0' .. (teams < 10 and 1 or 2) .. 'd'
	showSeeds = true
	if args['seeds'] and args['seeds'] == 'no' then
		showSeeds = false
	end

	-- set default seeds for round 1
	local seeds = getSeeds()
	local argname
	for i = 1, table.getn(seeds) do
		argname = getTeamArgName(1, 'seed', i)
		if not args[argname] then
			args[argname] = seeds[i]
		end
	end

	local tbl = mw.html.create('table')
		:css('border-style', 'none')
		:css('font-size', '90%')
		:css('margin', '1em 2em 1em 1em')
		:css('border-collapse', 'separate')
		:css('border-spacing', '0')

	local compact = false
	if args['compact'] and args['compact'] == 'yes' then
		compact = true
	end

	if compact then
		tbl:css('font-size', '90%'):attr('cellpadding', '0')
	end

	renderHeading(tbl, compact)
	renderTree(tbl, compact)
	return tostring(tbl)
end

return p