Модуль:Sports table/columns
Материал из вики «Вече»
Дополнительные действия
Для документации этого модуля может быть создана страница Модуль:Sports table/columns/doc
-- Столбцы показателей турнирной таблицы для всех стилей.
-- Строит заголовок таблицы (header) и статистические ячейки строк (row) и отдаёт словарь
-- обозначений статуса (status). Стиль задаётся моделью показателей (MODELS):
-- wdl — победы, ничьи и поражения (стили WDL и WL);
-- ha — те же исходы раздельно дома и на выезде (WDLHA);
-- ot — к ним победы и поражения в овертайме и в серии (WDL OT и WL OT).
-- Модель определяет, какие показатели читаются, как считаются очки, процент и отставание,
-- какие столбцы идут в блок исходов и в каком порядке стоят остальные. Всё прочее общее:
-- разбор параметров, подписи столбцов, сравнение забитого и пропущенного,
-- столбцы ранжирования, выездные голы, бонусные очки, отставание, запись ячеек.
-- Параметры таблицы разбираются один раз, при построении заголовка, а строки берут их готовыми.
-- Показатели каждой команды читаются из аргументов тоже один раз, даже если нужны дважды:
-- отставание сравнивает команду с опорной.
-- Подмодули стиля ([[Модуль:Sports table/WDL]] и остальные) состоят из одного вызова p.style;
-- турнирная таблица ([[Модуль:Sports table]]) вызывает у полученного стиля header, row и status
-- и читает признаки автосборки. Начало и конец заголовка, ширины столбцов, выравнивание чисел,
-- сноски личных встреч и строку итогов берёт из [[Модуль:Sports table/sub]].
-- Описание стилей и их параметров: [[Модуль:Sports table/doc]].
local p = {}
local p_sub = require('Модуль:Sports table/sub')
local yesno = require('Модуль:Yesno')
local total_row_code = p_sub.total_row_code
local decimal = p_sub.decimal
-- Словари значений параметров -----------------------------------------------------------
-- ranking_style: критерий ранжирования. Значение, которого здесь нет, — ранжирование по очкам.
local RANKING = {
w = 'wins', win = 'wins', wins = 'wins',
wperc = 'wperc', ['win percentage'] = 'wperc', wpct = 'wperc', ['%w'] = 'wperc',
perc = 'perc', percentage = 'perc', ['%'] = 'perc',
ppg = 'ppg', ['points per game'] = 'ppg', ptspergame = 'ppg',
percpts = 'percpts', ['%pts'] = 'percpts',
ppgpts = 'ppgpts',
none = 'none', no = 'none',
forpts = 'forpts',
}
-- Столбцы ранжирования каждого критерия. wins не выводит их, а выделяет столбец побед;
-- forpts выделяет вместе с очками столбец забитого. Выделенный столбец получает и сноску о личных встречах (hth_КОД).
local RANK_COLUMNS = {
wins = {wins = true},
wperc = {wperc = true},
perc = {perc = true},
ppg = {ppg = true},
percpts = {perc = true, points = true},
ppgpts = {ppg = true, points = true},
none = {},
forpts = {points = true, forpts = true},
points = {points = true},
}
-- for_against_style: единица подсчёта «забито/пропущено».
local FOR_AGAINST = {
g = 'goals', goal = 'goals', goals = 'goals',
f = 'frames', frame = 'frames', frames = 'frames',
m = 'matches', match = 'matches', matches = 'matches',
game = 'games', games = 'games',
p = 'points', point = 'points', points = 'points',
s = 'sets', set = 'sets', sets = 'sets',
r = 'runs', run = 'runs', runs = 'runs',
g2 = 'pucks', goal2 = 'pucks', goals2 = 'pucks',
l = 'legs', leg = 'legs', legs = 'legs',
none = 'none', no = 'none',
}
-- Буква и слово единицы в подписях столбцов. against — подпись столбца «пропущено» там,
-- где буква с «П» неоднозначна: у партий получилось бы «ПП».
local UNITS = {
goals = {letter = 'М', word = 'Мячей'},
frames = {letter = 'Ф', word = 'Фреймов'},
matches = {letter = 'М', word = 'Матчей'},
games = {letter = 'И', word = 'Игр'},
points = {letter = 'О', word = 'Очков'},
sets = {letter = 'С', word = 'Сетов'},
runs = {letter = 'Р', word = 'Ранов'},
pucks = {letter = 'Ш', word = 'Шайб'},
legs = {letter = 'П', word = 'Партий', against = 'Партия'},
}
-- Варианты столбца сравнения забитого и пропущенного в порядке старшинства:
-- из нескольких заданных выводится первый. Соотношение и среднее считаются одной формулой,
-- различается только подпись.
local COMPARISONS = {
{id = 'ratio', params = {'use_goal_ratio'}},
{id = 'average', params = {'use_goal_average'}},
{id = 'goal_perc', params = {'use_goal_percentage'}},
{id = 'against_perc', params = {'use_against_percentage'}},
{id = 'point_perc', params = {'use_point_percentage'}},
{id = 'per_match', params = {'use_goals_per_match', 'use_points_per_match'}},
{id = 'scored', params = {'use_goals_scored'}},
}
-- Толстая черта слева, которой в стиле WDLHA отделены блоки дома и на выезде и столбцы после них.
local BLOCK_BORDER = 'border-width:1px 1px 1px 3px;'
-- Полужирное начертание ячейки.
local BOLD = 'font-weight:bold;'
-- Исходы, заданные не числом, отменяют подсчёт сыгранного (стили WDL и с овертаймом).
local WDL_FIELDS = {'win', 'draw', 'loss'}
-- Вспомогательные функции ---------------------------------------------------------------
-- Возвращает число из значения параметра или 0.
local function num(v)
return tonumber(v) or 0
end
-- Возвращает значение для ячейки исхода, когда все исходы команды нулевые:
-- нечисловое значение (прочерк и т. п.) выводится как задано, пустое и незаданное — нулём.
local function raw_or_zero(s)
return s and ((s == '' and 0) or tonumber(s) or s) or 0
end
-- Проверяет, что все перечисленные параметры команды code заданы числами.
local function all_numeric(Args, code, fields)
for _, f in ipairs(fields) do
if tonumber(Args[f .. '_' .. code]) == nil then
return false
end
end
return true
end
-- Записывает долю с тремя знаками после запятой: «0,625», «1,000».
local function share(x)
return decimal(x, 3)
end
-- Возвращает число, выровненное ppp.num_align, с минусом перед выровненной частью:
-- ppp.num_align выравнивает только неотрицательные числа.
local function signed(n, digits)
if tonumber(n) and n < 0 then
return '−' .. p_sub.num_align(- n, digits)
end
return p_sub.num_align(n, digits)
end
-- Возвращает процент побед, в котором ничья идёт за половину победы: долей с тремя знаками
-- (pct_style=ratio, по умолчанию) или процентами с двумя. bonus — прибавить бонусные очки,
-- каждое тоже за половину победы.
local function win_share(o, r, bonus)
local x = (2 * r.wins + r.draws + (bonus and r.s_pts or 0)) / (2 * r.matches)
if o.pct_style == 'ratio' then
return share(x)
end
return decimal(100 * x, 2)
end
-- Запись заголовка и ячеек --------------------------------------------------------------
-- Накопитель заголовка: tt — накопитель ppp.colhead, w — ширины столбцов из ppp.open_table,
-- pending — черта слева, которая ждёт первого столбца блока.
local Header = {}
Header.__index = Header
local function new_header(tt, w)
return setmetatable({tt = tt, w = w, pending = ''}, Header)
end
-- Отдаёт черту слева, если она ждёт первого столбца блока, и снимает её.
function Header:border()
local b = self.pending
self.pending = ''
return b
end
-- Добавляет заголовок столбца; border — черта слева.
function Header:add(width, text, border)
self.tt = p_sub.colhead(self.tt, width, text, border)
end
-- Накопитель ячеек строки: t — вывод, cls — класс заливки строки, bg — её встроенный стиль,
-- fw — полужирное начертание строки (showteam, строка итогов) либо пустая строка, hth —
-- сноска о личных встречах, pending — как у накопителя заголовка.
local Row = {}
Row.__index = Row
local function new_row(t, bg_class, bg_col, ii_fw, hth)
return setmetatable({t = t, bg_class = bg_class, cls = mw.text.trim(bg_class), bg = bg_col,
fw = (ii_fw == BOLD) and BOLD or '', hth = hth, pending = ''}, Row)
end
Row.border = Header.border
-- Добавляет ячейку. weight — начертание этой ячейки вместо начертания строки, border — черта слева,
-- class — класс вместо класса заливки строки. Обычное начертание не записывается: ячейка и так не полужирная.
-- Пустые атрибуты не выводятся; без атрибутов между чертой и значением ставится пробел,
-- иначе «+» или «-» в начале значения прочитались бы как разметка таблицы.
function Row:add(value, weight, border, class)
class = class or self.cls
local style = (border or '') .. (weight or self.fw) .. self.bg
local t = self.t
if class == '' then
if style == '' then
t[#t + 1] = '| ' .. value .. '\n'
else
t[#t + 1] = '| style="' .. style .. '" |' .. value .. '\n'
end
elseif style == '' then
t[#t + 1] = '| class="' .. class .. '" |' .. value .. '\n'
else
t[#t + 1] = '| class="' .. class .. '" style="' .. style .. '" |' .. value .. '\n'
end
end
-- Добавляет ячейку столбца ранжирования: полужирную классом st-bold
-- (процент побед без ничьих — st-perc, выровненный вправо), со сноской о личных встречах.
function Row:rank(value, class)
self:add(value .. self.hth, '', nil, (class or 'st-bold') .. self.bg_class)
end
-- Подпись столбца исхода; custom — подпись из параметра …_header, если задана.
local function outcome_head(custom, title, letter)
return custom or ('<abbr title="' .. title .. '">' .. letter .. '</abbr>')
end
-- Подпись столбца ничьих: draw_header, при use_tie — «Р» (равны), иначе «Н».
local function draw_head(o, suffix)
if o.draw_head then
return o.draw_head
end
if o.use_tie then
return '<abbr title="Равны' .. (suffix or '') .. '">Р</abbr>'
end
return '<abbr title="Ничьи' .. (suffix or '') .. '">Н</abbr>'
end
-- Модели показателей --------------------------------------------------------------------
-- Каждая модель задаёт:
-- default_unit — единица «забито/пропущено» для нераспознанного for_against_style;
-- diff_always — столбец разницы выводится и рядом со столбцом сравнения (иначе только
-- без него), если use_goal_diff не задан;
-- write_ha, write_ot — признаки для автосборки ([[Модуль:Sports table/auto]]);
-- order — порядок столбцов; block — блок исходов, остальные — из COLUMNS;
-- totals_fields — показатели для строки итогов (ppp.add_to_totals);
-- read_options — разбор параметров модели: очки за исходы, её столбцы и подписи;
-- read — чтение показателей команды: исходы, забитое и пропущенное, поправка
-- очков (s_pts), сыграно, очки;
-- perc — процент для столбца процента;
-- gb — отставание от опорной команды g;
-- block — заголовок и ячейки блока исходов.
local MODELS = {}
-- Победы, ничьи, поражения ---------------------------------------------------------------
MODELS.wdl = {
default_unit = 'goals',
diff_always = true,
order = {'played', 'block', 'fa', 'compare', 'diff', 'away_goals', 'bonus', 'points', 'perc', 'ppg', 'gb'},
totals_fields = {'win', 'draw', 'loss', 'rw', {'gf', 'pf'}, {'ga', 'pa'}, 'adjust_points', 'startpoints'},
}
-- Очки за исходы. Без ничьих (show_draw=no, стиль WL) по умолчанию 2 за победу и 1 за поражение.
function MODELS.wdl.read_options(o, Args)
o.winpoints = tonumber(Args['winpoints']) or (o.show_draw and 3 or 2)
o.drawpoints = tonumber(Args['drawpoints']) or (o.show_draw and 1 or 0)
o.losspoints = tonumber(Args['losspoints']) or (o.show_draw and 0 or 1)
o.rwpoints = tonumber(Args['rwpoints']) or 1
end
function MODELS.wdl.read(o, Args, code)
local r = {}
r.wins, r.draws, r.losses = num(Args['win_' .. code]), num(Args['draw_' .. code]), num(Args['loss_' .. code])
r.all_wins = r.wins
r.rw = num(Args['rw_' .. code])
-- Исходные gf_ и ga_ (синонимы — pf_ и pa_) нужны и дальше: для очков за забитое
-- и для вывода нечислового значения как есть.
r.gf_raw = Args['gf_' .. code] or Args['pf_' .. code]
r.ga_raw = Args['ga_' .. code] or Args['pa_' .. code]
r.gf, r.ga = tonumber(r.gf_raw or '0') or '?', tonumber(r.ga_raw or '0') or '?'
-- Поправка очков (adjust_points_) и начальные очки (startpoints_) складываются.
r.s_pts = num(Args['adjust_points_' .. code]) + num(Args['startpoints_' .. code])
r.matches = r.wins + r.draws + r.losses
r.numeric = all_numeric(Args, code, WDL_FIELDS)
r.points = o.winpoints * r.wins + o.drawpoints * r.draws + o.losspoints * r.losses
+ o.rwpoints * r.rw + r.s_pts
if r.wins == 0 and r.draws == 0 and r.losses == 0 then
r.win_text = raw_or_zero(Args['win_' .. code])
r.draw_text = raw_or_zero(Args['draw_' .. code])
r.loss_text = raw_or_zero(Args['loss_' .. code])
else
r.win_text, r.draw_text, r.loss_text = r.wins, r.draws, r.losses
end
-- Выигранные раунды выводятся как заданы, если заданы не числом.
r.rw_text = tonumber(Args['rw_' .. code] or 0) and r.rw or (Args['rw_' .. code] or '')
return r
end
function MODELS.wdl.perc(o, r)
return win_share(o, r, true)
end
-- Отставание — полуразность побед и поражений.
function MODELS.wdl.gb(r, g)
return (g.wins - r.wins + r.losses - g.losses) / 2
end
MODELS.wdl.block = {
head = function(h, o)
if o.show_win then
h:add(h.w.num, outcome_head(o.win_head, 'Выигранные матчи', 'В'))
end
local loss = outcome_head(o.loss_head, 'Поражения', 'П')
if o.loss_first then
if o.show_loss then h:add(h.w.num, loss) end
if o.show_draw then h:add(h.w.num, draw_head(o)) end
else
if o.show_draw then h:add(h.w.num, draw_head(o)) end
if o.show_loss then h:add(h.w.num, loss) end
end
if o.rounds_won then
h:add(h.w.num, o.rw_head or '<abbr title="Раундов выиграно">РВ</abbr>')
end
end,
cell = function(c, o, r)
local d = o.digits
if o.show_win then
c:add(p_sub.num_align(r.win_text, d) .. c.win_note, c.win_weight)
end
if o.loss_first then
if o.show_loss then c:add(p_sub.num_align(r.loss_text, d)) end
if o.show_draw then c:add(p_sub.num_align(r.draw_display, d)) end
else
if o.show_draw then c:add(p_sub.num_align(r.draw_display, d)) end
if o.show_loss then c:add(p_sub.num_align(r.loss_text, d)) end
end
if o.rounds_won then
c:add(p_sub.num_align(r.rw_text, d))
end
end,
}
-- Дома и на выезде -----------------------------------------------------------------------
MODELS.ha = {
default_unit = 'goals',
diff_always = true,
write_ha = true,
order = {'played', 'block', 'fa', 'compare', 'diff', 'gb', 'perc', 'away_goals', 'bonus', 'points', 'ppg', 'ha_side'},
totals_fields = {'hwin', 'awin', 'hdraw', 'adraw', 'hloss', 'aloss',
{'hgf', 'hpf'}, {'agf', 'apf'}, {'hga', 'hpa'}, {'aga', 'apa'}, 'hrw', 'arw', 'adjust_points', 'startpoints'},
-- Подписи блоков: дополнение к всплывающей подсказке столбца.
block_titles = {home = ' дома', away = ' на выезде', total = ''},
-- Стороны и приставки их параметров.
sides = {{'home', 'h'}, {'away', 'a'}},
}
-- Очки за исходы; hwinpoints и awinpoints — за победу дома и на выезде, по умолчанию
-- winpoints. only_totals — один блок итогов вместо блоков дома и на выезде; ha_side включает его
-- и добавляет в конец итоги дома и на выезде краткой записью. only_fa_totals —
-- забитое и пропущенное одной парой после блоков, а не в каждом блоке.
function MODELS.ha.read_options(o, Args, flag)
o.hwinpoints = tonumber(Args['hwinpoints']) or tonumber(Args['winpoints']) or 3
o.awinpoints = tonumber(Args['awinpoints']) or tonumber(Args['winpoints']) or 3
o.drawpoints = tonumber(Args['drawpoints']) or 1
o.losspoints = tonumber(Args['losspoints']) or 0
o.rwpoints = tonumber(Args['rwpoints']) or 1
o.ha_side = flag('ha_side', 'no')
o.only_totals = o.ha_side or flag('only_totals', 'no')
o.only_fa_totals = flag('only_fa_totals', 'no')
o.block_border = o.only_totals and '' or BLOCK_BORDER
-- Исходы, заданные не числом, отменяют подсчёт сыгранного;
-- ничьи проверяются, только когда выводятся.
o.outcome_fields = o.show_draw and {'hwin', 'hloss', 'awin', 'aloss', 'hdraw', 'adraw'}
or {'hwin', 'hloss', 'awin', 'aloss'}
o.blocks = o.only_totals and {'total'} or {'home', 'away'}
if o.only_fa_totals then
o.totals_extra = {{'gf', 'pf'}, {'ga', 'pa'}}
end
end
function MODELS.ha.read(o, Args, code)
local r = {home = {}, away = {}}
for _, sp in ipairs(MODELS.ha.sides) do
local s, pre = r[sp[1]], sp[2]
s.wins, s.draws, s.losses = num(Args[pre .. 'win_' .. code]), num(Args[pre .. 'draw_' .. code]),
num(Args[pre .. 'loss_' .. code])
s.rw = num(Args[pre .. 'rw_' .. code])
s.gf_raw = Args[pre .. 'gf_' .. code] or Args[pre .. 'pf_' .. code]
s.ga_raw = Args[pre .. 'ga_' .. code] or Args[pre .. 'pa_' .. code]
s.gf, s.ga = tonumber(s.gf_raw or '0') or '?', tonumber(s.ga_raw or '0') or '?'
end
local h, a = r.home, r.away
r.wins, r.draws, r.losses = h.wins + a.wins, h.draws + a.draws, h.losses + a.losses
r.all_wins = r.wins
r.rw = h.rw + a.rw
r.gf = (h.gf ~= '?' and a.gf ~= '?') and (h.gf + a.gf) or '?'
r.ga = (h.ga ~= '?' and a.ga ~= '?') and (h.ga + a.ga) or '?'
-- only_fa_totals: к сумме дома и на выезде прибавляются общие gf_ и ga_
-- (синонимы — pf_ и pa_), если сама сумма определена.
if o.only_fa_totals then
if r.gf ~= '?' then r.gf = r.gf + num(Args['gf_' .. code] or Args['pf_' .. code]) end
if r.ga ~= '?' then r.ga = r.ga + num(Args['ga_' .. code] or Args['pa_' .. code]) end
end
r.s_pts = num(Args['adjust_points_' .. code]) + num(Args['startpoints_' .. code])
r.matches = r.wins + r.draws + r.losses
r.numeric = all_numeric(Args, code, o.outcome_fields)
r.points = o.hwinpoints * h.wins + o.awinpoints * a.wins + o.drawpoints * r.draws
+ o.losspoints * r.losses + o.rwpoints * r.rw + r.s_pts
r.goals_for_points = r.gf
-- Если все исходы нулевые, в ячейки идут исходные значения.
local zero = r.wins == 0 and r.draws == 0 and r.losses == 0
for _, sp in ipairs(MODELS.ha.sides) do
local s, pre = r[sp[1]], sp[2]
if zero then
s.win_text = raw_or_zero(Args[pre .. 'win_' .. code])
s.draw_text = raw_or_zero(Args[pre .. 'draw_' .. code])
s.loss_text = raw_or_zero(Args[pre .. 'loss_' .. code])
else
s.win_text, s.draw_text, s.loss_text = s.wins, s.draws, s.losses
end
s.rw_text = s.rw
s.gf_text = (s.gf == '?') and (s.gf_raw or '') or s.gf
s.ga_text = (s.ga == '?') and (s.ga_raw or '') or s.ga
end
r.total = {win_text = r.wins, draw_text = r.draws, loss_text = r.losses, rw_text = r.rw,
gf_text = r.gf, ga_text = r.ga}
if zero then
-- В блоке итогов нечисловые значения не суммируются: выводится ноль.
r.total.win_text, r.total.draw_text, r.total.loss_text = 0, 0, 0
end
r.gf_text, r.ga_text = r.gf, r.ga
return r
end
-- Процент побед без бонусных очков.
function MODELS.ha.perc(o, r)
return win_share(o, r, false)
end
-- Отставание — полуразность побед и поражений плюс разность ничьих.
function MODELS.ha.gb(r, g)
return (g.wins - r.wins + r.losses - g.losses) / 2 + (g.draws - r.draws)
end
MODELS.ha.block = {
head = function(h, o)
for _, side in ipairs(o.blocks) do
local suffix = MODELS.ha.block_titles[side]
h.pending = o.block_border
if o.show_win then
h:add(h.w.num, outcome_head(o.win_head, 'Выигранные матчи' .. suffix, 'В'), h:border())
end
local loss = outcome_head(o.loss_head, 'Поражения' .. suffix, 'П')
if o.loss_first then
if o.show_loss then h:add(h.w.num, loss, h:border()) end
if o.show_draw then h:add(h.w.num, draw_head(o, suffix), h:border()) end
else
if o.show_draw then h:add(h.w.num, draw_head(o, suffix), h:border()) end
if o.show_loss then h:add(h.w.num, loss, h:border()) end
end
if o.rounds_won then
h:add(h.w.num, o.rw_head or ('<abbr title="Раундов выиграно' .. suffix .. '">РВ</abbr>'), h:border())
end
if not o.hide_fa and not o.hide_fa_columns and not o.only_fa_totals then
local u = o.unit
h:add(h.w.num, '<abbr title="' .. u.word .. ' забито' .. suffix .. '">' .. u.letter .. 'З</abbr>', h:border())
h:add(h.w.num, '<abbr title="' .. u.word .. ' пропущено' .. suffix .. '">'
.. (u.against or (u.letter .. 'П')) .. '</abbr>', h:border())
end
end
-- Столбцы после блоков отделены той же чертой.
h.pending = o.block_border
end,
cell = function(c, o, r)
local d = o.digits
for _, side in ipairs(o.blocks) do
local s = r[side]
c.pending = o.block_border
if o.show_win then
c:add(p_sub.num_align(s.win_text, d) .. c.win_note, c.win_weight, c:border())
end
if o.loss_first then
if o.show_loss then c:add(p_sub.num_align(s.loss_text, d), nil, c:border()) end
if o.show_draw then c:add(p_sub.num_align(s.draw_display or s.draw_text, d), nil, c:border()) end
else
if o.show_draw then c:add(p_sub.num_align(s.draw_display or s.draw_text, d), nil, c:border()) end
if o.show_loss then c:add(p_sub.num_align(s.loss_text, d), nil, c:border()) end
end
if o.rounds_won then
c:add(p_sub.num_align(s.rw_text, d), nil, c:border())
end
if not o.hide_fa and not o.hide_fa_columns and not o.only_fa_totals then
c:add(p_sub.num_align(s.gf_text, d), nil, c:border())
c:add(p_sub.num_align(s.ga_text, d), nil, c:border())
end
end
c.pending = o.block_border
end,
}
-- Овертайм и серия -----------------------------------------------------------------------
MODELS.ot = {
-- Нераспознанная единица — шайбы: стиль рассчитан прежде всего на хоккей.
default_unit = 'pucks',
diff_always = false,
write_ot = true,
order = {'played', 'block', 'fa', 'compare', 'diff', 'away_goals', 'bonus', 'points', 'perc', 'ppg', 'gb'},
totals_fields = {'win', 'OTwin', 'draw', 'OTloss', 'loss', 'tiebr',
{'PKwin', 'SOwin'}, {'PKloss', 'SOloss'}, {'gf', 'pf'}, {'ga', 'pa'}, 'adjust_points', 'startpoints'},
perc_head = '<abbr title="Процент очков">% очков</abbr>',
}
-- Очки за исходы — от show_draw не зависят; SOwinpoints и SOlosspoints — синонимы PKwinpoints и PKlosspoints.
-- Столбцы исходов в овертайме по умолчанию выводятся, в серии — нет (SO… — синонимы PK…);
-- при …_after_loss поражения в овертайме и в серии стоят после обычных.
-- Подпись столбцов серии без …_header — «бул» при showSOwin и showSOloss, иначе «пен».
function MODELS.ot.read_options(o, Args, flag)
o.winpoints = tonumber(Args['winpoints']) or 3
o.otwinpoints = tonumber(Args['OTwinpoints']) or 2
o.drawpoints = tonumber(Args['drawpoints']) or 1
o.otlosspoints = tonumber(Args['OTlosspoints']) or 1
o.pkwinpoints = tonumber(Args['PKwinpoints'] or Args['SOwinpoints']) or 1
o.pklosspoints = tonumber(Args['PKlosspoints'] or Args['SOlosspoints']) or 0
o.losspoints = tonumber(Args['losspoints']) or 0
o.show_OTwin = flag('showOTwin', 'yes')
o.show_OTloss = flag('showOTloss', 'yes')
o.show_PKwin = yesno(Args['showPKwin'] or Args['showSOwin'] or 'no')
o.show_PKloss = yesno(Args['showPKloss'] or Args['showSOloss'] or 'no')
o.OTloss_after = flag('OTloss_after_loss', 'no')
o.PKloss_after = yesno(Args['PKloss_after_loss'] or Args['SOloss_after_loss'] or 'no')
o.show_tiebr = flag('show_tiebr', 'no')
o.OTwin_head = Args['OTwin_header'] or '<abbr title="Выигранные матчи в овертайме">В(ОТ)</abbr>'
o.OTloss_head = Args['OTloss_header'] or '<abbr title="Поражения в овертайме">П(ОТ)</abbr>'
o.PKwin_head = Args['PKwin_header'] or Args['SOwin_header'] or (flag('showSOwin', 'no')
and '<abbr title="Выигранные в серии буллитов матчи">В(бул)</abbr>'
or '<abbr title="Выигранные в серии пенальти матчи">В(пен)</abbr>')
o.PKloss_head = Args['PKloss_header'] or Args['SOloss_header'] or (flag('showSOloss', 'no')
and '<abbr title="Поражения в серии буллитов">П(бул)</abbr>'
or '<abbr title="Поражения в серии пенальти">П(пен)</abbr>')
o.tiebr_head = Args['tiebr_header'] or '<abbr title="Тай-брейк">Т</abbr>'
end
function MODELS.ot.read(o, Args, code)
local r = {}
r.wins, r.draws, r.losses = num(Args['win_' .. code]), num(Args['draw_' .. code]), num(Args['loss_' .. code])
r.otw, r.otl = num(Args['OTwin_' .. code]), num(Args['OTloss_' .. code])
r.pkw = num(Args['PKwin_' .. code] or Args['SOwin_' .. code])
r.pkl = num(Args['PKloss_' .. code] or Args['SOloss_' .. code])
r.tiebr = num(Args['tiebr_' .. code])
r.all_wins = r.wins + r.otw + r.pkw
r.gf_raw = Args['gf_' .. code] or Args['pf_' .. code]
r.ga_raw = Args['ga_' .. code] or Args['pa_' .. code]
r.gf, r.ga = tonumber(r.gf_raw or '0') or '?', tonumber(r.ga_raw or '0') or '?'
-- Поправка очков (adjust_points_), а если её нет — начальные очки (startpoints_):
-- в этом стиле они не складываются.
r.s_pts = tonumber(Args['adjust_points_' .. code]) or tonumber(Args['startpoints_' .. code]) or 0
r.matches = r.wins + r.otw + r.draws + r.otl + r.pkw + r.pkl + r.losses
r.numeric = all_numeric(Args, code, WDL_FIELDS)
r.points = o.winpoints * r.wins + o.otwinpoints * r.otw + o.drawpoints * r.draws
+ o.otlosspoints * r.otl + o.losspoints * r.losses
+ o.pkwinpoints * r.pkw + o.pklosspoints * r.pkl + r.s_pts
if r.wins == 0 and r.otw == 0 and r.pkw == 0 and r.draws == 0 and r.otl == 0 and r.pkl == 0
and r.losses == 0 then
r.win_text = raw_or_zero(Args['win_' .. code])
r.otw_text = raw_or_zero(Args['OTwin_' .. code])
r.pkw_text = raw_or_zero(Args['PKwin_' .. code] or Args['SOwin_' .. code])
r.draw_text = raw_or_zero(Args['draw_' .. code])
r.loss_text = raw_or_zero(Args['loss_' .. code])
r.otl_text = raw_or_zero(Args['OTloss_' .. code])
r.pkl_text = raw_or_zero(Args['PKloss_' .. code] or Args['SOloss_' .. code])
else
r.win_text, r.otw_text, r.pkw_text, r.draw_text = r.wins, r.otw, r.pkw, r.draws
r.loss_text, r.otl_text, r.pkl_text = r.losses, r.otl, r.pkl
end
return r
end
-- Процент очков — доля от максимума, который дали бы победы в основное время во всех матчах.
-- percentage_format: 'hundred 1 decimal' и 'hundred 2 decimals' — в процентах с одним или
-- двумя знаками, иначе доля с тремя знаками.
function MODELS.ot.perc(o, r)
local most = r.matches * o.winpoints
if most == 0 then
return '—'
end
local x = r.points / most
if o.percentage_format == 'hundred 1 decimal' then
return decimal(100 * x, 1)
elseif o.percentage_format == 'hundred 2 decimals' then
return decimal(100 * x, 2)
end
return share(x)
end
-- Отставание — полуразность побед и поражений в основное время.
function MODELS.ot.gb(r, g)
return (g.wins - r.wins + r.losses - g.losses) / 2
end
MODELS.ot.block = {
head = function(h, o)
local w = h.w.num
if o.show_win then h:add(w, outcome_head(o.win_head, 'Выигранные матчи', 'В')) end
if o.show_OTwin then h:add(w, o.OTwin_head) end
if o.show_PKwin then h:add(w, o.PKwin_head) end
if o.show_draw and not o.loss_first then h:add(w, draw_head(o)) end
if o.show_OTloss and not o.OTloss_after then h:add(w, o.OTloss_head) end
if o.show_PKloss and not o.PKloss_after then h:add(w, o.PKloss_head) end
if o.show_loss then h:add(w, outcome_head(o.loss_head, 'Поражения', 'П')) end
if o.show_draw and o.loss_first then h:add(w, draw_head(o)) end
if o.show_OTloss and o.OTloss_after then h:add(w, o.OTloss_head) end
if o.show_PKloss and o.PKloss_after then h:add(w, o.PKloss_head) end
if o.show_tiebr then h:add(w, o.tiebr_head) end
end,
cell = function(c, o, r)
local d = o.digits
local function put(v) c:add(p_sub.num_align(v, d)) end
if o.show_win then c:add(p_sub.num_align(r.win_text, d) .. c.win_note, c.win_weight) end
if o.show_OTwin then put(r.otw_text) end
if o.show_PKwin then put(r.pkw_text) end
if o.show_draw and not o.loss_first then put(r.draw_display) end
if o.show_OTloss and not o.OTloss_after then put(r.otl_text) end
if o.show_PKloss and not o.PKloss_after then put(r.pkl_text) end
if o.show_loss then put(r.loss_text) end
if o.show_draw and o.loss_first then put(r.draw_display) end
if o.show_OTloss and o.OTloss_after then put(r.otl_text) end
if o.show_PKloss and o.PKloss_after then put(r.pkl_text) end
if o.show_tiebr then put(r.tiebr) end
end,
}
-- Общие столбцы -------------------------------------------------------------------------
-- Столбцы, общие для моделей: head строит заголовок, cell — ячейку,
-- full — только в полной таблице, when — условие вывода.
local COLUMNS = {}
COLUMNS.played = {
when = function(o) return o.show_played end,
head = function(h, o) h:add(h.w.num, o.pld_head) end,
cell = function(c, _, r) c:add(r.matches_text) end,
}
-- Столбцы ранжирования.
COLUMNS.points = {
when = function(o) return o.rank.points end,
head = function(h) h:add(h.w.points, '<abbr title="Очки">О</abbr>') end,
cell = function(c, o, r) c:rank(signed(r.points, o.digits)) end,
}
COLUMNS.perc = {
when = function(o) return o.rank.perc or o.rank.wperc end,
head = function(h, o)
h:add(h.w.ratio, (not o.rank.wperc and o.model.perc_head)
or '<abbr title="Процент выигрышей">%В</abbr>')
end,
cell = function(c, o, r) c:rank(r.perc, o.rank.wperc and 'st-perc' or nil) end,
}
COLUMNS.ppg = {
when = function(o) return o.rank.ppg end,
head = function(h) h:add(h.w.points, '<abbr title="Очков за игру">ОЗИ</abbr>') end,
cell = function(c, _, r) c:rank(r.ppg) end,
}
-- Забито и пропущено одной парой. У модели с блоками дома и на выезде эта пара выводится
-- только при only_fa_totals — иначе забитое и пропущенное стоят в каждом блоке.
COLUMNS.fa = {
full = true,
when = function(o)
return not o.hide_fa and not o.hide_fa_columns and (o.blocks == nil or o.only_fa_totals)
end,
head = function(h, o)
local u = o.unit
h:add(h.w.num, '<abbr title="' .. u.word .. ' забито">' .. u.letter .. 'З</abbr>', h:border())
h:add(h.w.num, '<abbr title="' .. u.word .. ' пропущено">' .. (u.against or (u.letter .. 'П')) .. '</abbr>')
end,
cell = function(c, o, r)
c:add(p_sub.num_align(r.gf_text, o.digits), o.rank.forpts and BOLD or nil, c:border())
c:add(p_sub.num_align(r.ga_text, o.digits))
end,
}
-- Сравнение забитого и пропущенного.
COLUMNS.compare = {
full = true,
when = function(o) return not o.hide_fa and o.compare ~= nil end,
head = function(h, o)
local u, v = o.unit, o.compare
local lower = mw.ustring.lower(u.word)
if v == 'ratio' then
h:add(h.w.ratio, '<abbr title="Соотношение ' .. lower .. '">С' .. u.letter .. '</abbr>', h:border())
elseif v == 'average' then
h:add(h.w.ratio, '<abbr title="Среднее число ' .. lower .. '">С' .. u.letter .. '</abbr>', h:border())
elseif v == 'per_match' then
h:add(h.w.ratio, '<abbr title="' .. u.word .. ' за матч">' .. u.letter .. 'ОЗМ</abbr>', h:border())
elseif v == 'scored' then
h:add(h.w.num, '<abbr title="' .. u.word .. ' забито">' .. u.letter .. 'З</abbr>', h:border())
else
h:add(h.w.ratio, '<abbr title="Процент ' .. lower .. '">%' .. u.letter .. '</abbr>', h:border())
end
end,
cell = function(c, _, r) c:add(r.compare, nil, c:border()) end,
}
-- Разница забитого и пропущенного.
COLUMNS.diff = {
full = true,
when = function(o) return not o.hide_fa and o.diff end,
head = function(h, o)
local u = o.unit
h:add(h.w.num, '<abbr title="Разница ' .. mw.ustring.lower(u.word) .. '">Р' .. u.letter .. '</abbr>', h:border())
end,
cell = function(c, _, r) c:add(r.diff, nil, c:border()) end,
}
COLUMNS.away_goals = {
when = function(o) return o.show_away_goals end,
head = function(h, o) h:add(h.w.num, o.away_goals_head) end,
cell = function(c, _, r) c:add(r.away_goals) end,
}
-- Бонусные очки — поправка очков команды.
COLUMNS.bonus = {
when = function(o) return o.show_bonus end,
head = function(h, o) h:add(h.w.num, o.bonus_head) end,
cell = function(c, o, r) c:add(signed(r.s_pts, o.digits)) end,
}
-- Отставание от опорной команды.
COLUMNS.gb = {
when = function(o) return o.show_GB end,
head = function(h) h:add(h.w.num, p_sub.gb_header) end,
cell = function(c, _, r) c:add(r.gb_text) end,
}
-- Итоги дома и на выезде краткой записью «В–Н–П» (при loss_before_draw — «В–П–Н»,
-- без ничьих — «В–П»). Только у модели с блоками дома и на выезде.
COLUMNS.ha_side = {
when = function(o) return o.ha_side end,
head = function(h)
h:add(h.w.num, '<abbr title="Домашний рекорд">Дома</abbr>')
h:add(h.w.num, '<abbr title="Выездной рекорд">Выезд</abbr>')
end,
cell = function(c, o, r)
for _, s in ipairs({r.home, r.away}) do
local rest
if not o.show_draw then
rest = s.loss_text
elseif o.loss_first then
rest = s.loss_text .. '–' .. s.draw_text
else
rest = s.draw_text .. '–' .. s.loss_text
end
c:add(s.win_text .. '–' .. rest)
end
end,
}
-- Разбор параметров таблицы -------------------------------------------------------------
-- Разбирает параметры таблицы Args для модели model: какие столбцы выводить, в каком порядке
-- и по каким правилам считать. Возвращает набор параметров; в поле stats он хранит
-- прочитанные показатели команд (см. team_stats).
local function read_options(model, Args)
local o = {model = model, stats = {}}
local function flag(name, default)
return yesno(Args[name] or default)
end
o.digits = Args['digitpad']
o.show_played = not flag('hide_played', 'no')
o.show_win = flag('show_win', 'yes')
o.show_draw = flag('show_draw', 'yes')
o.show_loss = flag('show_loss', 'yes')
o.loss_first = yesno(Args['loss_before_draw'] or Args['loss_before_tie'] or 'no')
o.use_tie = flag('use_tie', 'no')
o.rounds_won = yesno(Args['show_rw'] or Args['rounds_won'] or 'no')
o.show_hth = flag('show_hth', 'yes')
o.show_totals = flag('show_totals', 'no')
o.show_bonus = flag('show_bonus_points', 'no')
o.show_away_goals = flag('show_away_goals', 'no')
o.show_GB = flag('show_GB', 'no')
o.GB_team = tonumber(Args['GB_team']) or 1
o.pct_style = string.lower(Args['pct_style'] or 'ratio')
o.percentage_format = Args['percentage_format']
-- goalpoints — очки за каждое забитое; нечисловое значение — по одному очку.
o.goalpoints = Args['goalpoints'] and (tonumber(Args['goalpoints']) or 1)
-- Критерий ранжирования. pts_first и perc_first ставят его столбцы сразу за командой.
o.rank = RANK_COLUMNS[RANKING[string.lower(Args['ranking_style'] or 'pts')] or 'points']
o.rank_first = yesno((o.rank.points and Args['pts_first'])
or ((o.rank.perc or o.rank.wperc) and Args['perc_first']) or 'no')
-- Единица «забито/пропущено»: незаданная — мячи, нераспознанная — единица модели.
local fa = FOR_AGAINST[string.lower(Args['for_against_style'] or 'goals')] or model.default_unit
o.hide_fa = (fa == 'none')
o.unit = UNITS[fa]
o.hide_fa_columns = flag('hide_for_against_columns', 'no')
-- Столбец сравнения и столбец разницы. Без use_goal_diff разница выводится по правилу модели:
-- всегда либо только когда сравнения нет.
for _, v in ipairs(COMPARISONS) do
for _, param in ipairs(v.params) do
if not o.compare and flag(param, 'no') then
o.compare = v.id
end
end
end
if Args['use_goal_diff'] then
o.diff = flag('use_goal_diff', 'yes')
else
o.diff = model.diff_always or not o.compare
end
-- Подписи столбцов; параметры …_header заменяют их своими.
o.pld_head = Args['pld_header'] or '<abbr title="Игры">И</abbr>'
o.bonus_head = Args['bonus_header'] or '<abbr title="Бонусные очки">БО</abbr>'
o.away_goals_head = Args['away_goals_header'] or '<abbr title="Выездные голы">ВГ</abbr>'
o.rw_head = Args['rw_header']
o.win_head = Args['win_header']
o.loss_head = Args['loss_header']
o.draw_head = Args['draw_header']
model.read_options(o, Args, flag)
-- Порядок столбцов: при rank_first столбцы ранжирования идут первыми.
local columns = {}
local rank_ids = {points = true, perc = true, ppg = true}
if o.rank_first then
columns = {'points', 'perc', 'ppg'}
end
for _, id in ipairs(model.order) do
if not (o.rank_first and rank_ids[id]) then
columns[#columns + 1] = id
end
end
o.columns = columns
return o
end
-- Разобранные параметры: ключ — таблица аргументов, значение — результат read_options.
-- Ключи слабые: разобранное живёт, пока жив сам вызов.
local options_cache = setmetatable({}, {__mode = 'k'})
-- Возвращает параметры таблицы Args для модели model; refresh — разобрать заново.
-- Заголовок разбирает их всегда: между двумя выводами одной таблицы аргументы могут измениться.
local function options(model, Args, refresh)
local o = options_cache[Args]
if refresh or not o or o.model ~= model then
o = read_options(model, Args)
options_cache[Args] = o
end
return o
end
-- Возвращает показатели команды code: прочитанные моделью (model.read) и производные —
-- очки за забитое, процент, очки за игру, сравнение и разницу, тексты ячеек.
-- Каждая команда читается один раз.
local function team_stats(o, Args, code)
local r = o.stats[code]
if r then
return r
end
r = o.model.read(o, Args, code)
local m = r.matches
if o.goalpoints and tonumber(r.goals_for_points or r.gf_raw) then
r.points = r.points + o.goalpoints * tonumber(r.goals_for_points or r.gf_raw)
end
-- Если исходы заданы не числами, число матчей — из matches_КОД, и выводится оно как задано.
r.matches_text = m
if not r.numeric then
m = tonumber(Args['matches_' .. code]) or m
r.matches = m
r.matches_text = Args['matches_' .. code] or m
end
if m == 0 then
r.perc, r.ppg = '—', '—'
else
r.perc = o.model.perc(o, r)
r.ppg = decimal(r.points / m, 2)
if o.rank.wperc then
-- Процент побед без учёта ничьих, с двумя знаками.
r.perc = decimal(100 * r.all_wins / m, 2)
end
end
-- Сравнение забитого и пропущенного: тире там, где делить не на что или значение не число.
local gf, ga, v = r.gf, r.ga, o.compare
local unknown = (gf == '?' or ga == '?')
if v == 'ratio' or v == 'average' then
r.compare = (unknown or ga == 0) and '—' or decimal(gf / ga, 3)
elseif v == 'goal_perc' then
r.compare = (unknown or ga == 0) and '—' or decimal(100 * gf / ga, 1)
elseif v == 'against_perc' then
r.compare = (unknown or gf == 0) and '—' or decimal(100 * ga / gf, 1)
elseif v == 'point_perc' then
r.compare = (unknown or gf + ga <= 0) and '—' or decimal(100 * gf / (gf + ga), 2)
elseif v == 'per_match' then
r.compare = (gf == '?' or m <= 0) and '—' or decimal(gf / m, 1)
elseif v == 'scored' then
r.compare = (gf == '?') and '—' or decimal(gf)
end
r.diff = p_sub.goal_diff(gf, ga)
-- Нечисловое забитое или пропущенное выводится как задано.
if not r.gf_text then
r.gf_text = (gf == '?') and (r.gf_raw or '') or gf
r.ga_text = (ga == '?') and (r.ga_raw or '') or ga
end
r.away_goals = Args['away_goals_' .. code] or ''
-- Строка итогов: каждый матч учтён дважды, по разу за каждую команду, поэтому «сыграно»
-- и ничьи выводятся делёнными на 2. Победы и поражения не делятся: матч даёт одну
-- победу и одно поражение. В блоках дома и на выезде ничья тоже учтена один раз —
-- у хозяев дома, у гостей на выезде.
r.draw_display = r.draw_text
if code == total_row_code then
if tonumber(r.matches_text) then
r.matches_text = decimal(tonumber(r.matches_text) / 2)
end
if tonumber(r.draw_text) then
r.draw_display = r.draw_text / 2
end
if r.total then
r.total.draw_display = r.total.draw_text / 2
end
end
o.stats[code] = r
return r
end
-- Стиль ---------------------------------------------------------------------------------
-- Строит подмодуль стиля для модели model_name ('wdl', 'ha' или 'ot').
-- defaults — значения параметров, которые стиль подставляет, если параметр не задан (у WL и WL OT — show_draw=no).
-- Подставляются прямо в Args: show_draw читают и вне стиля — по нему автосборка и подбор ширины столбца очков
-- (ppp.measure_points_width) выбирают очки за исходы.
-- Возвращает таблицу стиля: header, row, status и признаки автосборки auto_supported,
-- write_ha, write_ot (см. [[Модуль:Sports table/auto]]).
function p.style(model_name, defaults)
local model = MODELS[model_name]
local pp = {auto_supported = true, write_ha = model.write_ha, write_ot = model.write_ot}
local function prepare(Args)
for k, v in pairs(defaults or {}) do
if Args[k] == nil then
Args[k] = v
end
end
end
-- Строит заголовок таблицы: позицию, группу и команду выводит ppp.open_table,
-- столбец результатов полной таблицы в конце — ppp.close_header, между ними —
-- столбцы показателей в порядке модели. Возвращает накопитель заголовков {count, tab_text}.
function pp.header(t, Args, pos_label, group_col, VTE_text, full_table, results_header_txt)
prepare(Args)
local o = options(model, Args, true)
local tt, w = p_sub.open_table(t, Args, pos_label, group_col, VTE_text, full_table)
local h = new_header(tt, w)
for _, id in ipairs(o.columns) do
if id == 'block' then
if full_table then
model.block.head(h, o)
end
else
local col = COLUMNS[id]
if (full_table or not col.full) and col.when(o) then
col.head(h, o)
end
end
end
return p_sub.close_header(h.tt, full_table, results_header_txt)
end
-- Дописывает в t статистические ячейки строки команды code — те же и в том же порядке,
-- что столбцы заголовка. Возвращает {t, notes_exist, hth_id_list}:
-- признак примечаний и список сносок личных встреч передаются следующим строкам.
function pp.row(frame, t, Args, notes_exist, hth_id_list, full_table, rand_val, team_list, code, ii_start, ii_end, ii_fw, bg_col, bg_class, ii)
prepare(Args)
local o = options(model, Args)
-- При show_totals показатели команды прибавляются к строке итогов.
if o.show_totals and code ~= total_row_code then
p_sub.add_to_totals(Args, code, model.totals_fields)
if o.totals_extra then
p_sub.add_to_totals(Args, code, o.totals_extra)
end
end
local r = team_stats(o, Args, code)
-- Сноска о личных встречах (hth_КОД) — см. ppp.hth; show_hth=no отключает её.
local hth_local = o.show_hth and Args['hth_' .. code] or nil
local hth = p_sub.hth(frame, Args, full_table, hth_id_list, hth_local, notes_exist, team_list,
code, ii_start, ii_end, rand_val)
-- Отставание от опорной команды (GB_team — её позиция, по умолчанию первая).
if o.show_GB then
local gb
local g_code = team_list[o.GB_team]
if ii ~= o.GB_team and g_code then
gb = model.gb(r, team_stats(o, Args, g_code))
end
r.gb_text = p_sub.gb_text(gb, o.digits)
end
local c = new_row(t, bg_class, bg_col, ii_fw, hth.str)
-- При ранжировании по победам выделен столбец побед, и сноска hth стоит у него.
c.win_weight = o.rank.wins and BOLD or nil
c.win_note = o.rank.wins and hth.str or ''
for _, id in ipairs(o.columns) do
if id == 'block' then
if full_table then
model.block.cell(c, o, r)
end
else
local col = COLUMNS[id]
if (full_table or not col.full) and col.when(o) then
col.cell(c, o, r)
end
end
end
return {t = t, notes_exist = hth.notes_exist, hth_id_list = hth.list}
end
-- Возвращает данные об обозначениях статуса — см. ppp.status.
-- Обозначения стилей совпадают с общим словарём.
function pp.status(Args)
prepare(Args)
return p_sub.status(Args)
end
return pp
end
return p