Modul:Turnierplan

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen
Vorlagenprogrammierung Diskussionen Lua Unterseiten
Modul Deutsch

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus

Weiterleitung der Diskussionsseite fehlt

Modul zur Erstellung kompletter Turnierpläne.

{{#invoke:Turnierplan|bracket|runden=2}}
Halbfinale Finale


{{#invoke:Turnierplan|bracket|reseed=ja|runden=3
|n1= |n2= |n3= |n4= |n5= |n6= |n7=}}
Viertelfinale Halbfinale Finale
1
4
5
2
7
3
6
„Reseeding“: In jeder Runde spielt das höchstgesetzte Team zu Hause gegen das niedrigste, das zweithöchste zu Hause gegen das zweitniedrigste und so weiter. Die hellblauen Linien entsprechen dem Fall, in dem jeweils das Heimteam gewinnt.

local footnotes = {}
local rounds = {}
local nameWidth = '12em'

local byFinish = { 'Achtelfinale', 'Viertelfinale', 'Halbfinale', 'Finale' }

local function notNilStr(s)
	if s == nil then return '' end
	return s
end

--[[
	fillRounds(args)
	* generates rounds (global variable) by args.runden
	* optionally overwrites items of rounds by args.rd
	parameters:
		args.runden		mandantory: either a number or a predefined table
		args.rd<number>	optional, name of round with given number where number
						starts with 1 in closed sequence
						(if there's no args.rd3 then args.rd4 and subsequent
						 parameters are ignored.)
	returns: nothing
]]
function fillRounds(args)
	if args.runden then
		if type(args.runden) ~= 'table' then
			local count = 1
			if type(args.runden) == 'string' then
				local c = tonumber(args.runden)
				if c then count = c end
			elseif type(args.runden) == 'number' then count = args.runden
			end
		
			-- fill rounds with default names
			local i = 1
			-- fill rounds with generic names by given number
			while i <= count - 4 do
				table.insert(rounds, 'Runde ' .. i)
				i = i + 1
			end
			if i == 1 then i = 5 - count else i = 1 end
			-- fill final rounds with default names
			while i <= 4 do
				table.insert(rounds, byFinish[i])
				i = i + 1
			end
		end
	else
		rounds = byFinish
	end
	
	-- overwrite rounds with explicitely given args
	for i = 1, table.getn(rounds) do
		if args['rd' .. i] ~= nil then
			rounds[i] = args['rd' .. i]
		end
	end
	if args.finale ~= nil then rounds[#rounds] = args.finale end
end

local function setBracketSize(bracket)
	if bracket.branch[0].size() == 0 then
		bracket.sizeO = math.max(bracket.branch[1].sizeO - 1, 3)
		bracket.sizeU = math.max(bracket.branch[1].sizeU + 1, 3)
	elseif bracket.branch[1].size() == 0 then
		bracket.sizeO = bracket.branch[0].sizeO + 1
		bracket.sizeU = math.max(bracket.branch[0].sizeU - 1, 3)
	else
		bracket.sizeO = bracket.branch[0].size()
		bracket.sizeU = bracket.branch[1].size()
	end
	if bracket.header ~= nil then
		bracket.sizeO = math.max(bracket.sizeO, 5)
	end
	if bracket.footer ~= nil then
		bracket.sizeU = math.max(bracket.sizeU, 5)
	end
end

local function addFootnote(fn)
	local count = 1
	while footnotes[count] ~= nil do
		if footnotes[count] == fn then return count end
		count = count + 1
	end
	footnotes[count] = fn
	return count
end

local function parseScore(input)
	local score = {}
	score.num = tonumber(mw.ustring.match(input, "^%s*(%d+)"))
	local fn = mw.ustring.match(input, "^%d+%*(.*)%s*$")
	if fn ~= nil then
		if fn == '' then fn = 'nach Verlängerung' end
		local index = addFootnote(fn)
		score.text = '<sup><span style="visibility:hidden;">' .. index .. '</span></sup>' .. score.num ..
			'<span class="reference"><sup id="FN_tp' .. index .. '_v">[[#FN_tp' .. index ..
			'|' .. index .. ']]</sup></span>'
	else score.text = score.num end
	return score
end

local function fillScore(bracket, score)
	if score == nil then return end
	local hs = parseScore(mw.ustring.match(score,"^(.+)[:-]"))
	local as = parseScore(mw.ustring.match(score,"[:-](.+)$"))
	if hs ~= nil and as ~= nil then
		if hs.num > as.num then
			bracket.branch[0].win = true
			bracket.key = bracket.branch[0].key
		elseif hs.num < as.num then
			bracket.branch[1].win = true
			bracket.key = bracket.branch[1].key
		end
		bracket.branch[0].score = hs.text
		bracket.branch[1].score = as.text
	end
end

local function fillBranch(branch, args)
	if branch.key == nil then
		return
	end
	branch.name = branch.key
	if args[branch.key] ~= nil then
		branch.name = args[branch.key]
	end 
	branch.seed = args['seed_' .. branch.key]
end

local function bracketDetails(kind, key, args, round)
	local kindKey = key .. string.sub(kind, 1, 1)
	if args[kindKey] == nil then
		if args[kind .. round] ~= nil then
			kindKey = kind .. round
		else
			return nil
		end
	end
	details = {}
	details.text = args[kindKey]
	if args[kindKey .. 's'] ~= nil then 
		details.style = args[kindKey .. 's']
	else
		details.style = args[kind .. '-style']
	end
	return details
end

local function fillBracket(bracket, args)
	local b0, b1 = bracket.branch[0], bracket.branch[1]
	local key = notNilStr(b0.key) .. '-' .. notNilStr(b1.key)
	fillScore(bracket, args[key])
	bracket.header = bracketDetails('header', key, args, bracket.round)
	bracket.footer = bracketDetails('footer', key, args, bracket.round)
	setBracketSize(bracket)
	fillBranch(bracket, args)
end

local function createBranch()
	local branch = {}
	function branch.size()
		return branch.sizeO + branch.sizeU
	end
	return branch
end

local function atomBranch(key, args)
	local branch = createBranch()
	branch.key = key
	branch.sizeO = 0
	branch.sizeU = 0
	branch.round = 0
	branch.link = args[key .. '_l']
	fillBranch(branch, args)
	return branch
end

local function branchesToBracket(upper, lower, args, round)
	local bracket = createBranch()
	bracket.branch = {}
	bracket.branch[0] = upper
	bracket.branch[1] = lower
	if round == nil then round = math.max(upper.round, lower.round) + 1 end
	bracket.round = round
	fillBracket(bracket, args)
	return bracket
end

local function buildBracket(args, round, spot)
	local branch = {}
	for i = 0, 1 do
		local entry = 'r' .. round .. '_' .. spot+i
		local key = args[entry]
		if key == nil and round == #rounds then key = args['n'..spot+i+1] end
		if key ~= nil then
			branch[i] = atomBranch(key, args)
		elseif round == #rounds then
			args[entry] = '' -- rather be nil?
			branch[i] = atomBranch(entry, args)
		else
			if round > 20 then
				mw.logObject(args, 'args')
				mw.logObject(rounds, 'rounds')
				assert(false)
			end
			branch[i] = buildBracket(args, round + 1, 2*(spot+i))
		end
	end
	return branchesToBracket(branch[0], branch[1], args, round + 1)
end

local function reseedColumn(t, p, args, branch)
	local l, i = p - 1, 0
	local reseed = false
	while t <= l do
		local j = 2 * p - i - 1
		local bracket
		local ziel = p + t
		if branch[2 * p + j] == nil then
			bracket = branch[2 * p + i]
		else
			bracket = branchesToBracket(branch[2 * p + i], branch[2 * p + j], args)
			if reseed then bracket.key, bracket.seed, bracket.name = nil, nil, nil end
			if bracket.branch[1].win then
				ziel = p + l
				t, l = t - 1, l - 1
			elseif not bracket.branch[0].win and t < l then
				reseed = true
				local re = '\n{|\n|-\n|style="height:.2ex; width:1.2em; border-top:3px solid #CAD2EE"|\n|}\n'
				if footnotes[re] == nil then
					footnotes[re] = '„Reseeding“: In jeder Runde spielt das höchstgesetzte Team zu Hause gegen das niedrigste, das zweithöchste zu Hause gegen das zweitniedrigste und so weiter. Die hellblauen Linien entsprechen dem Fall, in dem jeweils das Heimteam gewinnt.'
				end
			end
								   
		end

		bracket.reseed = reseed
		branch[ziel] = bracket

		t = t + 1
		i = i + 1
	end
end

local function buildReseedBracket(args)
	local branch = {}
	local j = math.pow(2, args.runden)
	for i = 1, j do
		local key = args['n' .. i]
		if key ~= nil then
			if key == '' then key = 'n' .. i end
			if args['seed_' .. key] == nil then args['seed_' .. key] = i end
			branch[j + i - 1] = atomBranch(key, args)
		else
			branch[j + i - 1] = nil
		end
	end
	for i = args.runden - 1, 0, -1 do
		reseedColumn(0, math.pow(2, i), args, branch)
	end
	return branch[1]
end

-- print bracket methods

local function printBox(seed, name, score, border, weight)
	local ret = {}
	local sty0 = '|rowspan="2" style="background:#'
	local sty1 = '; border-style:solid; border-color:#AAAAAA; '

	table.insert(ret, sty0..'EAECF0;'..border..' 0 1px 1px'..sty1..'text-align:center; padding:0 0.6em"| '..seed..'\n')
	table.insert(ret, sty0..'F8F9FB;'..border..' 1px 1px 1px'..sty1..weight..'padding-left:0.4em"| '..name..'\n')
	table.insert(ret, sty0..'F8F9FB;'..border..' 1px 1px 0'..sty1..weight..'text-align:center; padding:0 0.6em"| '..score..'\n')

	return table.concat(ret)
end

local function printContent(content)
	if content == nil or content == '' then
		return content
	end
	if type(content) == "string" then
		return '|rowspan="2" colspan="3" style="font-size:90%; vertical-align:bottom; padding:.2ex .5em .2ex .5em"| ' .. content .. '\n'
	end
	if content.text ~= nil then
		local ret = '|rowspan="2" colspan="3"'
		if content.style ~= nil then ret = ret .. ' style="' .. content.style .. '"' end
		return ret .. '| ' .. content.text .. '\n'
	end
	local seed = notNilStr(content.seed)
	local name = notNilStr(content.name)
	if content.link ~= nil then name = '[[' .. content.link .. '|' .. name .. ']]' end
	local score = notNilStr(content.score)
	local weight = ''
	if content.win then weight = 'font-weight:bold; ' end
	return printBox(seed, name, score, ' border-width:' .. content.top, weight)
end

local function connectLine(col, y, z, color)
	local s0, s1, i, pos, wid = 'rowspan="', 'px solid', 1, 'bottom:', '1px 3px 0'
	if color ~= '' then s1 = s1 .. ' ' .. color end
	if y == z then
		col[y - 1] = 'colspan="2" style="border-bottom:2' .. s1 .. '"'
		col[y] = 'colspan="2" style="border-top:1' .. s1 .. '"'
		return
	elseif y > z then
		i, pos, wid = 0, 'top:', '0 3px 2px'
	end
	col[y - i] = 'style="border-' .. pos .. 1 + i .. s1 .. '"| |'
	if y > z then y, z = z, y end
	s0 = s0 .. z - y .. '" style="border-'
	col[y] = s0 .. 'width:' .. wid .. ' 0; border-style:solid; border-color:' .. color .. '"| ||' .. s0 .. pos .. 3 .. s1 .. '"'
	for i = 1, z - y - 1 do
		col[y + i] = ''
	end
end

local function bracketPos(pos, p0, p1, header)
	if p0 ~= nil and p1 ~= nil then
		return math.floor((p0+p1)/2)
	elseif p0 ~= nil then
		return p0 + 1
	elseif p1 ~= nil then
		return p1 - 1
	elseif header ~= nil then
		return pos + 5
	else
		return pos + 3
	end
end

local function printLines(ma, branch, x, y1)
	if branch.branch == nil then
		return
	end
	local color = ''
	if branch.reseed then
		color = ' #CAD2EE'
	end
	local y0 = branch.pos
	connectLine(ma[x], y0, y1, color)
end

local function insertContentInMatrix(ma, x, y, h, content)
	if content == nil then
		return
	end
	ma[x][y] = content
	for i = 1, h - 1 do
		ma[x][y + i] = ''
	end
end

local function printBranch(ma, bracket, round, pos)
	if bracket.branch == nil then
		return ma
	end
	ma = printBranch(ma, bracket.branch[0], round-1, pos)
	if bracket.branch[0].size() == 0 then
		posOs = bracket.sizeO + 1 - bracket.branch[1].sizeO
	else
		posOs = bracket.branch[0].size()
	end
	ma = printBranch(ma, bracket.branch[1], round-1, pos + posOs)
	
	bracket.pos = bracketPos(pos, bracket.branch[0].pos, bracket.branch[1].pos, bracket.header)

	local x = 2 * round - 2
	insertContentInMatrix(ma, x, bracket.pos-4, 2, bracket.header)
	insertContentInMatrix(ma, x, bracket.pos+2, 2, bracket.footer)
	
	bracket.branch[0].top = '1px'
	insertContentInMatrix(ma, x, bracket.pos-2, 2, bracket.branch[0])
	bracket.branch[1].top = '0'
	insertContentInMatrix(ma, x, bracket.pos, 2, bracket.branch[1])

	printLines(ma, bracket.branch[0], 2 * round - 3, bracket.pos - 1)
	printLines(ma, bracket.branch[1], 2 * round - 3, bracket.pos + 1)
		
	return ma
end

local function tableHeader(x, class)
	local tab = {}
	table.insert(tab, '{| ' .. class .. ' style="font-size:90%"\n')
	table.insert(tab, '|-\n|\n')
	for i = 1, x do
		table.insert(tab, '|style="width:2em"| ||style="width:' .. nameWidth .. '"| ||style="width:3em"|\n')
		table.insert(tab, '|style="width:1em"| ||style="width:.8em"|\n')
	end
	table.insert(tab, '|style="width:2em"| ||style="width:' .. nameWidth .. '"| ||style="width:3em"|\n')
	return tab
end

local function matrixToTable(ma, x, y, tab)
	for j = 0, y do
		table.insert(tab, '|-\n|style="height:1.6ex"|\n')
		for i = 0, 2*x, 2 do
			if ma[i][j] == nil then
				table.insert(tab, '|colspan="3"|\n')
			else
				table.insert(tab, printContent(ma[i][j]))
			end
			if ma[i+1] == nil or ma[i+1][j] == '' then
			elseif ma[i+1][j] == nil then
				table.insert(tab, '|colspan="2"|\n')
			else
				table.insert(tab, '|' .. ma[i+1][j] .. '|\n')
			end
		end
	end
	table.insert(tab, '|}\n')
	return table.concat(tab)
end

local tp = {}

--[[
	processArgs(args)
	* preprocesses given args
	* generates rounds based on args.runden
	* optionally overwrites nameWidth (global variable)
	parameters:
		args.runden		mandantory: either a number or a predefined table
		args.rd<number>	optional, name of round with given number where number
						starts with 1 (no closed sequence needed, if <number> is
						larger than count of rounds args.rd<number> is ignored.)
		args.nameWidth	optional, fall back to args.width
		args.width		optional, string, css style width information
	returns: nothing
]]
function tp.processArgs(args)
	fillRounds(args)
	
	if args.nameWidth and type(args.nameWidth) == 'string' then
		nameWidth = args.nameWidth
	elseif args.width and type(args.width) == 'string' then
		nameWidth = args.width
	end
end

function tp.buildBracket(args)
	if args.reseed then
		return buildReseedBracket(args)
	end
	return buildBracket(args, 1, 0)
end

function tp.branchesToBracket(upper, lower, args)
	return branchesToBracket(upper, lower, args)
end

function tp.footnoteTable()
	if not next(footnotes) then
		return ''
	end
	local ret = '\n<div style="border-bottom:1px solid #777777; margin-top:2ex; margin-bottom:.5ex; width:7em"></div><div style="padding-left:0.7em">\n'
	ret = ret .. '{| border="0" cellspacing="0" style="font-size:81%"\n'
	for i, fn in pairs(footnotes) do
		ret = ret .. '|-\n|style="text-align:right"| '
		if type(i) == "number" then
			ret = ret .. '[[#FN_tp' .. i .. '_v|<sup id="FN_tp' .. i
			.. '" style="display:inline-block; min-width:0.5em">' .. i .. '</sup>]]'
		else
			ret = ret .. i
		end
		ret = ret .. ' || ' .. fn .. '\n'
	end
	return ret .. '|}</div>'
end

function tp.bracketToTable(bracket)
	local content = {}
	local x = table.getn(rounds)
	for i = 0, 2 * (x - 1) do
		content[i] = {}
	end
	for i = 0, 2 * (x - 1), 2 do
		content[i][0] = {}
		content[i][0].style = 'text-align:center; border:1px solid; background:#EAECF0; padding:.2ex .5em .2ex .5em'
		content[i][0].text = rounds[i/2 + 1]
		content[i][1] = ''
	end
	content = printBranch(content, bracket, x, 2)
	local tab = tableHeader(x - 1, 'border="0" cellpadding="0" cellspacing="0"')
	return matrixToTable(content, x - 1, bracket.size(), tab)
end

function tp.bracket(frame)
	tp.processArgs(frame.args)
	local bracket = tp.buildBracket(frame.args)
	local tabB = tp.bracketToTable(bracket)
	local tabF = tp.footnoteTable()
	return tabB .. tabF
end

return tp