This template implements a generic visual representation of the bracket of a single-elimination tournament with any number or rounds. For common usage, use the {{64TeamBracket}}, {{32TeamBracket}}, {{16TeamBracket}}, {{8TeamBracket}}, {{4TeamBracket}}, {{2TeamBracket}} templates.

{{#invoke: TeamBracket | teamBracket
| rounds      =

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

| RD1         =
| RD1-group1  =
| RD1-seed1   =
| RD1-team1   =
| RD1-score1  =
 ...
}}

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.
seeds – set to no to hide all seeds.
RDnThe name of round n.
RDn-groupmThe name of group m in round n.
RDn-seedmThe seed of team m in round n.
RDn-teammThe name of team m in round n.
RDn-scoremThe score of team m in round n.
seed-width
The width of the seed cells in CSS. Examples:
N [em/%/px]
auto
team-width
The width of the team name cells in CSS.
score-width
The width of the score cells in CSS.
RDn
The name of round n. Defaults are "Round of m", ..., "Quarterfinals", "Semifinals", and "Finals", where m is the number of teams in the round.
RDn-groupm
The name of group m in round n. For each round, every set of four teams is classified as a group.
RDn-seedm
The seed of team m in round n. For round 1, this value defaults to the conventional seed allocation for tournaments. If omitted, the cell is hidden. To hide seeds for round 1, the value must be explicitly set to be empty. m is the zero-padded position.
RDn-teamm
The name of team m in round n. m is the zero-padded position.
RDn-scorem
The score of team m in round n. m is the zero-padded position.
{{#invoke: TeamBracket | teamBracket
| rounds    = 2
| RD1-seed1 =
| RD1-seed2 =
| RD1-seed3 =
| RD1-seed4 =
}}
Semifinals Finals
      
 
 
 
 
 
 
{{#invoke: TeamBracket | teamBracket
| rounds     = 3
| RD1-group1 = Pacific
| RD1-group2 = Mountain
| RD2-group1 = West
}}
Quarterfinals Semifinals Finals
         
1  
8  
 
Pacific
 
5  
4  
 
West
 
3  
6  
 
Mountain
 
7  
2  



--
-- This module will implement {{TeamBracket}}
--
 
local p = {}
local args
local rounds
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')
		:css('border-width', '0')
		:css('border-style', 'solid')
		:css('border-color', 'black')
	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')
		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)
	return args[getTeamArgName(round, type, team)]
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)
	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
		row:tag('td')
			:attr('rowspan', '2')
			:css('text-align', 'center')
			:css('background-color', '#f2f2f2')
			:css('border', '1px solid #aaa')
			: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')
		:attr('rowspan', '2')
		:css('background-color', '#f9f9f9')
		:css('border', '1px solid #aaa')
		:css('padding', '0 2px')
		:wikitext(teamArg)
		:newline()
	if not showSeed and showSeeds then
		teamCell:attr('colspan', '2')
	end
	row:tag('td')
		:attr('rowspan', '2')
		:css('text-align', 'center')
		:css('border', '1px solid #aaa')
		:css('background-color', '#f9f9f9')
		:wikitext(getTeamArg(round, 'score', team))
		:newline()
end

function renderRound(rows, count, r)	
	local teams = math.pow(2, rounds - r + 1)
	local step = count / teams
	local top = true
	local open = false
	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 = top and i or i + 2
			height = step - 2
		else
			offset = top and i + 1 or i + 2
			height = step - 3
		end
		if height > 0 then
			blank = addBlank(rows[offset])
				:attr('colspan', showSeeds and '5' or '4')
				:attr('rowspan', height)
		end
		-- add bracket
		local j = top and i + step - 2 or i
		addPath(rows, j, r, top, true)
		renderTeam(rows[j], r, team)
		local right = addPath(rows, j, r, top, false)
		if not top then open = not open end
		if open and r < rounds then
			if blank then blank:css('border-right-width', '2px') end
			right:css('border-right-width', '2px')
		end
		team = team + 1
		top = not top
	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 5 or 4) * round - 1)
			:css('text-align', 'center')
			:css('border-right-width', '2px')
			:wikitext(args[name])
			:newline()
		group = group + 1
	end
end

function renderTree(tbl)
	-- create 3 rows for every team
	local count = math.pow(2, rounds) * 3
	local rows = {}
	for i = 1, count do
		rows[i] = addTableRow(tbl)
	end
	-- fill rows with groups
	for r = 1, rounds - 1 do
		renderGroups(rows, count, r)
	end
	-- fill rows with bracket
	for r = 1, rounds do
		renderRound(rows, count, r)
	end
end

function renderHeading(tbl)
	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', showSeeds and '3' or '2')
			:css('text-align', 'center')
			:css('border', '1px solid #aaa')
			:css('background-color', '#f2f2f2')
			:wikitext(getRoundName(r))
			:newline()
		if showSeeds then
			addBlank(widthRow, getWidth('seed', '25px')):wikitext('&nbsp;')
		end
		addBlank(widthRow, getWidth('team', '150px')):wikitext('&nbsp;')
		addBlank(widthRow, getWidth('score', '25px')):wikitext('&nbsp;')
		addBlank(titleRow)
		addBlank(widthRow, r < rounds and '5px' or nil)
	end
end

function p.teamBracket(frame)
	args = getArgs(frame)
	rounds = tonumber(args.rounds) or 2
	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')
 
	renderHeading(tbl)
	renderTree(tbl)
	return tostring(tbl)
end
 
return p