Модуль:MatchTable: различия между версиями

Материал из ЧТМ
Перейти к навигации Перейти к поиску
Нет описания правки
Нет описания правки
Строка 5: Строка 5:
local p = {}
local p = {}


-- Подключаем твою базу данных команд
-- Подключаем базу данных команд
local teamsData = require('Модуль:Data/Teams')
local teamsData = require('Модуль:Data/Teams')


-- Вспомогательная функция: очищает параметр от пробелов.
-- Вспомогательная функция: очищает параметр от пробелов.
-- Если параметр пустой, возвращает nil (удобно для проверок).
local function cleanParam(param)
local function cleanParam(param)
     if param then
     if type(param) == 'string' then
         local trimmed = mw.text.trim(param)
         local trimmed = mw.text.trim(param)
         if trimmed ~= '' then
         if trimmed ~= '' then return trimmed end
            return trimmed
    elseif param ~= nil then
         end
         return param -- Если передали число или boolean из другого модуля
     end
     end
     return nil
     return nil
end
end


-- Функция для вывода таблицы плей-офф
-- Вспомогательная функция для парсинга счёта
function p.playoff(frame)
local function parse_score(str)
     -- Получаем аргументы из шаблона (или напрямую из #invoke для тестов)
    if type(str) ~= 'string' then return nil, nil end
    local g1, g2 = string.match(str, "(%d+)%s*[:%-—]%s*(%d+)")
    if g1 and g2 then
        return tonumber(g1), tonumber(g2)
     end
    return nil, nil
end
 
-- Вспомогательная функция для извлечения аргументов из frame (для #invoke)
local function getArgsFromFrame(frame)
     local args = frame:getParent().args
     local args = frame:getParent().args
     if next(args) == nil then
     if next(args) == nil then args = frame.args end
        args = frame.args
     return args
     end
end


    -- Создаём каркас таблицы с помощью mw.html
-- =======================================
-- ПЛЕЙ-ОФФ (Внутренняя функция для Lua)
-- =======================================
function p._playoff(args)
     local tbl = mw.html.create('table')
     local tbl = mw.html.create('table')
         :attr('border', '1')
         :attr('border', '1')
Строка 36: Строка 47:
         :css('width', '500px')
         :css('width', '500px')


    -- Определяем количество строк
     local row_count = tonumber(cleanParam(args.number_of_strings))
     local row_count = tonumber(cleanParam(args.number_of_strings))
   
    -- Если number_of_strings не задан, считаем автоматически по наличию sX_team1
     if not row_count then
     if not row_count then
         row_count = 0
         row_count = 0
Строка 47: Строка 55:
     end
     end


    -- Генерируем строки таблицы
     for i = 1, row_count do
     for i = 1, row_count do
        -- Считываем параметры строки
         local t1_code = cleanParam(args['s' .. i .. '_team1']) or '?'
         local t1_code = cleanParam(args['s' .. i .. '_team1']) or '?'
         local t2_code = cleanParam(args['s' .. i .. '_team2']) or '?'
         local t2_code = cleanParam(args['s' .. i .. '_team2']) or '?'
Строка 56: Строка 62:
         local color2 = cleanParam(args['s' .. i .. '_color2'])
         local color2 = cleanParam(args['s' .. i .. '_color2'])


        -- Получаем короткие имена команд из базы
         local t1_name = teamsData.getName(t1_code, 'short')
         local t1_name = teamsData.getName(t1_code, 'short')
         local t2_name = teamsData.getName(t2_code, 'short')
         local t2_name = teamsData.getName(t2_code, 'short')


        -- Добавляем строку (<tr>)
         local tr = tbl:tag('tr')
         local tr = tbl:tag('tr')


        -- 1. Ячейка: Флаг первой команды
         local td_t1_img = tr:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
         local td_t1_img = tr:tag('td')
            :attr('align', 'center')
            :css('white-space', 'nowrap')
             :wikitext(string.format('[[Файл:%s.jpg|50x100px]]', t1_name))
             :wikitext(string.format('[[Файл:%s.jpg|50x100px]]', t1_name))
         if color1 then td_t1_img:css('background-color', color1) end
         if color1 then td_t1_img:css('background-color', color1) end


        -- 2. Ячейка: Название первой команды
         local td_t1_name = tr:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
         local td_t1_name = tr:tag('td')
            :attr('align', 'center')
            :css('white-space', 'nowrap')
             :wikitext(string.format('[[%s]]', t1_name))
             :wikitext(string.format('[[%s]]', t1_name))
         if color1 then td_t1_name:css('background-color', color1) end
         if color1 then td_t1_name:css('background-color', color1) end


        -- 3. Ячейка: Счёт матча (всегда без цвета)
         tr:tag('td'):attr('align', 'center'):wikitext(tostring(score))
         tr:tag('td')
            :attr('align', 'center')
            :wikitext(score)


        -- 4. Ячейка: Название второй команды
         local td_t2_name = tr:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
         local td_t2_name = tr:tag('td')
            :attr('align', 'center')
            :css('white-space', 'nowrap')
             :wikitext(string.format('[[%s]]', t2_name))
             :wikitext(string.format('[[%s]]', t2_name))
         if color2 then td_t2_name:css('background-color', color2) end
         if color2 then td_t2_name:css('background-color', color2) end


        -- 5. Ячейка: Флаг второй команды
         local td_t2_img = tr:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
         local td_t2_img = tr:tag('td')
            :attr('align', 'center')
            :css('white-space', 'nowrap')
             :wikitext(string.format('[[Файл:%s.jpg|50x100px]]', t2_name))
             :wikitext(string.format('[[Файл:%s.jpg|50x100px]]', t2_name))
         if color2 then td_t2_img:css('background-color', color2) end
         if color2 then td_t2_img:css('background-color', color2) end
     end
     end


    -- Возвращаем готовый HTML-код таблицы
     return tostring(tbl)
     return tostring(tbl)
end
-- Обёртка для вики-кода (Плей-офф)
function p.playoff(frame)
    return p._playoff(getArgsFromFrame(frame))
end
-- =======================================
-- ГРУППОВОЙ ЭТАП (Внутренняя функция для Lua)
-- =======================================
function p._group(args)
    local rounds = tonumber(cleanParam(args.number_of_rounds)) or 1
    local ms_arg = cleanParam(args.manual_sort)
    local is_manual = (ms_arg == 'yes' or ms_arg == true)
    local teams = {}
    local n = 0
    while cleanParam(args['team' .. (n + 1)]) do
        n = n + 1
        local code = cleanParam(args['team' .. n])
        local name = teamsData.getName(code, 'short')
        local color = cleanParam(args['color' .. n])
        teams[n] = {
            id = n, code = code, name = name, color = color,
            p = 0, w = 0, d = 0, l = 0, gf = 0, ga = 0, pts = 0
        }
    end
    if n == 0 then return "Нет команд для отображения." end
    local grid = {}
    for i = 1, n do
        grid[i] = {}
        for j = 1, n do grid[i][j] = {text = "", gf = nil, ga = nil, played = false} end
    end
    for i = 1, n do
        for j = 1, n do
            if i ~= j and not grid[i][j].played then
                local s_ij = cleanParam(args['score' .. i .. '_' .. j])
                local s_ji = cleanParam(args['score' .. j .. '_' .. i])
               
                if s_ij then
                    local g1, g2 = parse_score(s_ij)
                    grid[i][j] = {text = s_ij, gf = g1, ga = g2, played = (g1 ~= nil)}
                    if rounds == 1 and g1 then
                        grid[j][i] = {text = g2 .. ":" .. g1, gf = g2, ga = g1, played = true}
                    end
                elseif rounds == 1 and s_ji then
                    local g1, g2 = parse_score(s_ji)
                    if g1 then
                        grid[i][j] = {text = g2 .. ":" .. g1, gf = g2, ga = g1, played = true}
                        grid[j][i] = {text = s_ji, gf = g1, ga = g2, played = true}
                    end
                end
            end
        end
    end
    for i = 1, n do
        for j = 1, n do
            if i ~= j and grid[i][j].played then
                local gf, ga = grid[i][j].gf, grid[i][j].ga
                teams[i].p = teams[i].p + 1
                teams[i].gf = teams[i].gf + gf
                teams[i].ga = teams[i].ga + ga
                if gf > ga then
                    teams[i].w = teams[i].w + 1
                    teams[i].pts = teams[i].pts + 3
                elseif gf == ga then
                    teams[i].d = teams[i].d + 1
                    teams[i].pts = teams[i].pts + 1
                else
                    teams[i].l = teams[i].l + 1
                end
            end
        end
    end
    local sorted_teams = {}
    for i = 1, n do table.insert(sorted_teams, teams[i]) end
    if not is_manual then
        local function get_h2h_stats(t_ids)
            local h2h = {}
            for _, id in ipairs(t_ids) do h2h[id] = {pts = 0, gd = 0, gf = 0} end
            for i = 1, #t_ids do
                for j = 1, #t_ids do
                    local id1, id2 = t_ids[i], t_ids[j]
                    if id1 ~= id2 and grid[id1][id2].played then
                        local g1, g2 = grid[id1][id2].gf, grid[id1][id2].ga
                        h2h[id1].gf = h2h[id1].gf + g1
                        h2h[id1].gd = h2h[id1].gd + (g1 - g2)
                        if g1 > g2 then h2h[id1].pts = h2h[id1].pts + 3
                        elseif g1 == g2 then h2h[id1].pts = h2h[id1].pts + 1
                        end
                    end
                end
            end
            return h2h
        end
        table.sort(sorted_teams, function(a, b)
            if a.pts ~= b.pts then return a.pts > b.pts end
            if (a.gf - a.ga) ~= (b.gf - b.ga) then return (a.gf - a.ga) > (b.gf - b.ga) end
            if a.gf ~= b.gf then return a.gf > b.gf end
            return a.id < b.id
        end)
        local i = 1
        while i <= n do
            local j = i
            while j < n and
                  sorted_teams[j+1].pts == sorted_teams[i].pts and
                  (sorted_teams[j+1].gf - sorted_teams[j+1].ga) == (sorted_teams[i].gf - sorted_teams[i].ga) and
                  sorted_teams[j+1].gf == sorted_teams[i].gf do
                j = j + 1
            end
           
            if j > i then
                local tied_ids = {}
                for k = i, j do table.insert(tied_ids, sorted_teams[k].id) end
                local h2h = get_h2h_stats(tied_ids)
               
                local sub = {}
                for k = i, j do table.insert(sub, sorted_teams[k]) end
               
                table.sort(sub, function(a, b)
                    if h2h[a.id].pts ~= h2h[b.id].pts then return h2h[a.id].pts > h2h[b.id].pts end
                    if h2h[a.id].gd ~= h2h[b.id].gd then return h2h[a.id].gd > h2h[b.id].gd end
                    if h2h[a.id].gf ~= h2h[b.id].gf then return h2h[a.id].gf > h2h[b.id].gf end
                    return a.id < b.id
                end)
               
                for k = i, j do sorted_teams[k] = sub[k - i + 1] end
            end
            i = j + 1
        end
    end
    local tbl = mw.html.create('table')
        :attr('border', '1')
        :attr('cellspacing', '1')
        :attr('cellpadding', '1')
        :addClass('article-table')
        :css('width', '500px')
    local tr_head = tbl:tag('tr')
    tr_head:tag('th'):attr('scope', 'col'):wikitext('Место')
    tr_head:tag('th'):attr('scope', 'col'):wikitext('')
    tr_head:tag('th'):attr('scope', 'col'):wikitext('Команда')
    for i = 1, n do tr_head:tag('th'):attr('scope', 'col'):wikitext(tostring(i)) end
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Игры'):wikitext('И')
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Выигрыши'):wikitext('В')
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Ничьи'):wikitext('Н')
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Поражения'):wikitext('П')
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Забитые и пропущенные мячи'):wikitext('М')
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Очки'):wikitext('О')
    for rank, t in ipairs(sorted_teams) do
        local r = tbl:tag('tr')
       
        local td_rank = r:tag('td'):attr('align', 'center'):css('white-space', 'nowrap'):wikitext(tostring(rank))
        if t.color then td_rank:css('background-color', t.color) end
       
        local td_flag = r:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
            :wikitext(string.format('[[Файл:%s.jpg|50x100px]]', t.name))
        if t.color then td_flag:css('background-color', t.color) end
       
        local td_name = r:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
            :wikitext(string.format('[[%s]]', t.name))
        if t.color then td_name:css('background-color', t.color) end
       
        for j = 1, n do
            local opp = sorted_teams[j]
            local td_match = r:tag('td'):attr('align', 'center')
            if t.id == opp.id then
                td_match:css('background-color', 'grey')
            else
                local m = grid[t.id][opp.id]
                if m.played or m.text ~= "" then td_match:wikitext(m.text) end
            end
        end
       
        r:tag('td'):attr('align', 'center'):wikitext(tostring(t.p))
        r:tag('td'):attr('align', 'center'):wikitext(tostring(t.w))
        r:tag('td'):attr('align', 'center'):wikitext(tostring(t.d))
        r:tag('td'):attr('align', 'center'):wikitext(tostring(t.l))
        -- ЗДЕСЬ ИЗМЕНЕН ЗНАК НА ДЕФИС:
        r:tag('td'):attr('align', 'center'):css('white-space', 'nowrap'):wikitext(string.format('%d-%d', t.gf, t.ga))
        r:tag('td'):attr('align', 'center'):tag('b'):wikitext(tostring(t.pts))
    end
    return tostring(tbl)
end
-- Обёртка для вики-кода (Группы)
function p.group(frame)
    return p._group(getArgsFromFrame(frame))
end
end


return p
return p

Версия от 10:01, 22 апреля 2026

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

Модуль-оболочка для вывода таблиц матчей. Главное назначение — отрисовывать таблицы по указанию мощного модуля TournamentResults, но может генерировать таблички и в чистом виде, если их заполнять вручную, поскольку из-за архитектурных особенностей прямого выхода на базу данных не имеет.

Параметры

Функция group

Рисует таблицы групповых раундов, автоматически подсчитывая количество участвовавших команд и очки.

  • number_of_rounds = Количество кругов, один или два.
  • compact = yes Если параметр задан, то в таблице не отображаются колонки «И», «В», «Н» и «П».
  • team1, team2 и т. д. — трёхбуквенные коды участвовавших команд.
  • score1_2 — счёт матча с участием указанных команд. Принимает также ссылки вида [[Матч|0:0]], корректно учитывая счёт в результатах
  • bold1, bold2 и т. д. — выделение жирным шрифтом нужных команд.
  • color1, color2 и т. д. — цвета ячеек указанных команд.

Функция playoff

Более простая функция, поскольку здесь не нужно ничего подсчитывать.

  • s1_team1 =, s1_team2 = участники первого противостояния.
  • s2_team1 =, s2_team2 = участники второго противостояния.
  • и т. д.
  • s1_score =, s2_score = счёт первого матча, счёт второго матча.
  • s2_bold2 = выделение жирным шрифтом нужной команды на нужной строке.
  • s1_color2 = выделение цветом нужной команды на нужной строке.

Функция _rating

Нужна для отрисовывания сворачиваемых таблиц рейтингов мест в группах, но напрямую не вызывается, поскольку для её составления нужен доступ к БД. Является внутренней функцией модуля TournamentResults.

Примеры использования

{{#invoke:MatchTable|playoff
|s1_team1 = ДОМ |s1_team2 = ЧАД |s1_score = 2:1(ET) |s1_color1 = lightgreen |s1_color2 = lightsalmon
|s2_team1 = АБХ |s2_team2 = АВИ |s2_score = [[Финал ЧТМ-2014|5:1]] |s2_color1 = |s2_color2 = 
}}
{{#invoke:MatchTable|group
| number_of_rounds = 1
| compact = yes
|team1 = БАГ |color1 = lightgreen | bold1 = yes
|team2 = СКН |color2 = lightsalmon
|team3 = АВИ |color3 = lightsalmon
|team4 = ГАЛ |color4 = lightsalmon

|score1_2 = [[Финал ЧТМ-2014|5:1]]
|score1_3 = 4:0
|score1_4 = 3:2
|score2_3 = 5:1
|score2_4 = 4:1
|score3_4 = 0:0
}}

Пожалуйста, добавляйте категории на страницу документации.

-- =======================================
-- Модуль:MatchTable
-- Оболочка для вывода таблиц матчей
-- =======================================
local p = {}

-- Подключаем базу данных команд
local teamsData = require('Модуль:Data/Teams')

-- Вспомогательная функция: очищает параметр от пробелов.
local function cleanParam(param)
    if type(param) == 'string' then
        local trimmed = mw.text.trim(param)
        if trimmed ~= '' then return trimmed end
    elseif param ~= nil then
        return param -- Если передали число или boolean из другого модуля
    end
    return nil
end

-- Вспомогательная функция для парсинга счёта
local function parse_score(str)
    if type(str) ~= 'string' then return nil, nil end
    local g1, g2 = string.match(str, "(%d+)%s*[:%-—]%s*(%d+)")
    if g1 and g2 then
        return tonumber(g1), tonumber(g2)
    end
    return nil, nil
end

-- Вспомогательная функция для извлечения аргументов из frame (для #invoke)
local function getArgsFromFrame(frame)
    local args = frame:getParent().args
    if next(args) == nil then args = frame.args end
    return args
end

-- =======================================
-- ПЛЕЙ-ОФФ (Внутренняя функция для Lua)
-- =======================================
function p._playoff(args)
    local tbl = mw.html.create('table')
        :attr('border', '1')
        :attr('cellspacing', '1')
        :attr('cellpadding', '1')
        :addClass('article-table')
        :css('width', '500px')

    local row_count = tonumber(cleanParam(args.number_of_strings))
    if not row_count then
        row_count = 0
        while cleanParam(args['s' .. (row_count + 1) .. '_team1']) do
            row_count = row_count + 1
        end
    end

    for i = 1, row_count do
        local t1_code = cleanParam(args['s' .. i .. '_team1']) or '?'
        local t2_code = cleanParam(args['s' .. i .. '_team2']) or '?'
        local score = cleanParam(args['s' .. i .. '_score']) or '—'
        local color1 = cleanParam(args['s' .. i .. '_color1'])
        local color2 = cleanParam(args['s' .. i .. '_color2'])

        local t1_name = teamsData.getName(t1_code, 'short')
        local t2_name = teamsData.getName(t2_code, 'short')

        local tr = tbl:tag('tr')

        local td_t1_img = tr:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
            :wikitext(string.format('[[Файл:%s.jpg|50x100px]]', t1_name))
        if color1 then td_t1_img:css('background-color', color1) end

        local td_t1_name = tr:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
            :wikitext(string.format('[[%s]]', t1_name))
        if color1 then td_t1_name:css('background-color', color1) end

        tr:tag('td'):attr('align', 'center'):wikitext(tostring(score))

        local td_t2_name = tr:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
            :wikitext(string.format('[[%s]]', t2_name))
        if color2 then td_t2_name:css('background-color', color2) end

        local td_t2_img = tr:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
            :wikitext(string.format('[[Файл:%s.jpg|50x100px]]', t2_name))
        if color2 then td_t2_img:css('background-color', color2) end
    end

    return tostring(tbl)
end

-- Обёртка для вики-кода (Плей-офф)
function p.playoff(frame)
    return p._playoff(getArgsFromFrame(frame))
end

-- =======================================
-- ГРУППОВОЙ ЭТАП (Внутренняя функция для Lua)
-- =======================================
function p._group(args)
    local rounds = tonumber(cleanParam(args.number_of_rounds)) or 1
    local ms_arg = cleanParam(args.manual_sort)
    local is_manual = (ms_arg == 'yes' or ms_arg == true)

    local teams = {}
    local n = 0
    while cleanParam(args['team' .. (n + 1)]) do
        n = n + 1
        local code = cleanParam(args['team' .. n])
        local name = teamsData.getName(code, 'short')
        local color = cleanParam(args['color' .. n])
        teams[n] = {
            id = n, code = code, name = name, color = color,
            p = 0, w = 0, d = 0, l = 0, gf = 0, ga = 0, pts = 0
        }
    end

    if n == 0 then return "Нет команд для отображения." end

    local grid = {}
    for i = 1, n do
        grid[i] = {}
        for j = 1, n do grid[i][j] = {text = "", gf = nil, ga = nil, played = false} end
    end

    for i = 1, n do
        for j = 1, n do
            if i ~= j and not grid[i][j].played then
                local s_ij = cleanParam(args['score' .. i .. '_' .. j])
                local s_ji = cleanParam(args['score' .. j .. '_' .. i])
                
                if s_ij then
                    local g1, g2 = parse_score(s_ij)
                    grid[i][j] = {text = s_ij, gf = g1, ga = g2, played = (g1 ~= nil)}
                    if rounds == 1 and g1 then
                        grid[j][i] = {text = g2 .. ":" .. g1, gf = g2, ga = g1, played = true}
                    end
                elseif rounds == 1 and s_ji then
                    local g1, g2 = parse_score(s_ji)
                    if g1 then
                        grid[i][j] = {text = g2 .. ":" .. g1, gf = g2, ga = g1, played = true}
                        grid[j][i] = {text = s_ji, gf = g1, ga = g2, played = true}
                    end
                end
            end
        end
    end

    for i = 1, n do
        for j = 1, n do
            if i ~= j and grid[i][j].played then
                local gf, ga = grid[i][j].gf, grid[i][j].ga
                teams[i].p = teams[i].p + 1
                teams[i].gf = teams[i].gf + gf
                teams[i].ga = teams[i].ga + ga
                if gf > ga then
                    teams[i].w = teams[i].w + 1
                    teams[i].pts = teams[i].pts + 3
                elseif gf == ga then
                    teams[i].d = teams[i].d + 1
                    teams[i].pts = teams[i].pts + 1
                else
                    teams[i].l = teams[i].l + 1
                end
            end
        end
    end

    local sorted_teams = {}
    for i = 1, n do table.insert(sorted_teams, teams[i]) end

    if not is_manual then
        local function get_h2h_stats(t_ids)
            local h2h = {}
            for _, id in ipairs(t_ids) do h2h[id] = {pts = 0, gd = 0, gf = 0} end
            for i = 1, #t_ids do
                for j = 1, #t_ids do
                    local id1, id2 = t_ids[i], t_ids[j]
                    if id1 ~= id2 and grid[id1][id2].played then
                        local g1, g2 = grid[id1][id2].gf, grid[id1][id2].ga
                        h2h[id1].gf = h2h[id1].gf + g1
                        h2h[id1].gd = h2h[id1].gd + (g1 - g2)
                        if g1 > g2 then h2h[id1].pts = h2h[id1].pts + 3
                        elseif g1 == g2 then h2h[id1].pts = h2h[id1].pts + 1
                        end
                    end
                end
            end
            return h2h
        end

        table.sort(sorted_teams, function(a, b)
            if a.pts ~= b.pts then return a.pts > b.pts end
            if (a.gf - a.ga) ~= (b.gf - b.ga) then return (a.gf - a.ga) > (b.gf - b.ga) end
            if a.gf ~= b.gf then return a.gf > b.gf end
            return a.id < b.id
        end)

        local i = 1
        while i <= n do
            local j = i
            while j < n and
                  sorted_teams[j+1].pts == sorted_teams[i].pts and
                  (sorted_teams[j+1].gf - sorted_teams[j+1].ga) == (sorted_teams[i].gf - sorted_teams[i].ga) and
                  sorted_teams[j+1].gf == sorted_teams[i].gf do
                j = j + 1
            end
            
            if j > i then
                local tied_ids = {}
                for k = i, j do table.insert(tied_ids, sorted_teams[k].id) end
                local h2h = get_h2h_stats(tied_ids)
                
                local sub = {}
                for k = i, j do table.insert(sub, sorted_teams[k]) end
                
                table.sort(sub, function(a, b)
                    if h2h[a.id].pts ~= h2h[b.id].pts then return h2h[a.id].pts > h2h[b.id].pts end
                    if h2h[a.id].gd ~= h2h[b.id].gd then return h2h[a.id].gd > h2h[b.id].gd end
                    if h2h[a.id].gf ~= h2h[b.id].gf then return h2h[a.id].gf > h2h[b.id].gf end
                    return a.id < b.id
                end)
                
                for k = i, j do sorted_teams[k] = sub[k - i + 1] end
            end
            i = j + 1
        end
    end

    local tbl = mw.html.create('table')
        :attr('border', '1')
        :attr('cellspacing', '1')
        :attr('cellpadding', '1')
        :addClass('article-table')
        :css('width', '500px')

    local tr_head = tbl:tag('tr')
    tr_head:tag('th'):attr('scope', 'col'):wikitext('Место')
    tr_head:tag('th'):attr('scope', 'col'):wikitext('')
    tr_head:tag('th'):attr('scope', 'col'):wikitext('Команда')
    for i = 1, n do tr_head:tag('th'):attr('scope', 'col'):wikitext(tostring(i)) end
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Игры'):wikitext('И')
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Выигрыши'):wikitext('В')
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Ничьи'):wikitext('Н')
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Поражения'):wikitext('П')
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Забитые и пропущенные мячи'):wikitext('М')
    tr_head:tag('th'):attr('scope', 'col'):attr('title', 'Очки'):wikitext('О')

    for rank, t in ipairs(sorted_teams) do
        local r = tbl:tag('tr')
        
        local td_rank = r:tag('td'):attr('align', 'center'):css('white-space', 'nowrap'):wikitext(tostring(rank))
        if t.color then td_rank:css('background-color', t.color) end
        
        local td_flag = r:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
            :wikitext(string.format('[[Файл:%s.jpg|50x100px]]', t.name))
        if t.color then td_flag:css('background-color', t.color) end
        
        local td_name = r:tag('td'):attr('align', 'center'):css('white-space', 'nowrap')
            :wikitext(string.format('[[%s]]', t.name))
        if t.color then td_name:css('background-color', t.color) end
        
        for j = 1, n do
            local opp = sorted_teams[j]
            local td_match = r:tag('td'):attr('align', 'center')
            if t.id == opp.id then
                td_match:css('background-color', 'grey')
            else
                local m = grid[t.id][opp.id]
                if m.played or m.text ~= "" then td_match:wikitext(m.text) end
            end
        end
        
        r:tag('td'):attr('align', 'center'):wikitext(tostring(t.p))
        r:tag('td'):attr('align', 'center'):wikitext(tostring(t.w))
        r:tag('td'):attr('align', 'center'):wikitext(tostring(t.d))
        r:tag('td'):attr('align', 'center'):wikitext(tostring(t.l))
        -- ЗДЕСЬ ИЗМЕНЕН ЗНАК НА ДЕФИС:
        r:tag('td'):attr('align', 'center'):css('white-space', 'nowrap'):wikitext(string.format('%d-%d', t.gf, t.ga))
        r:tag('td'):attr('align', 'center'):tag('b'):wikitext(tostring(t.pts))
    end

    return tostring(tbl)
end

-- Обёртка для вики-кода (Группы)
function p.group(frame)
    return p._group(getArgsFromFrame(frame))
end

return p