Module:TeamBracket

--
-- 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')
	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)
	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, 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', showSeeds and '5' or '4')
				: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', showSeeds and '5' or '4')
				: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 5 or 4) * 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 1 or 3)
	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', showSeeds and '3' or '2')
			: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 scoreCell = addBlank(widthRow, getWidth('score', '25px'))
		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;')
			scoreCell:wikitext('&nbsp;')
		end
	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')

	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

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.