Modulo:TeamBracket

Questo è un modulo scritto in Lua. Le istruzioni che seguono sono contenute nella sottopagina Modulo:TeamBracket/man (modifica · cronologia)
Sandbox: Modulo:TeamBracket/sandbox (modifica · cronologia) · Sottopagine: lista · Test: Modulo:TeamBracket/test (modifica · cronologia · Esegui)
Questo modulo serve a realizzare dei tabelloni per i tornei sportivi.
{{#invoke:TeamBracket|teamBracket
|compact = yes/no [default: yes]
|rounds =
|maxround = deve essere inferiore a rounds
|headings = valorizzare con no
per non farli apparire
|RD-shade = colore di sfondo dell'intestazione del round (se diverso da quello di default)
|RDn-RDn+1-path = valorizzare con 0
per non far apparire i collegamenti fra i due round selezionati
|byes = valorizzare con il numero di turni in cui un incontro potrebbe non verificarsi (bye, walkover, ecc.)
|seeds = valorizzare con yes
per farli apparire in ogni turno
|autoseeds = yes/no [default: no]
|sets = alternativo a legs
(se variano da turno a turno, separare con /)
|legs = alternativo a sets
(se variano da turno a turno, separare con /)
|hideomittedscores = yes/no [default: no]
|aggregate = valorizzare con yes
, sets
o legs
|boldwinner = yes/no [default: no]
|seed-width = [default: 28px]
|team-width = [default: 170px]
|score-width = [default: 28px]
|RDn = intestazione del round (se diverso dal nome di default)
|RD-finalina = intestazione della finale per il 3º posto (se diverso dal nome di default)
|RDn-groupm =
|RDn-seedm = seed della squadra o del partecipante
|RDn-teamm = squadra o partecipante
|RDn-scorem = punteggio della squadra o del partecipante
|RDn-scorem-s = punteggio della squadra o del partecipante (se l'incontro prevede più di una gara o di un set)
|RDn-shadem-s = colore di sfondo del risultato (se diverso da quello di default)
|RDn-scorem-agg = sovrascrive il valore automatico
}}
--
-- This module will implement {{TeamBracket}}
--
require('Module:No globals')
local getArgs = require('Module:Arguments').getArgs
local p = {}
local yes_replies = { 'yes', 'y', 's', 'si', 'sì'}
local no_replies = { 'n', 'no'}
local debug = false
local style = {
table = "border-style:none;font-size:90%;margin:1em 2em 1em 1em;border-collapse:separate;border-spacing:0",
seed_cell = "background-color:#f2f2f2;border:1px solid #aaa;text-align:center;",
team_cell = "background-color:#f2f2f2;border:1px solid #aaa;text-align:left;", --padding:0 2px
score_cell ="background-color:#f9f9f9;border:1px solid #aaa;text-align:center;",
header_third_place = "text-align:center;border:1px solid #aaa;background-color:#f2f2f2",
header_cell = "text-align:center;border:1px solid #aaa;background-color:#f2f2f2",
path1 = "border:0 solid black;border-right-width:2px",
path2 = "border:0 solid black",
group = "text-align:center",
winner = {'font-weight' , 'bold'},
first_place = {'background-color', 'gold'},
second_place = {'background-color', 'silver'},
third_place = {'background-color', 'bronze'},
row_height = "3px",
buffer_sx_width = "5px",
seed_width = "25px",
team_width = "150px",
score_width = "25px",
row_width = "10px"
}
if debug then
style.group = style.group .. ";background-color:green"
style.path1 = style.path1 .. ";background-color:#F08080"
style.path2 = style.path2 .. ";background-color:#F08080"
end
--[[ ===============================================================================
Ritorna true se needle è lista haystack, altrimenti ritorna false
===============================================================================]]
local function in_array( needle, haystack )
if needle == nil then return false; end
for n,v in ipairs( haystack ) do
if v == needle then return true; end
end
return false;
end
--[[ ===============================================================================
Oggetto per generare un TeamBracket
- args: array dei parametri passati al modulo
===============================================================================]]
local TeamBracket = {}
function TeamBracket:new(args)
local self = {}
setmetatable(self, { __index = TeamBracket,
__tostring = function(t) return self:__tostring() end })
self.args = args
self.rounds = tonumber(self.args['rounds']) or 2
self.teams = math.pow(2, self.rounds)
self.compact = in_array(self.args['compact'], yes_replies)
self.hideSeeds = in_array(self.args['seeds'], no_replies)
self.showSeeds = in_array(self.args['seeds'], yes_replies)
local padding = '%0' .. ((self.teams < 10) and 1 or 2) .. 'd'
self.argname_pattern = 'RD%d-%s' .. padding
self.scorename_pattern = 'RD%d-score' .. padding
self.scores = {}
-- load number of scores for each round
local scores_raw = args['scores'] or '1'
local max_scores = 1
for i,score_value in ipairs(mw.text.split(scores_raw, ',')) do
self.scores[i] = tonumber(score_value) or 1
if self.scores[i] > max_scores then max_scores = self.scores[i] end
end
local last_scores = self.scores[#self.scores]
for i =#self.scores+1, self.rounds do
self.scores[i] = last_scores
end
if max_scores > 1 then
self.scorename_pattern = self.scorename_pattern .. "-%d"
end
-- set default seeds for round 1
if not(self.hideSeeds) then
local seeds = self:getSeeds()
local argname
for i = 1, table.getn(seeds) do
argname = self:getTeamArgName(1, 'seed', i)
if not args[argname] then
args[argname] = seeds[i]
end
end
end
self.tbl = mw.html.create('table'):cssText(style.table)
if in_array(args['nowrap'], yes_replies) then
self.tbl:css('white-space', 'nowrap')
end
self.rows = { }
if self.compact then self.tbl:attr('cellpadding', '0') end
self.last_element = {}
self.rows = {}
self.current_col = 0
self.dump = {}
self.not_draw_top = false
self:renderHeading()
self:renderTree()
return self
end
--[[ ===============================================================================
-- collassa l'oggetto in una stringa contenente il wikicodice per generare l'albero
===============================================================================]]
function TeamBracket:__tostring()
return tostring(self.tbl)
end
--[[ ===============================================================================
Genera i valori di default dei seeds
===============================================================================]]
function TeamBracket:getSeeds()
local seeds = {1, 2}
local count = 2
local before = false
for r = 2, self.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
--[[ ===============================================================================
Se fino alla riga row, colonna col-1 non sono ancora stati aggiunti elementi aggiunte
una cella vuota dall'ultima colonna usta dalla riga.
Aggiorna quindi il conteggio delle colonne usate alla posizione colonna +width
Il valore di default di width è 0
=============================================================================== --]]
function TeamBracket:addGap(row, col, width, debug_text)
width = width or 0
if self.last_element[row] + 1 < col then
local gap = tostring(col - self.last_element[row] - 1)
self.rows[row]:tag('td'):css('background-color', (debug and '#F5F5DC') or ''):attr('colspan', gap)
--:wikitext(self.last_element[row] .. " c:".. col .. " w gap:" .. gap .. " w add:" .. width .. " n col:" .. col + width)
end
self.last_element[row] = col + width
end
--[[ ===============================================================================
Aggiunge le righe grafiche di congiunzione delle celle per la riga index del
turno round. top e left indicano se deve essere generata rispettivamente la riga
sinistra o quella superiore
- row1 = prima riga del match superiore da cui esce il path
- row2 = prima riga del match inferiore da cui esce il path
- row_gap = ampiezza del path
- col = colonna in cui inizia il path
row1 nullo significa che il path parte solo da row2 e viceversa. Se entrambi sono
nulli la funzione non disegna niente.
===============================================================================]]
function TeamBracket:addPath(row1, row2, row_gap, col)
if not (row1 or row2) then return end
local start_row = row1 and row1 + 2
local end_row = row2
local middle_row
if start_row then
if not end_row then
row_gap = row_gap / 2
middle_row = start_row + row_gap - 1
else
middle_row = start_row + row_gap/2 - 1
end
else
row_gap = row_gap / 2
middle_row = end_row - row_gap + 2
end
if start_row then
for i = start_row, middle_row -1 do self:addGap(i, col) end
end
self:addGap(middle_row, col, 1)
if end_row then
for i = middle_row +1, end_row+1 do self:addGap(i, col) end
end
local cell = self.rows[start_row or middle_row]
:tag('td')
:attr('rowspan', row_gap)
:cssText(style.path1)
if start_row then cell:css('border-top-width', '2px') end
if end_row then cell:css('border-bottom-width', '2px') end
local cell2 = self.rows[middle_row]:tag('td'):cssText(style.path2)
if start_row then
cell2:css('border-bottom-width', '2px')
else
cell2:css('border-top-width', '2px')
end
end
function TeamBracket:getWidth(param, default)
local arg = self.args[param .. '-width']
if not arg or arg == '' then arg = default end
if tonumber(arg) ~= nil then
arg = arg .. 'px'
end
return arg
end
function TeamBracket:getTeamArgName(round, argname, team)
return string.format(self.argname_pattern, round, argname, team)
end
function TeamBracket:getTeamArg(round, argname, team)
return self.args[self:getTeamArgName(round, argname, team)]
end
function TeamBracket:getScoreArg(round, team, score)
return self.args[string.format(self.scorename_pattern, round, team, score)]
end
function TeamBracket:getRoundName(round)
local name = self.args['RD' .. round]
if name then return name end
local round_names = {"Finale", "Semifinali", "Quarti", "Ottavi"}
local roundFromLast = self.rounds - round + 1
if roundFromLast < 5 then
return round_names[roundFromLast]
else
return tostring(round) .. "° turno"
end
end
function TeamBracket:renderTeam(round, team_name, team_number, row, show_seed, top, winner, score_results, debug_info)
self:addGap(row, self.current_col, self.current_width)
self:addGap(row+1, self.current_col, self.current_width)
-- seed cell
local seedCell
local seedArg = self:getTeamArg(round, 'seed', team_number)
if show_seed and (not self.hideSeeds) then
seedCell = self.rows[row]:tag('td'):attr('rowspan', '2'):cssText(style.seed_cell):wikitext(seedArg):newline()
if self.not_draw_top then seedCell:css('border-top-width', '0' ) end
if winner then seedCell:css(style.winner[1], style.winner[2]) end
end
-- team cell
local teamCell = self.rows[row]:tag('td'):attr('rowspan', '2'):cssText(style.team_cell):wikitext(team_name):newline()
if not show_seed and (not self.hideSeeds) then
teamCell:attr('colspan', '2')
end
if self.not_draw_top then
teamCell:css('border-top-width', '0' )
end
if winner then teamCell:css(style.winner[1], style.winner[2]) end
if round == self.rounds then
if winner then
teamCell:css(style.first_place[1], style.first_place[2])
else
teamCell:css(style.second_place[1], style.second_place[2])
end
end
-- scores cells
for i = 1, self.scores[round] do
local scoreCell = self.rows[row]
:tag('td')
:attr('rowspan', '2')
:cssText(style.score_cell)
:wikitext(self:getScoreArg(round, team_number, i))
:newline()
if self.not_draw_top then scoreCell:css('border-top-width', '0') end
if score_results[i] then scoreCell:css(style.winner[1], style.winner[2]) end
end
if round == 1 then self.not_draw_top = not self.not_draw_top end
end
function TeamBracket:getScore(round, team_number, i)
return tonumber(self:getScoreArg(round, team_number, i)) or 0
end
function TeamBracket:getWinner(round, first_team_number)
local victory1 = 0
local victory2 = 0
local winners = {}
for i = 1, self.scores[round] do
local score1 = self:getScore(round, first_team_number, i)
local score2 = self:getScore(round, first_team_number+1, i)
if score1 > score2 then
victory1 = victory1 + 1
winners[i] = 1
else
victory2 = victory2 + 1
winners[i] = 2
end
end
if victory1 == victory2 then return 0, winners end
if victory1 > victory2 then return 1, winners end
return 2, winners
end
function TeamBracket:renderMatch(round, match_number, row_base, debug_info)
local team_number2 = match_number * 2
local team_number1 = team_number2 - 1
local team_name1 = self:getTeamArg(round, 'team', team_number1)
local team_name2 = self:getTeamArg(round, 'team', team_number2)
if team_name1 == 'bye' or team_name2 == 'bye' then
self.not_draw_top = false
return nil
end
local seedArg1 = self:getTeamArg(round, 'seed', team_number1)
local seedArg2 = self:getTeamArg(round, 'seed', team_number2)
local showSeed = self.showSeeds
or (seedArg1 and seedArg1 == '-')
or (seedArg2 and seedArg2 == '-')
local winner, score_results = self:getWinner(round, team_number1)
self:renderTeam(round, team_name1, team_number1, row_base, showSeed, true, winner == 1, score_results, debug_info)
self:renderTeam(round, team_name2, team_number2, row_base+2, showSeed, false, winner ==2, score_results, debug_info)
return row_base
end
function TeamBracket:render_final_3(row)
local team_name1 = self:getTeamArg(self.rounds, 'team', 3)
local team_name2 = self:getTeamArg(self.rounds, 'team', 4)
if team_name1 == 'bye' or team_name2 == 'bye' then
return nil
end
local seedArg1 = self:getTeamArg(self.rounds, 'seed', 3)
local seedArg2 = self:getTeamArg(self.rounds, 'seed', 4)
local showSeed = self.showSeeds
or (seedArg1 and seedArg1 == '-')
or (seedArg2 and seedArg2 == '-')
local winner, score_results = self:getWinner(self.rounds, 3)
local seedArg1 = self:getTeamArg(self.rounds, 'seed', 3)
local seedArg2 = self:getTeamArg(self.rounds, 'seed', 4)
self:addGap(row, self.current_col, self.current_width)
self.rows[row]:tag('td')
:cssText(style.header_third_place)
:attr('colspan', self.current_width + 1)
:wikitext("Finale 3° posto")
:newline()
self:renderTeam(self.rounds, team_name1, 3, row+2, showSeed, true, winner==1, score_results)
self:renderTeam(self.rounds, team_name2, 4, row+4, showSeed, true, winner==1, score_results)
end
function TeamBracket:AddGroup(round, row, group_number)
local name = self.args[string.format('RD%d-group%d', round, group_number)]
if name then
local span = self.current_col + self.current_width - 1
self:addGap(row, self.current_col, self.current_col + span)
self:addGap(row+1, self.current_col, self.current_col + span)
self.rows[row]:tag('td')
:attr('rowspan', '2')
:attr('colspan', span)
:cssText(style.group)
:wikitext(name)
:newline()
end
end
function TeamBracket:renderTree()
local function normal_gap(row_count, match_number)
return (row_count + 2 - match_number * 4) / match_number
end
local function compact_gap(row_count, match_number)
return (row_count - 4 * match_number) / match_number
end
-- create 3 or 1 rows for every team
local row_count = self.teams * 2 + (self.compact and 0 or (self.teams - 2))
local gap_function = (self.compact and compact_gap) or normal_gap
for i = 1, row_count do
self.rows[i] = mw.html.create('tr')
self.rows[i]:tag('td'):css('height', style.row_height):wikitext(debug and i or '')
self.last_element[i] = 1
end
self.current_col = 2
for round =1, self.rounds do
self.current_width = (self.hideSeeds and 2 or 1) + self.scores[round]
local match_number = math.pow(2, self.rounds - round)
local gap = gap_function(row_count, match_number)
local row_base = gap / 2 + (self.compact and 1 or 0)
local group_number = 1
for n = 1, match_number, 2 do
local match1 = self:renderMatch(round, n, row_base)
if round < self.rounds then
local match2 = self:renderMatch(round, n+1, row_base + gap + 4)
if not self.compact and round % 4 == 1 then
self:AddGroup(round, row_base+4, group_number)
group_number = group_number + 1
end
self:addPath(match1, match2, gap + 4, self.current_col + self.current_width + 1)
row_base = row_base + 2 * gap + 8
end
end
self.current_col = self.current_col + self.current_width + 3
end
local third_place = self:getTeamArg(self.rounds, 'team', 3)
--if true then return third_place end
if third_place then -- semifinale 3° e 4° posto
self.current_col = self.current_col - self.current_width - 3
local offset = gap_function(row_count, 1) / 2 + (self.compact and 1 or 0) + 6
if offset+5 > row_count then
for i=row_count+1, offset+5 do
self.rows[i] = mw.html.create('tr')
self.rows[i]:tag('td'):css('height', style.row_height):wikitext(debug and i or '')
self.last_element[i] = 1
end
end
self:render_final_3(offset)
end
for _,row in ipairs(self.rows) do
self.tbl:node(row)
end
end
function TeamBracket:renderHeading()
local titleRow = self.tbl:tag('tr')
local widthRow = self.tbl:tag('tr')
local blank_text = self.compact and '' or ' '
titleRow:tag('td')
local row_count = 1
widthRow:tag('td')
:css('width', style.buffer_sx_width)
:css('height', '5px')
:wikitext(debug and row_count or '')
row_count = row_count + 1
for round = 1, self.rounds do
local colspan = tostring((self.hideSeeds and 1 or 2) + self.scores[round])
local teamCell = titleRow:tag('td')
:cssText(style.header_cell)
:attr('colspan', colspan)
:wikitext(self:getRoundName(round))
:newline()
if not self.hideSeeds then
widthRow:tag('td'):css('width', self:getWidth('seed', style.score_width)):wikitext(debug and row_count or blank_text)
row_count = row_count + 1
end
teamCell = widthRow:tag('td'):css('width', self:getWidth('team', style.team_width)):wikitext(debug and row_count or blank_text)
row_count = row_count + 1
for i = 1, self.scores[round] do
widthRow:tag('td'):css('width', self:getWidth('score', style.score_width)):wikitext(debug and row_count or blank_text)
row_count = row_count + 1
end
if round < self.rounds then
titleRow:tag('td'):attr('colspan', '2')
widthRow:tag('td'):css('width', style.row_width):wikitext(debug and row_count or blank_text)
row_count = row_count + 1
widthRow:tag('td'):css('width', style.row_width):wikitext(debug and row_count or blank_text)
row_count = row_count + 1
end
end
end
function p.teamBracket(frame)
local args = getArgs(frame, {
-- se l'argomento è un seed lo ritorna così com'è anche se blank, tutti gli altri argomenti sono puliti
valueFunc = function (key, value)
if not value then return nil end
if key:find("^RD%d%d?-seed") then return value end
value = mw.text.trim(value)
if value == '' then return nil end
return value
end
}
)
--return TeamBracket:new(args)
debug = in_array(args['debug'], yes_replies) or debug
local team_bracket = TeamBracket:new(args)
return tostring(team_bracket)
end
return p