Interested Article - RoundN

Документация

Это модуль для предназначен для построения турнирных сеток различных соревнований.

Использование

Основная команда {{#invoke:RoundN|main|columns=Х]}} , где Х — количество стадий турнира (3 стадии для 8 команд, 4 стадии для 16 команд и т. д.).

Альтернативная, сокращенная форма {{#invoke:RoundN|N16}} (или любая степень двойки от 2 до 512) эквивалентна {{#invoke:RoundN|main|columns = 4}} .

Список параметров

Параметр Описание
| columns = Количество столбцов/стадий (3 стадии для 8 команд, 4 стадии для 16 и т. д.)

Примечание : Если значение параметра | columns = больше или равно 4 по умолчание отображается поле для игры за третье место. Если же | columns = менее 4 (от 2 до 8 команд) — по умолчанию скрыто.

{{{#}}} Неименованные параметры считываются последовательно группами по 5 таким образом, что:

Пример

{{#invoke:RoundN|main|columns=2
|День 1|A|'''7'''|B|5
|День 2|C|       |D|
|TDB   |A|       | |
}}
Полуфинал Финал
День 1
A 7
TDB
B 5
A
День 2
C
D

Размещение каждой группы из 5 параметров на новой строке необязательно, но облегчает чтение. Рекомендуется добавлять в виде комментариев к коду наименования параметров <!-- Дата — Место|Команда 1|Счёт 1|Команда 2|Счёт 2 --> и стадию турнира <!-- Четвертьфинал / Полуфинал / Финал -->

| style = Установите параметр | style = , чтобы добавить пользовательский CSS в таблицу. {{#invoke:RoundN|main|columns=2|style=width:20em; font-size:70%}}
Полуфинал Финал

Чтобы выровнять весь блок по центру, используйте | style = margin:auto;

| scroll_height = Для больших таблиц установите | scroll_height = на желаемую высоту. {{#invoke:RoundN|main|columns=2|scroll_height=9em}}
Полуфинал Финал
Полуфинал Финал

Примечание Работает путём дублирования всей таблицы, а затем использования CSS для привязки клона таблицы к верхней части. Предположительно, для очень больших таблиц это может привести к значительному объёму дополнительного HTML-кода для загрузки по сравнению с использованием параметра | scroll_head_unlock = .

| scroll_head_unlock = Если необходимо, чтобы заголовок прокручивался вместе с таблицей (например, если требуется большая область просмотра), установите параметр | scroll_head_unlock = yes {{#invoke:RoundN|main|columns=2|scroll_height=9em|scroll_head_unlock=yes}}
Полуфинал Финал
| skipmatchN = Если установлено, поля для N-й группы матчей отображаться не будут. Чаще всего используется для плей-офф или когда количество команд, играющих в первом раунде, не равно степени 2.

{{#invoke:RoundN|main|columns=2|skipmatch2=yes}}

Полуфинал Финал

Запись вида: | skipmatch = 1-2;4;6-7 равнозначна записи:

|skipmatch1=yes
|skipmatch2=yes
|skipmatch4=yes
|skipmatch6=yes
|skipmatch7=yes

Примечания:

  • Модуль не требует начальных нулей в параметре, то есть | skipmatch001 = совпадает с | skipmatch1 = (хотя это может облегчить чтение кода шаблона, если ввести соответствующее количество нулей);
  • 5 параметров, которые заполнили бы пропущенное поле, будут проигнорированы независимо от значения, если для параметра | omit_blanks = не установлено значение yes (см. ниже);
  • Ранее пропуск матча действовал только в первом раунде. Это ограничение больше не применяется.
| omit_blanks = Если для параметра | omit_blanks = установлено значение yes , то все параметры, которые были бы пропущены, вместо этого будут перенесены в следующее поле без пропусков. (По умолчанию это отключено, поскольку большинство шаблонов, созданных до выпуска этого модуля, должны были использовать пустые параметры в качестве заполнителей).
| bold_winner = Параметр bold_winner принимает значения high или low , что автоматически выделит жирным шрифтом текст с более высоким или более низким баллом соответственно. Другими словами, установите значение low , если выигрывает участник с более низким баллом, и high , если выигрывает участник с более высоким баллом.

Пример

{{#invoke:RoundN|main|columns=2|bold_winner=low
|День 1|A|7    |B|5
|День 2|C|7 (3)|D|5 (2)
|TDB   |B|     |D|
}}
Полуфинал Финал
День 1
A 7
TDB
B 5
B
День 2
D
C 7 (3)
D 5 (2)

Примечания:

  • При вводе результата, который включает в себя не только (например: 7 (3) ) модуль сначала удалит все нецифровые символы и объединит остальные. Так код 7 (3) и код 5 (2) будут преобразованы перед сравнением в 73 и 52 , соответственно. Это должно быть верно для большинства случаев, однако вы можете переопределить значение, используя параметр manualboldmatchN ;
  • Если значения равны или не содержат цифр, то ни одно из них не будет выделено жирным шрифтом;
  • Это не приводит к удалению уже существующего форматирования.
| manualboldmatchN = Аналогично параметрам | skipmatch = вы можете использовать | manualboldmatch = группой чтобы предотвратить автоматическое выделение жирным шрифтом ( | manualboldmatch = 1-2;4;6-7 ). Опять же, как и в случае со | skipmatch = , начальные нули могут быть добавлены по желанию.
| previewnumbers = Установите параметр | previewnumbers = yes , чтобы отображать номера рядом с каждой группой (полезно для | skipmatch = и | manualboldmatch = ) при просмотре на странице шаблона. Обратите внимание, что эти цифры не будут отображаться в статье.
| RD## = Используйте | RD## = , заменив # на нужный столбец таким образом, чтобы 1 был крайним левым кругом, а X - крайним правым, | columns = X
{{#invoke:RoundN|N128
|RD2 = {{red|'''Второй раунд'''}}
|RD7 = {{red|'''Чемпионат'''}}
|RD8 = {{red|'''Прочее'''}}
|scroll_height=15em
}}
Раунд из 128 Второй раунд Раунд из 32 Раунд из 16 Четвертьфинал Полуфинал Чемпионат
Прочее
Раунд из 128 Второй раунд Раунд из 32 Раунд из 16 Четвертьфинал Полуфинал Чемпионат
Прочее

Примечание: RD[N+1] = матч за третье место и = параметру | Consol = , если последний опущен, и наоборот RD[N+1] игнорируется, если параметр | Consol = заполнен. При этом RD[N+1] выглядит более интуитивно и предпочтительно.

| 3rdplace = По умолчанию для | 3rdplace = установлено значение yes , если | columns = больше 3, и no в противном случае.
| Consol = Установите параметр | Consol = name , чтобы изменить метку «Третье место» на «name». Рекомендуется формат RD# , где # = столбцы + 1. Данный параметр существует только для обеспечения совместимости со старыми шаблонами и признан неинтуитивным.
{{#invoke:RoundN|main|columns=1
|3rdplace=yes
|Consol=Второй финал
}}
Финал
Второй финал
| color = Включает подсветку призёров командой | color = yes
{{#invoke:RoundN|main|columns=1
|color=yes
|3rdplace=yes
||Команда А|1|Команда Б|2
||Команда В|3|Команда Г|4
}}
Финал
Команда А 1
Команда Б 2
Матч за 3-е место
Команда В 3
Команда Г 4

Примечание: Когда задано значение | color = yes , строки жёстко закодированы так, чтобы они были окрашены, как показано в примере выше. Чтобы раскрасить «правильные» ячейки, вы также должны активировать параметр | bold_winner = (см. выше). Таким образом, модуль сможет правильно идентифицировать и раскрасить победителей и проигравших.

{{#invoke:RoundN|main|columns=1
|color=yes
|bold_winner=high
|3rdplace=yes
||Команда А|1|Команда Б|2
||Команда В|3|Команда Г|4
}}
Финал
Команда А 1
Команда Б 2
Матч за 3-е место
Команда В 3
Команда Г 4
| color_repechage = Добавьте параметр | color_repechage = yes для случаев когда победитель финала награждается бронзовой медалью.
{{#invoke:RoundN|main|columns=1
|color_repechage=yes
|3rdplace=yes
||Команда А|1|Команда Б|2
||Команда В|3|Команда Г|4
}}
Финал
Команда А 1
Команда Б 2
Матч за 3-е место
Команда В 3
Команда Г 4

Примечания:

  • Когда задано значение | color_repechage = yes , строки жёстко закодированы так, чтобы они были окрашены, как показано в примере выше. Чтобы раскрасить «правильные» ячейки, вы также должны активировать параметр | bold_winner = (см. выше). Таким образом, модуль сможет правильно идентифицировать и раскрасить победителей и проигравших.
  • Когда задано значение | color_repechage = yes и название финальной секции переопределено, как в случаях, когда есть два бронзовых призёра, все видимые победители финального этапа будут окрашены в бронзовый цвет.
{{#invoke:RoundN|main|columns=3
|bold_winner     = high
|color_repechage = yes
|flex_tree       = yes
|skipmatch       = 2;4;7
|omit_blanks     = yes
|RD1             = Переигровка
|RD2             = Игры за бронзу
|RD3             = omit_label
|1|Команда А| 3|Команда Б| 2
|2|Команда В| 6|Команда Г| 5
|3|Команда Д| 8|Команда Е| 7
|4|Команда Ж|10|Команда З|11
}}
Переигровка Игры за бронзу
1
Команда А 3
3
Команда Б 2
Команда Д 8
Команда Е 7
2
Команда В 6
4
Команда Г 5
Команда Ж 10
Команда З 11
| team-width = При необходимости установите параметр | team-width = на желаемую ширину. (по умолчанию 170px). Менять ширину данного столбца без особой необходимости не рекомендуется так как она подбирается автоматически.
| score-width = При необходимости установите параметр | score-width = на желаемую ширину. (по умолчанию 30px) Менять ширину данного столбца без особой необходимости не рекомендуется так как она подбирается автоматически.
| widescore = Значение | widescore = yes в основном равно | score-width = 40 . Предусмотрено для совместимости. Игнорируется, если задан параметр | score-width = .
| score-boxes = Установите в параметре | score-boxes = желаемое количество полей для подсчета очков за матч. (по умолчанию 1). За номером может следовать + sum , что добавит еще одно поле для подсчёта очков к сумме всех остальных.
{{#invoke:RoundN|main|columns=3
|score-boxes     = 3+ sum
|bold_winner     = high
|color_repechage = yes
|flex_tree       = yes
|skipmatch       = 2;4;7
|omit_blanks     = yes
|RD1             = Переигровка
|RD2             = Игры за бронзу
|RD3             = omit_label
|1|Команда А| 3| 3| 3|Команда Б| 2| 2| 2
|2|Команда В| 6| 6| 6|Команда Г| 5| 5| 5
|3|Команда Д| 8| 8| 8|Команда Е| 7| 7| 7
|4|Команда Ж|10|10|10|Команда З|11|11|11
}}
Переигровка Игры за бронзу
1
Команда А 3 3 3 9
3
Команда Б 2 2 2 6
Команда Д 8 8 8 24
Команда Е 7 7 7 21
2
Команда В 6 6 6 18
4
Команда Г 5 5 5 15
Команда Ж 10 10 10 30
Команда З 11 11 11 33
| flex_tree = Установите параметр | flex_tree = yes , чтобы сделать скобки по вертикали более компактными. То есть, чтобы между матчами одного раунда оставалось меньше места.
| short_brackets = Установите параметр | short_brackets = yes , чтобы сделать скобки более компактными по горизонтали. То есть, чтобы между матчами следующих раундов оставалось меньше места.
{{#invoke:RoundN|main|columns=3
|short_brackets = yes
|flex_tree      = yes
|score-boxes    = 3+ sum
|bold_winner    = high
|skipmatch      = 2;4;7
|omit_blanks    = yes
|RD1            = Переигровка
|RD2            = Игры за бронзу
|RD3            = omit_label
|1|Команда А| 3| 3| 3|Команда Б| 2| 2| 2
|2|Команда В| 6| 6| 6|Команда Г| 5| 5| 5
|3|Команда Д| 8| 8| 8|Команда Е| 7| 7| 7
|4|Команда Ж|10|10|10|Команда З|11|11|11
}}
Переигровка Игры за бронзу
1
Команда А 3 3 3 9
3
Команда Б 2 2 2 6
Команда Д 8 8 8 24
Команда Е 7 7 7 21
2
Команда В 6 6 6 18
4
Команда Г 5 5 5 15
Команда Ж 10 10 10 30
Команда З 11 11 11 33

Недокументированные параметры

Обратите внимание, что некоторые из этих функций, возможно, не были задокументированы из-за неполной реализации.

  • | no_column_head =
  • | branch_upwards =
local p = {
	RD = {'Четвертьфинал', 'Полуфинал', 'Финал', 'Матч за 3-е место'
	},
-- Цвета текста и фона сопряжены, и когда вы устанавливаете один, вы должны установить другой (специальные возможности)
	textColor = {head = '#202122', '#202122', '#202122', '#202122', '#202122'
	},
	bgColor = {
		head = '#f2f2f2', 'gold', 'silver', '#c96', '#f9f9f9'
	},
	reuseStr = {},
	saveStr = function(self, name, ...)
		if not self.reuseStr[name] then
			self.reuseStr[name] = table.concat{...}
		end
		return self.reuseStr[name]
	end
}

-- Обеспечивает удобное сокращение вплоть до {{#invoke:RoundN|N512}} = {{invoke:RoundN|main|columns = 9}}
for columns = 1, 9 do
	local N = math.pow(2, columns)
	p['N' .. N] = function(frame)
		return p.main(frame.args, columns)
	end
	p['n' .. N] = p['N' .. N]	-- чтобы сделать регистр нечувствительным
end

-- Экономит память и позволяет избежать ошибок при использовании nil в качестве таблицы, предоставляя временную таблицу;
-- при использовании nil как false; используйте 'table(k)' для поиска таблицы[k]
p.nilAsTab = {
	__index = function(t, i)
		return setmetatable({}, setmetatable(p.nilAsTab, {__index = {t = t, i = i}}))
	end,
	__newindex = function (pt, pi, v)	-- сохраняем новые значения в постоянной таблице, а не во временной
		rawset(p.nilAsTab.t, p.nilAsTab.i, {})[p.nilAsTab.i][pi] = v
		setmetatable(p.nilAsTab.t[p.nilAsTab.i], {__call = p.nilAsTab.__call})
	end,
	__call = function(t, i)
		return t and rawget(t, i)
	end
}
-- never assign a value to these or they will stop being empty
local infiniteEmpty = setmetatable({}, {__index = setmetatable({}, p.nilAsTab), p.nilAsTab})	-- infiniteEmpty[1][2][3]...[infinity] = {}
local callableEmpty = setmetatable({}, p.nilAsTab)

local rowNum, head, m, col, tab, esc = {}, {}, {num = 1, phase = 0, bold = infiniteEmpty}, {}, mw.html.create'table', {
	bs = require'Модуль:Escape',-- backslash
	comma = {['(%([^,]*),([^%)]*%))'] = '%1|@!#|%2'},	-- escape commas in ()
}
local nodeFunc = {
	scanPattern = function(self, args, step)
		self.pattern = nil
		if args[step] then
			self.pattern, self.nonFunc = string.match(esc.bs:text(args[step]), '^node_function{(.-)}(.*)')
		end
		if self.pattern then
			for k, v in pairs(esc.comma) do
				self.pattern = self.pattern:gsub(k, v)
			end
			self.nonFunc = self.nonFunc and esc.bs:undo(self.nonFunc)
			self.pattern = mw.text.split(self.pattern, '%s*,%s*')
			for k, v in ipairs(self.pattern) do
				local func, arg = string.match(v, '^(%w+)%(?([^%)]*)')
				if func and self[func] and self[func].main then
					self.pattern[k] = func
					if arg then
						for x, y in pairs(esc.comma) do
							arg = esc.bs:undo(arg):gsub(y:gsub('%%%d', ''), x:match('%)([^%(])%(') or x:gsub('\\', ''))
						end
						self[func].arg = self[func].arg or {}
						self[func].arg[m.num] = arg
					end
				end
			end
		end
		return self.pattern
	end,
	helper = {
		topBranch = function() --node is top of fork if top is 0
			return (m.num - col.top) % 2
		end,
		addText = function(text)
			if text and text ~= '' then
				tab.r:wikitext(text)
			end
		end
	},
	line = { -- this node is omitted and replaced with a line
		main = function(x)
			local h = p.getNodeFunc()
			if m.available then
				local text, topId, isTop, notTop = h.line.arg[m.num] or '', h.topBranch()
				isTop = topId == 0
				notTop = {[isTop and 1 or 0] = p.reuseStr.solid}
				for k = 0, 1 do
					tab.r = rowNum[m.r + k * 4]:tag'td'
:css(notTop[k] and {
						[isTop and 'border-top' or 'border-bottom'] = notTop[k]
					} or {})
:attr{
						rowspan = ({[0] = 4, 2})[k],
						colspan = p.colspan
					}
					h.addText(text or h.nonFunc)
					text = nil
				end
				m.available = false
			else
				return nil
			end
			return x
		end
	},
	bridge = {	-- Draw a line to the neighboring node in the same column that is not connected to the current node
		main = function(x)
			local h = p.getNodeFunc()
			h.bridge.lay[col.c][m.num - col.top + 1 + (h.topBranch() == 1 and 1 or -1)] = true
			h.addText(nonFunc)
			return x
		end,
		lay = setmetatable({}, p.nilAsTab)
	},
	canvas = { -- Merges all cells in node. Content will be the next parameter
		main = function(x)
			local h = p.getNodeFunc()
			if m.available then
				tab.r = rowNum[m.r]:tag'td'
:attr{
					rowspan = 6,
					colspan = p.colspan
				}
				h.addText(h.nonFunc)
				m.available = false
				return x
			else
				return nil
			end
		end
	},
	orphan = { -- sets a flag for skipMatch to be set by p._main
		main = function(x)
			p.getNodeFunc().orphan.num = m.num
			return x
		end
	},
	skipAllowed = { -- table of supported node functions when node is skipped (i.e. by skipmatch)
		bridge = true,
		canvas = true
	}
}

setmetatable(nodeFunc.helper, {__index = nodeFunc})
function p.getNodeFunc()
	return nodeFunc.helper
end

local function newRow(bodyRow)
	local first = p.flex_tree.merge and mw.clone(p.flex_tree.cell) or p.flex_tree.cell
	tab.r = tab:tag'tr'
:node(first)
	if bodyRow then
		table.insert(rowNum, bodyRow, tab.r)
		if p.flex_tree.merge then
			rowNum[bodyRow].first = first
			rowNum[bodyRow].first.unchanged = true
		end
	end
end

local function drawHead(text, row3rd)
	local td = (row3rd and rowNum[row3rd]:tag'td':attr{rowspan = 2}
		or head.row:tag'td')
:attr{colspan = p.colspan}
	if text ~= 'omit_label' then
		td:wikitext(text):css{
			['text-align'] = 'center',
			border = '1px solid #aaa',
			['background-color'] = p.bgColor.head,
			color = p.textColor.head
		}
	end
end

local function spacer(width)
	tab.r:tag'td'
:attr{width = width}
:wikitext(p.no_column_head and '' or '&nbsp;')
end

local function dpBox(v, r)
	p.dpBoxBase = p.dpBoxBase or mw.html.create'td':attr{rowspan = 2, colspan = p.colspan}
	if not v then
		p.dpBoxEmpty = p.previewnumbers and mw.clone(p.dpBoxBase) or p.dpBoxEmpty or mw.clone(p.dpBoxBase):wikitext(p.flex_tree.wt)
		rowNum[r]:node(p.dpBoxEmpty)
	else
		rowNum[r]:node(mw.clone(p.dpBoxBase):wikitext(v))
	end
end

p.scoreWasher = {
	numberFormat = '%-?%d+%.?%d*',
	main = function (self, s)
		if s then
			for _, cycle in ipairs(self.cycles) do
				s = s:gsub(unpack(cycle))
			end
			if p.scoreSumBox and self.plus then
				local t = 0
				for _, part in ipairs(mw.text.split(s, self.plus)) do
					t = t + (tonumber(part:match('%-?%d+%.?%d*')) or 0)
				end
				return t
			end
			return tonumber(s:match(self.numberFormat)) or math.huge
		end
		return 0
	end,
	spin = function(self, v)
		table.insert(self, v)
		return self
	end,
	load = function (self, cycle)
		local wash, rinse = 0, {spin = self.spin}
		for v in cycle:gfind('%(([^%(%)]-)%)') do
			if v == '_plus_' then
				self.plus = v
				rinse:spin(v)
				cycle = cycle:gsub('%(_plus_%)', '', 1)
			else
				wash = wash + 1
				rinse:spin('%'):spin(wash)
			end
		end
		table.insert(self.cycles, {esc.bs:undo(cycle, '%%'), table.concat(rinse)})
	end,
	init = function(self, setting)
		self.cycles = {original = setting}
		for cycle in (setting and esc.bs:text(setting) or '{<.->} {[^%d]*}'):gfind('{(.-)}') do
			self:load(cycle)
		end
	end,
	sum = function (clean)
		local sum = {0, 0}
		for _, box in ipairs(clean) do
			for team, score in ipairs(box) do
				sum[team] = sum[team] + score
			end
		end
		return unpack(math.max(unpack(sum)) == math.huge and {'&mdash;', '&mdash;'} or sum)
	end
}

local function boldWin(s1, s2)
	return setmetatable(p.bold and s1 ~= s2 and (math[({'min', 'max'})[p.bold]](s1, s2) == s1 and {true
	} or {[2] = true
	}) or callableEmpty, p.nilAsTab)
end

local function maxSpan(span, start, rows)
	return math.min(span, math.max(0, rows - start + 1))
end

-- in case of templates like RDseed need padding value
p.teamBoxPadding = function()
	return '.6ex'
end
p.teamBoxPadTab = {padding = '0 ' .. p.teamBoxPadding()}
p.teamBoxNormal = {border = '1px solid #aaa', ['background-color'] = p.bgColor[4], color = p.textColor[4]}
local function teamBox(v, r, f)
	if p.flex_tree.merge and not v and f.phase == 2 then
		for i = -2, 0 do
			if rowNum[r + i].first.unchanged then
				rowNum[r + i].first.unchanged = nil
				rowNum[r + i].first:node(p.unflex_div)
			end
		end
		tab.r:attr{rowspan = 4}:css{['vertical-align'] = 'center'}
	else
		if not p.bold then
-- обратная совместимость (wikitemplates bold each arg individually)
			local hasBold, b = tostring(v):gsub("([^']*)'''([^']*)", '%1<b>%2</b>')
			if b == 1 then
				v = hasBold
			end
		end
		local cell
		if f[1] then
			cell = f.sumBox and f.sumBox[1] and {
				padding = f.sumBox[1]
			} or {['border-left'] = f.borderLeft}
			cell['text-align'] = v and f[1]
		else
			cell = p.teamBoxPadTab
		end
		local text = v or f[1] and '' or '&nbsp;'
		if f.bold then
			text = mw.ustring.gsub(text, '(%(%[%[[^%[%]]*%]%]%))', '<span style="font-weight:normal">%1</span>')
		end
		tab.r = rowNum[r]:tag'td'
:css(p.teamBoxCSS)
:css(cell)
:attr{rowspan = 2}
:node(mw.html.create(f.bold and 'b'):wikitext(text))
	end
end

function p._main(args)
	function args:clean(key, params) -- prevent html comments from breaking named args and reduces repeat concatenation
		params = params or {}
		local clean = args[key] or params.ifNil
		if clean then
			params.append = params.append or ''
			clean = mw.text.decode(clean):gsub('<!%-.-%->', ''):gsub(params.pattern or '[^%w-;%.]', '') .. params.append
			clean = clean ~= params.append and clean or params.ifNil
		end
		args[key] = params.keepOld and args[key] or clean
		return clean
	end
	p.cols = tonumber(args:clean('columns', {pattern = '%D'}))
	p.tCols = (tonumber(args:clean('final_RDs_excluded', {pattern = '%D'})) or 0) + p.cols
	local matchPer = {
		pattern = '%d*per%d+[%-x]%d+',
		vals = '(%d*)per(%d+)([%-x])(%d+)'
	}
	local skipMatch, unBold = {}, {}	-- (skip|manualbold)match# to boolean
	for k, _ in pairs(args) do
		local mType, mNum = string.match(k, '^(%l+)match(%d*)$')
		mType, mNum = ({skip = skipMatch, manualbold = unBold})[mType], tonumber(mNum)
		if mType then
			if mNum then
				mType[mNum] = args:clean(k) == 'yes' or args[k] == 'true'
			else
				for pattern in args:clean(k, {ifNil = ''}):gfind(matchPer.pattern) do
					local d1, period, op, d2 = pattern:match(matchPer.vals)
					d1 = tonumber(d1) or 1
					d2 = op == '-' and d2 or (d1 + period * (d2 - 1))
					for y = d1, d2, period do
						mType[y] = true
					end
				end
				for _, x in ipairs(mw.text.split(args[k]:gsub(matchPer.pattern, ''):gsub('[;%-%a][;%-%a]+', ';'):match('^;*(.-)[;%-]*$'), ';')) do
					x = mw.text.split(x, '-')
					for y = tonumber(x[1]) or 1, tonumber(x[2] or x[1]) or 0 do
						mType[y] = true
					end
				end
			end
		end
	end
	for _, v in ipairs({ -- more args to boolean
		'widescore', 'color', 'color_repechage', '3rdplace', 'omit_blanks', 'scroll_head_unlock',
		'previewnumbers', 'flex_tree', 'no_column_head', 'short_brackets', 'branch_upwards'
	}) do
		if args[v] and (p[v] == nil or type(p[v]) == 'boolean') then
			p[v] = args:clean(v) == 'yes' or args[v] == 'true'
		end
	end
	p.namespace = mw.title.getCurrentTitle().namespace
	p.previewnumbers = p.namespace ~= 0 and p.previewnumbers
	p.scoreWasher:init(args['score-clean'])
	p.scoreWasher.demo = args.demoWash and tonumber(args:clean('demoWash', {pattern = '%D'}), 10)
	p.scoreSumBox = args['score-boxes'] and args['score-boxes']:match('%d ?%+ ?sum')
	p.bold = ({low = 1, high = 2})[args:clean('bold_winner')] or p.scoreSumBox and 2
	local sumBox = p.scoreSumBox and 1 or 0
	p.scoreBoxes = (tonumber(args:clean('score-boxes', {pattern = '%D'})) or 1) + sumBox
	p.scoreSumBox = p.scoreBoxes > 0 and p.scoreSumBox or nil
	local boxStyle = p.scoreBoxes > 1 and (p.scoreSumBox and setmetatable({{}, [p.scoreBoxes] = {'0 1ex'}},
		{__call = function(t, i) if t[i] then return nil
			end
			return 0
		end
	}) or setmetatable({}, {__call = function() return 0
		end
	})) or setmetatable({}, {__call = function() return nil
		end
	})
	p.colspan = p.scoreBoxes > 0 and (p.scoreBoxes + 1) or nil
	local nodeArgs = {
		score = p.scoreBoxes - sumBox,
		team = {offset = 1 + p.scoreBoxes - sumBox}
	}
	nodeArgs.all = 1 + nodeArgs.team.offset * 2
	nodeArgs.tableSum = {
		__add = function(v, t)
			if # t == 3 then
				return v + nodeArgs.all
			end
			local s = v
			for i, n in ipairs(t) do
				s = s + n
			end
			return s	-- [[ + (p.scoreSumBox and #t == 3 and -2 or 0) -- merging disabled with score boxes, uncomment if enable]]
		end
	}
	nodeArgs.team[1] = 1 -- constant to be replaced later by new param
	nodeArgs.team[2] = nodeArgs.team[1] + nodeArgs.team.offset
	nodeArgs.blank = setmetatable({}, nodeArgs.tableSum)
	p.unflex_div = mw.html.create'div'
:css{overflow = 'hidden', height = '1ex'}
:wikitext'&nbsp;'
	p.flex_tree = setmetatable({}, {__index = {
			merge = p.flex_tree and p.scoreBoxes == 0,
			wt = p.flex_tree and '' or '&nbsp;',
			cell = mw.html.create'td'
:node(not p.flex_tree and p.unflex_div or nil)
		}})
	if args:clean'scroll_height' then
		local fontSize, fontUnit = args.style and args.style:match('font%-size *: *(%d+)([^ ]+)')
		if fontSize then
			local units = {em = 1, ex = 2, ['%'] = 0.01}
			fontSize, fontUnit = {fontSize * fontUnit}
		end
	end
	tab
:cssText(table.concat{args.scroll_height and 'padding' or 'margin', ':', fontSize
	and (math.ceil(fontSize * 10) / 10) or '.9', 'em 2em 1em 1em;border:0;', fontSize
	and '' or 'font-size:90%;border-collapse:separate;', args.style})
:attr{cellpadding = 0, cellspacing = 0}
	if not p.no_column_head then -- headings row
		newRow()
		head.row = tab.r
:css{['white-space'] = args.scroll_height and 'nowrap'}
		newRow()
	else
		tab.r = tab:tag'tr'
		tab.r:tag'td'
	end
	local sp = { -- ширина столбцов по умолчанию
		args['team-width'] or 170,
		p.widescore and 40 or 30,
		p.short_brackets and 6 or 15,
		p.short_brackets and 4 or 20
	}
	local scoreWidth = args:clean('score-width', {pattern = '[^%d;]'}) and mw.text.split(args['score-width'], ';') or {}
	scoreWidth[1] = tonumber(scoreWidth[1], 10)
	if p.scoreSumBox and #scoreWidth ~= 1 then
		local _scoreWidth = {}
		for k = 1, p.scoreBoxes - 1 do
			_scoreWidth[k] = tonumber(scoreWidth[k], 10) or math.ceil(sp[2] * 0.75)
		end
		setmetatable(scoreWidth, _scoreWidth)
	end
	local head_br = {
		count = 0,
		compare = function (self, text)
			if text and args.scroll_height then
				local _, count = text:gsub('<br[ >/]', '%1')
				self.count = math.max(self.count, count)
			end
			return text
		end
	}
	p.branch_upwards = p.branch_upwards and 0
	for k = 1, p.cols do
		if k > 1 then
			spacer(sp[3])
			spacer(sp[4])
			if not p.no_column_head then
				head.row:tag'td':attr{colspan = 2}
			end
		end
		spacer(sp[1])
		for s = 1, p.scoreBoxes do
			spacer(#scoreWidth == 1 and scoreWidth[1] or scoreWidth[s] or sp[2])
		end
		if not p.no_column_head then
			head.wt = head_br:compare(args:clean('RD' .. k, {
				pattern = ''})) or p.RD[# p.RD + k - p.tCols - 1] or ('Раунд из ' .. math.pow(2, p.tCols - k + 1))
			drawHead(head.wt)
		end
	end
	sp.row = tab.r
	col.tot = math.pow(2, p.tCols - 1)
	local step, bump, bumpBase, rows = 1, 0, mw.html.create'td':attr{colspan = p.colspan}, col.tot * 6 -- Begin body row output
	args.line_px = table.concat{
		args:clean('line_px') or 3,
		args.line_px ~= '0' and 'px' or nil}
	tab.line = { -- reduces concats and 'or' statements
		{[true] = args.line_px, [false] = 0},
		args.line_px:rep(2):gsub('(%a)(%d)', '%1 %2', 1)
	}
	p['3rdplace'] = p.tCols == p.cols and (p['3rdplace'] or p.cols > 3 and nil == p['3rdplace'] and not p.no_column_head)
	if p['3rdplace'] then
		p.textThird = args.Consol or args['RD' .. (p.cols + 1)] or p.RD[4]
		local no3rdText = p.no_column_head or p.textThird and p.textThird:match('omit_label')
		rowNum.third = math.max(math.pow(2, p.branch_upwards and -3 or p.cols - 2) * 9 + (no3rdText and 4 or 9), no3rdText and 12 or 17, rows)
	end
	for r = 1, rowNum.third or rows do
		newRow(r)
	end
	p:saveStr('solid', tab.line[1][true], ' solid')
	p.cornerDiv = mw.html.create'div':css{height = tab.line[1][true], ['border-right'] = p.reuseStr.solid}
	for c = 1, p.cols do
		col.c = c
		local bumps = bump
		if c > 1 then
			col.tot = col.tot + math.pow(2, p.tCols - c)
			if p.branch_upwards then
				bumps = 0
				rowNum[1]:tag'td':attr{rowspan = 4}
			else
				rowNum[1]:node(c < p.cols and mw.clone(bumpBase):attr{rowspan = bump})
			end
		end
		col.top = m.num
		p.span = p.tCols > c and bump * 2 or p.branch_upwards or math.max((bump - 1) / 2, 2)
		col.color_repechage = p['color_repechage'] and ((c == p.tCols) or ((c == p.tCols - 1) and skipMatch[math.pow(2, p.tCols) - 1]))
		col.show3rd = p['3rdplace'] and c == p.tCols and rowNum.third
		local colorFinal, bumpMid = p.color and c == p.tCols, p.span > 0 and mw.clone(bumpBase):attr{rowspan = p.span} or nil
		for r = 1, col.show3rd or rows, 2 do
			m.r = r + bumps
			if col.show3rd or rowNum[m.r] and m.num <= col.tot then
				if m.phase == 0 then
					m.showBox = setmetatable({1, nodeArgs.team.offset, nodeArgs.team.offset}, nodeArgs.tableSum)
					if nodeFunc:scanPattern(args, step) then
						nodeFunc.called = {}
						m.available = true
					else
						m.available = nil
					end
				end
				if skipMatch[m.num] then
					if m.phase == 0 then
						if nodeFunc.pattern then
							for x, y in ipairs(nodeFunc.pattern) do
								if nodeFunc.skipAllowed[y] then
									nodeFunc.called[y] = nodeFunc[y].main(x)
								end
							end
						end
						local canvas = nodeFunc.pattern and nodeFunc.called.canvas and 6
						rowNum[m.r + (canvas or 0)]:tag'td':attr{
							rowspan = maxSpan((canvas and 0 or 6) + bump * 2, m.r + (canvas or 0), rows),
							colspan = p.colspan}
					elseif m.phase == 2 then
						if nodeFunc.pattern and (nodeFunc.called.bridge or nodeFunc.called.canvas) then
							step = step + 1
						end
						m.num = m.num + 1
						step = step + (p.omit_blanks and 0 or m.showBox)
						bumps = bumps + (col.show3rd and 0 or maxSpan(p.span, m.r, rows))
					end
				elseif m.phase == 0 then
					if nodeFunc.pattern then
						for x, y in ipairs(nodeFunc.pattern) do
							if nodeFunc[y] and nodeFunc[y].main then
								nodeFunc.called[y] = nodeFunc[y].main(x)
							end
						end
						if m.available == false then
							m.showBox = nodeArgs.blank
							step = step + 1
						end
					end
					if m.showBox[1] then
						if col.show3rd then
							col.show3rd = (m.num - col.top) * 2
							if col.show3rd == 2 then
								if p.textThird:match('omit_label') then
									p.textThird = nil
								end
								if rowNum[rows + 1] and p.cols > 1 then	-- if 3rd place extends below bottom cell
									rowNum[rows + 1]:tag'td':attr{
										rowspan = m.r + 9 - rows - (text and 0 or 2),
										colspan = (p.cols - 1) * (3 + p.scoreBoxes)}
								end
								if p.tCols == 1 then
									bumps = p.textThird and 3 or 0
								elseif p.branch_upwards then
									r = 7
									bumps = p.textThird and 2 or 0
								end
								m.r = r + bumps
								if p.textThird then
									drawHead(p.textThird, m.r)
									bumps = bumps + 2
									m.r = r + bumps
								end
							end
						end
						dpBox(nodeFunc.pattern and nodeFunc.nonFunc or args[step], m.r)
						if p.previewnumbers then
							rowNum[m.r].nodes[# rowNum[m.r].nodes]
:tag'div'
:css{float = 'left', border = '1px solid red', padding = '0 .5ex', ['color'] = 'red'}
:wikitext(m.num)
:attr{title = 'Номер виден только за пределами пространства статьи (например, в шаблонах) если есть параметр |numberpreview=yes'}
						end
					end
					if p.colspan then
						m.nonEmpty = {}
						for s = step + 2, step + nodeArgs.team.offset do
							local i = {s, s + nodeArgs.team.offset}
							if args[i[1]] or args[i[2]] then
								table.insert(m.nonEmpty, i)
							end
						end
						if p.bold and m.showBox[2] and m.showBox[3] and not unBold[m.num] then
							m.bold = {box = {}, clean = {}}
							local notSummed = not p.scoreSumBox or #m.nonEmpty < 2
							for s, i in ipairs(m.nonEmpty) do
								m.bold.clean[s] = {
									p.scoreWasher:main(args[i[1]]),
									p.scoreWasher:main(args[i[2]])}
								m.bold.box[s] = notSummed and boldWin(m.bold.clean[s][1], m.bold.clean[s][2]) or callableEmpty
							end
							if p.scoreSumBox and m.nonEmpty[2] then
								local i = {-step, -step - 1}
								table.insert(m.nonEmpty, i)
								args[i[1]], args[i[2]] = p.scoreWasher.sum(m.bold.clean)
								m.bold.box[p.scoreBoxes] = boldWin(args[i[1]], args[i[2]])
							end
							getmetatable(boxStyle).__index = p.scoreSumBoxes and {
								[#m.nonEmpty] = boxStyle[p.scoreBoxes]}
							m.bold.win = m.bold.box[#m.nonEmpty] or callableEmpty
						else
							m.bold = infiniteEmpty
						end
					end
				else
					if m.showBox[m.phase] then
						if col.color_repechage then
							col.color_repechage = 2
						end
						if p.bold then
							if m.bold.win(m.phase) and (colorFinal or col.color_repechage) then
								color_index = 1 + (col.show3rd or 0) + (col.color_repechage or 0)
							elseif m.bold.box[#m.nonEmpty] then
								color_index = 2 + (col.show3rd or 0) + (col.color_repechage or 0)
							else
								color_index = 4
							end
							p.teamBoxCSS = (colorFinal or col.color_repechage) and {
								border = p.teamBoxNormal.border, ['background-color'] = p.bgColor[color_index], color = p.textColor[color_index]
							} or p.teamBoxNormal
						else
							p.teamBoxCSS = (colorFinal or col.color_repechage) and {
								border = p.teamBoxNormal.border, ['background-color'] = p.bgColor[m.phase + (col.show3rd or 0) + (col.color_repechage or 0)],
								color = p.textColor[m.phase + (col.show3rd or 0) + (col.color_repechage or 0)]
							} or p.teamBoxNormal
						end
						local f = {phase = m.phase, bold = m.bold.win(m.phase)}
						teamBox(args[step + nodeArgs.team[m.phase]], m.r, f)
						f[1] = 'center'
						if p.colspan then
							if m.nonEmpty[1] then
								local loneSum
								if #m.nonEmpty < p.scoreBoxes then
									loneSum = #m.nonEmpty == 1 and boxStyle[p.scoreBoxes]
									tab.r:attr{colspan = 1 + p.scoreBoxes - #m.nonEmpty}
								end
								for s, i in ipairs(m.nonEmpty) do
									f.borderLeft = boxStyle(s)
									f.sumBox = loneSum or boxStyle[s]
									f.bold = m.bold.box[s](m.phase)
									teamBox(args[i[m.phase]], m.r, f)
								end
							else
								for s = 1, p.scoreBoxes do
									f.borderLeft = boxStyle(s)
									teamBox(nil, m.r, f)
								end
							end
						end
					end
					if m.phase == 2 then
						col.show3rd = col.show3rd ~= 2 and col.show3rd or nil
						if p.scoreWasher.demo and p.scoreWasher.demo == m.num and p.namespace ~= 0 then
							table.insert(m.bold.clean, 1, {
								args[step + nodeArgs.team[1]],
								args[step + nodeArgs.team[2]]
							})
							return table.concat{
								'Score data for match specified by <code>|demoWash=</code>:<br>',
								mw.dumpObject{scores = m.bold.clean, cycles = p.scoreWasher.cycles,
									sum = p.scoreSumBox and {m.nonEmpty[#m.nonEmpty][1], m.nonEmpty[#m.nonEmpty][1]}
								},
								'<table>',
								tostring(sp.row), '<tr>',
								tostring(rowNum[m.r - 4]), '<tr>',
								tostring(rowNum[m.r - 2]), '<tr>',
								tostring(rowNum[m.r]),
								'</table>',
							}
						end
						if nodeFunc.orphan.num == m.num then
							skipMatch[m.num] = 'orphan'
						end
						step = step + m.showBox
						m.num = m.num + 1
						if bump > 0 and rowNum[m.r + 2] and not (nodeFunc.pattern and nodeFunc.called.canvas) then
							bumps = bumps + p.span
							rowNum[m.r + 2]:node(bumpMid)
						end
						r = r + (col.show3rd or bump)
					end
				end
				m.phase = (m.phase + 1) % 3
			end
		end
		if p.cols > c then -- draw lines to next round
			p.unit = bump + 3
			bump = 3 * math.pow(2, c) - 3
			bumps = p.branch_upwards and 4 or (p.unit + 1)
			rowNum[1]
:tag'td':attr{rowspan = bumps}
			if not p.branch_upwards then
				rowNum[1]:tag'td'
:attr{rowspan = (p.branch_upwards or bump) + 4}
:css(nodeFunc.bridge.lay[c](0) and {['border-right'] = p.reuseStr.solid} or {})
			end
			col.n = 0
			for r = bumps + 1, rows, p.unit * 2 do
				tab.r = rowNum[r]:tag'td'
				local interval = ((r - bumps - 1) / (p.unit * 2)) % 4
				if interval % 2 == 0 then
-- col.t and col.t2 control whether lines are drawn
					col.t = col.t2 or skipMatch[col.tot + col.n / 2 + 1] and 3 or ((skipMatch[col.top] and 1 or 0) + (skipMatch[col.top + 1] and 2 or 0))
					col.n = col.n + 2
					col.t2 = skipMatch[col.tot + col.n / 2 + 1] and 3 or ((skipMatch[col.top + col.n] and 1 or 0) + (skipMatch[col.top + col.n + 1] and 2 or 0))
					if col.t == 0 then
						tab.r
:attr{rowspan = maxSpan(p.unit * 2, r, rows)}
:css(skipMatch[col.tot + col.n / 2] and {} or {border = p.reuseStr.solid, ['border-left'] = 0})
					else
						tab.r
:attr{rowspan = maxSpan(p.unit, r, rows)}
:cssText(col.t == 2 and p:saveStr('topRight', 'border-width:', tab.line[2], ' 0 0;border-style:solid') or col.t == 1
	and (nodeFunc.bridge.lay[c](col.n - 2) and p:saveStr('right', ';border-right:', p.reuseStr.solid) or 'vertical-align:bottom') or nil)
:node(col.t == 1 and interval > 0 and not nodeFunc.bridge.lay[c](col.n - 2) and p.cornerDiv)
						rowNum[r + (p.branch_upwards and (4 - bump) or p.unit)]:tag'td'
:attr{rowspan = maxSpan(p.unit, r + p.unit, rows)}
:cssText(col.t == 1 and p:saveStr('bttmRght', 'border-width:0 ', tab.line[2], ' 0;border-style:solid') or col.t == 2
	and (nodeFunc.bridge.lay[c](col.n + 2) and p:saveStr('right', ';border-right:', p.reuseStr.solid) or 'vertical-align:top') or nil)
:node(col.t == 2 and interval ~= 2 and not nodeFunc.bridge.lay[c](col.n + 2) and p.cornerDiv)
					end
					col.t = {col.t < 3,
						rowNum[r + p.unit * 5] and col.t2 < 3 or false}
					rowNum[r + (p.branch_upwards or p.unit)]:tag'td'
:attr{rowspan = maxSpan(p.unit * 4, r + (p.branch_upwards and (4 - bump) or p.unit), rows)}
:css(interval == 0 and (col.t[1] or col.t[2]) and {['border-width'] = table.concat{
							tab.line[1][col.t[1]], ' 0 ', tab.line[1][col.t[2]]},
						['border-style'] = 'solid'} or {})
				else
					tab.r
:attr{rowspan = maxSpan(p.unit * 2, r, rows)}
:css(nodeFunc.bridge.lay[c](col.n) and {['border-right'] = p.reuseStr.solid} or {})
				end
			end
		end
	end
	local lock_height = (head_br.count or 0) + 1
	return args.scroll_height and mw.html.create'div'
:cssText'border-bottom:1px solid #eee;display:inline-block'
:node(not (p.scroll_head_unlock or p.no_column_head) and mw.html.create'div'
:css{overflow = 'hidden', height = lock_height * 1.4 + 1.6 .. 'em', ['border-bottom'] = 'inherit', ['margin-right'] = '17px'}
:node(mw.clone(tab)))
:tag'div'
:css{
		['overflow-y'] = 'scroll',
		['max-height'] = tonumber(args.scroll_height, 10) and args.scroll_height .. 'px' or args.scroll_height}
:node(not (p.scroll_head_unlock or p.no_column_head) and tab:css{
		['margin-top'] = math.floor(-10 * (lock_height * 1.4 + 1.6) / (fontSize or .9)) / 10 .. 'em',
		['padding-top'] = '-3px'} or tab)
:done() or tab
end
function p.main(frame, columns)
	local args = require'Модуль:Arguments'.getArgs(frame, {trim = false})
	args.columns = args.columns or columns
	return p._main(args)
end

function p.seed(frame)
	local parent = frame:getParent() or frame
	local function arg(k, alt)
		return parent.args[k] or frame.args[k] or alt
	end
	local padding, width = arg(2, p.teamBoxPadding()), arg(3, arg('widescore') and 40 or 30)
	padding = tonumber(padding) and tonumber(padding) .. 'px' or padding
	width = tonumber(width) and tonumber(width) .. 'px' or width
	return mw.html.create'div'
:css{
		margin = ('-1px %s -1px -0.7ex'):format(padding, padding),
		float = 'left',
		['background-color'] = p.bgColor.head,
		border = '1px solid #aaa',
		color = p.textColor.head,
		['text-align'] = 'center',
		width = width}
:wikitext(arg(1, '&nbsp;'))
end

return p
Источник —

Same as RoundN