Перейти к содержанию

Модуль:Football

Материал из Наро-Фоминский Футбол
Версия от 14:20, 14 сентября 2026; Andrew (обсуждение | вклад) (Добавлен вывод таблицы с движением по турам)

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

Турнир, в котором после первого этапа таблица делится на группы, оформляется несколькими таблицами: таблицы групп начинаются с итогов первого этапа — см. «Турнир из нескольких этапов».

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

Модуль вызывается напрямую из статьи:

{{#invoke:Football|main| … }}

Модуль читает только параметры самого вызова #invoke. Если нужен шаблон-обёртка, он должен явно передавать в модуль каждый параметр.

Модуль выводит по порядку:

  1. турнирную таблицу — без собственного заголовка (заголовок раздела ставится в статье);
  2. строку «Критерии определения мест» и, если с какой-то команды снимались очки, блок «Примечания»;
  3. раздел «Результаты» с шахматкой;
  4. раздел «Положение команд по турам» — только при show_progress = yes.

Заголовки разделов — обычные вики-заголовки (по умолчанию === … ===), поэтому они попадают в оглавление статьи. Уровень заголовков меняется параметром heading_level.

Быстрый старт

{{#invoke:Турнирная таблица|main
| teams    = СПА;ЦСК;ЗЕН
| name_СПА = Спартак
| name_ЦСК = ЦСКА
| name_ЗЕН = Зенит
| match1   = 1;1;СПА;ЦСК;2:1
| match2   = 2;1;ЦСК;ЗЕН;0:0
| match3   = 3;1;ЗЕН;СПА;1:3
| match4   = 4;2;ЦСК;СПА;
| match5   = 5;2;ЗЕН;ЦСК;
| match6   = 6;2;СПА;ЗЕН;
}}

Несыгранные матчи можно внести заранее с пустым счётом: в шахматке они видны как «—» и начинают учитываться, как только вписан счёт.

Для однокругового турнира добавьте rounds = 1 — см. «Шахматка».

Команды

Параметр Описание
teams Обязательный. Коды команд через «;», например СПА;ЦСК;ЗЕН. Порядок кодов — это порядок строк и столбцов шахматки и строк таблицы по турам; итоговая таблица сортируется сама. Коды должны быть уникальными. Удобнее всего короткие коды из букв и цифр без пробелов: они входят в имена других параметров.
name_КОД Название команды, например name_СПА = Спартак. Можно использовать вики-разметку: name_СПА = [[ФК «Спартак»|Спартак]]. Если название не указано, выводится код.

Матчи

Каждый матч — отдельный параметр: match1, match2 и так далее. Нумерация без пропусков: модуль читает матчи по порядку и останавливается на первом отсутствующем или пустом номере, поэтому матчи после пропуска не учитываются.

Части значения разделяются «;». Допускаются три формата:

Формат Пример Примечание
тур;круг;хозяева;гости;счёт 5;2;СПА;ЛОК;2:2 Полный формат. Номер тура нужен для таблицы положения по турам, номер круга — для режима шахматки resultsMode = round.
круг;хозяева;гости;счёт 2;СПА;ЛОК;2:2 Матч не попадает в таблицу положения по турам.
хозяева;гости;счёт СПА;ЛОК;2:2 Матч не попадает в таблицу положения по турам.

Хозяева и гости указываются кодами из teams. Тур — число, круг — 1 или 2.

Возможные значения счёта:

Значение Что означает Что видно в шахматке
2:1 (можно 2 : 1) Матч сыгран: голы хозяев, двоеточие, голы гостей Счёт и цвет результата
пусто, - или — Матч ещё не сыгран —
перенесён или перенесен Матч перенесён перенесён
отменён или отменен Матч отменён отменён
что-либо другое Счёт не распознан, матч не учитывается ошибка: …

Техническое поражение записывается обычным счётом, например 0:3. Если за матч с команды ещё и сняли очки, это указывается отдельно — см. раздел «Снятие очков».

Очки за результат

Параметр По умолчанию Описание
points_win 3 Очки за победу
points_draw 1 Очки за ничью
points_loss 0 Очки за поражение

Критерии определения мест

Параметры tie1 … tie10 задают критерии в порядке применения: если по первому критерию команды равны, сравнивается второй, и так далее. Если не указан ни один, используется порядок очки → разница → забитые → победы → название. Если команды равны по всем критериям, выше та, чей код раньше по алфавиту.

Значение Выше команда, у которой…
очки больше очков (с учётом снятых и стартовых)
разница лучше разница забитых и пропущенных мячей
забитые больше забитых мячей
победы больше побед
игры больше сыгранных матчей
личные лучше результаты в матчах между всеми командами, набравшими столько же очков: сравниваются очки в этих матчах, затем разница мячей в них, забитые в них и победы в них. Учитываются матчи этой таблицы и матчи h2h_matchN (см. «Личные встречи с учётом первого этапа»); снятые и стартовые очки здесь не учитываются. Критерий имеет смысл только после очки.
название название раньше по алфавиту (сравнивается текст параметра name_КОД как есть, вместе с разметкой)

Стартовые показатели команд (start_КОД) входят во все критерии, кроме личные.

Под таблицей выводится строка «Критерии определения мест: …» с выбранным порядком. Нераспознанные значения модуль пропускает, но показывает в этой строке, так что по ней удобно проверять написание.

Пример: очки, затем личные встречи, затем общая разница мячей.

| tie1 = очки
| tie2 = личные
| tie3 = разница

Цветовые зоны

Параметры highlight1 … highlight10 (без пропусков в нумерации) выделяют группы мест:

highlightN = место;количество;класс;текст

Часть Описание
место Первое место зоны — в нумерации таблицы, то есть с учётом first_place (см. «Нумерация мест»)
количество Сколько мест подряд входит в зону (по умолчанию 1)
класс CSS-класс. Строки зоны получают классы highlight и указанный, например class="highlight green". Сами цвета задаются стилями вики (см. «Оформление»).
текст Подпись зоны (необязательно). Может содержать ссылки и «;».

Если хотя бы у одной зоны есть текст, в таблице появляется столбец «Повышение или выбывание»: подпись выводится одной объединённой ячейкой на всю зону, места между зонами — пустыми объединёнными ячейками. Зоны не должны пересекаться. Зона, которая начинается выше первого места таблицы, обрезается, а зоны целиком за пределами таблицы не выводятся.

| highlight1 = 1;1;green;Выход в Лигу чемпионов
| highlight2 = 2;2;blue;Выход в Лигу конференций
| highlight3 = 15;2;red;Выбывание в Первую лигу

Шахматка

Раздел «Результаты». Строки и столбцы — команды в порядке teams, в ячейке — матч команды строки с командой столбца.

Параметр Значения Описание
rounds 2 (по умолчанию) или 1 Число кругов: 1 — однокруговой турнир
resultsMode round Только для двухкругового турнира: строки по кругам, а не «дома — в гостях»

Двухкруговой турнир (по умолчанию): каждая пара играет по разу на поле каждой из команд, поэтому у команды две строки. В первой — её домашние матчи, во второй — выездные. При resultsMode = round первая строка показывает матчи первого круга, вторая — второго (по номеру круга в matchN).

Однокруговой турнир (rounds = 1): каждая пара играет один раз, поэтому у команды одна строка, а в ячейке — единственный матч пары, на чьём бы поле он ни был. Параметр resultsMode при этом не действует.

Счёт всегда записан как «хозяева:гости», а цвет ячейки показывает результат для команды строки: класс ui green — победа, ui yellow — ничья, ui red — поражение. Например, в однокруговой шахматке матч «Спартак» — ЦСКА 0:2 и в строке «Спартака», и в строке ЦСКА записан как «0:2», но у «Спартака» ячейка красная, а у ЦСКА — зелёная.

Если у пары больше матчей, чем ячеек (например, отменённый матч и переигровка), в ячейке показывается первый сыгранный из них, а если сыгранных нет — первый с пометкой «перенесён», «отменён» или «ошибка: …».

Положение команд по турам

Параметр show_progress = yes добавляет раздел «Положение команд по турам»: для каждого тура — место команды после всех матчей этого и предыдущих туров. Учитываются только матчи с номером тура (полный формат matchN); туры идут по возрастанию, строки — в порядке teams. Места определяются по тем же критериям, что и в итоговой таблице (личные встречи — только по матчам до этого тура включительно). Если ни у одного матча нет номера тура, вместо таблицы выводится пояснение.

Ячейки с определёнными местами можно выделить параметрами progress_highlight1 … progress_highlight10:

progress_highlightN = место;класс — ячейка получает классы highlight и указанный.

| show_progress       = yes
| progress_highlight1 = 1;green
| progress_highlight2 = 16;red

Снятие очков учитывается начиная с тура матча, к которому оно привязано, а ячейка этого тура получает ту же сноску, что и команда в итоговой таблице.

Стартовые показатели (start_КОД) действуют с первого тура таблицы, а матчи h2h_matchN учитываются в критерии личные как сыгранные до первого тура. Места выводятся в нумерации таблицы (с учётом first_place), и в progress_highlightN места указываются в этой же нумерации.

Карточки

Карточки необязательны. Если они не указаны ни в одном матче, ни у одной команды и ни в одних стартовых показателях, столбцов карточек в таблице нет. Если они указаны хотя бы где-нибудь, после столбца «О» появляются три столбца — у всех команд.

Карточки учитываются раздельно, как в подробной футбольной статистике, чтобы вторая жёлтая карточка не засчитывалась дважды:

Столбец Вид Что считается
​ yellow Жёлтые карточки — все предупреждения, кроме второй жёлтой, после которой игрок удалён
​ yellowred Удаления за вторую жёлтую карточку — по одному на каждое такое удаление
​ red Прямые красные карточки

Как записывать разные случаи (один игрок в одном матче):

Что произошло yellow yellowred red
Одна жёлтая карточка 1 0 0
Две жёлтые карточки и удаление 1 1 0
Прямая красная карточка 0 0 1
Жёлтая, затем прямая красная 1 0 1

Общее число удалений команды равно сумме столбцов yellowred и red.

Карточки выводятся для справки и на места в таблице не влияют.

Карточки по матчам

matchN_yellow, matchN_yellowred, matchN_red — карточки в матче N в формате хозяева:гости:

| match1           = 1;1;СПА;ЦСК;2:1
| match1_yellow    = 2:3
| match1_yellowred = 0:1

Здесь у хозяев две жёлтые карточки, у гостей — три жёлтые и одно удаление за вторую жёлтую. Неуказанный вид карточек считается как 0:0. Карточки суммируются по всем матчам, где они указаны, независимо от счёта, и прибавляются к карточкам из стартовых показателей (start_КОД), если они там заданы.

Итог команды

yellow_КОД, yellowred_КОД, red_КОД — итоговое количество карточек команды (целое неотрицательное число). Если у команды указан хотя бы один из этих параметров, все её карточки берутся только из них (неуказанные считаются нулём), а карточки из матчей и стартовых показателей этой команды не учитываются. У остальных команд карточки по-прежнему считаются по матчам.

| yellow_ЗЕН    = 41
| yellowred_ЗЕН = 2
| red_ЗЕН       = 1

Так удобно поступать, если поматчевой статистики нет или итог нужно привести в соответствие с официальными данными.

Снятие очков

Снятие очков привязывается к конкретному матчу:

matchN_deduction = код;очки;причина

Часть Описание
код Команда, с которой сняты очки. Должна быть одной из двух команд матча N.
очки Сколько очков снято: положительное число, можно дробное (1,5).
причина Необязательно. Пишется как продолжение фразы: «за неявку на матч», «по решению КДК». Точка в конце не нужна. Можно использовать ссылки и «;».

Для второго снятия в том же матче (например, с обеих команд) используется matchN_deduction2, и так далее до matchN_deduction5.

| match7           = 4;2;ЦСК;СПА;1:1
| match7_deduction = СПА;3;за участие в матче дисквалифицированного игрока

Результат:

  • в итоговой таблице очки команды уменьшены, у названия стоит сноска вида [a], а при наведении на очки видна подсказка «Снято 3 очка»;
  • под таблицей появляется блок «Примечания» с текстом С команды «Спартак» снято 3 очка в 4-м туре (матч «ЦСКА» — «Спартак») за участие в матче дисквалифицированного игрока. Номер тура упоминается, если он указан в матче;
  • в таблице положения по турам очки снимаются начиная с этого тура, а в ячейке этого тура стоит та же сноска.

Сноски обозначаются буквами a, b, c… сверху вниз по итоговой таблице. Снятие действует независимо от того, сыгран ли матч, и не влияет на критерий личные.

Очки, снятые с команды до начала таблицы (например, в первом этапе турнира), здесь не указываются: они уже входят в её стартовые очки — см. «Стартовые показатели».

Турнир из нескольких этапов

Бывает, что после первого этапа таблица делится на группы — пополам или в другой пропорции, например 6 первых команд играют за 1–6 места, а 8 последних — за 7–14 места, — и команды доигрывают в своих группах. Такой турнир оформляется несколькими таблицами:

  1. первый этап — обычная таблица всех команд (если этап однокруговой — с rounds = 1);
  2. каждая группа второго этапа — отдельная таблица только из команд группы и матчей второго этапа. Итоги первого этапа задаются стартовыми показателями start_КОД, а у групп ниже первой нумерация мест начинается с first_place. Если среди критериев есть личные, в таблицу группы добавляются ещё и матчи первого этапа — h2h_matchN.

Стартовые показатели переписываются из итоговой таблицы первого этапа вручную. Зато так можно описать любой регламент перехода: сохраняются все результаты, очки делятся пополам или учитываются только матчи с командами своей группы.

Полный пример — в разделе «Пример турнира из двух этапов».

Стартовые показатели

start_КОД = И;В;Н;П;МЗ;МП;О;ЖК;2ЖК;КК — показатели, с которыми команда начинает таблицу. Части идут в том же порядке, что и столбцы итоговой таблицы (без РМ — она вычисляется сама), а за ними карточки:

Часть Что означает Если пусто
И Игры В + Н + П
В, Н, П Победы, ничьи, поражения 0
МЗ, МП Забитые и пропущенные мячи 0
О Очки; могут быть дробными (12,5) и отрицательными Вычисляются по победам, ничьим и поражениям с учётом points_win, points_draw, points_loss
ЖК, 2ЖК, КК Жёлтые карточки, удаления за вторую жёлтую карточку, прямые красные — как yellow, yellowred, red в разделе «Карточки» 0

Все части, кроме О, — целые неотрицательные числа. Если И указано, оно должно быть равно В + Н + П. Последние части можно не писать: при 3 очках за победу start_СПА = 13;9;2;2;27;10 означает то же, что start_СПА = 13;9;2;2;27;10;29.

Стартовые показатели:

  • прибавляются к результатам матчей таблицы и учитываются во всех критериях, кроме личные;
  • в таблице положения по турам действуют с первого тура этой таблицы;
  • в шахматке не видны: в ней только матчи этой таблицы;
  • если в них указаны карточки, те складываются с карточками из матчей, а итог команды (yellow_КОД и т. д.) заменяет и те и другие.

Очки, снятые в первом этапе, уже входят в очки О, и сноски о них в таблице группы не выводятся. Снятие очков за матчи второго этапа указывается как обычно — matchN_deduction.

Если во второй этап переходят не все результаты первого, в start_КОД указывается только то, что переходит. Например, если команды сохраняют половину очков, а игры и мячи считаются заново: start_СПА = ;;;;;;14,5.

Параметры start_КОД для кодов, которых нет в teams, не читаются, поэтому в таблицы всех групп можно вставить один и тот же блок со всеми командами.

Нумерация мест

first_place — номер первого места таблицы, по умолчанию 1. Для группы за 7–14 места указывается first_place = 7: места в турнирной таблице и в таблице положения по турам нумеруются с 7-го. В этой же нумерации указываются места в highlightN и progress_highlightN, например highlight1 = 13;2;red;Выбывание.

Зона, которая начинается выше первого места таблицы, обрезается, а зоны целиком за пределами таблицы не выводятся, поэтому одинаковый набор зон тоже можно вставить в таблицы всех групп.

Личные встречи с учётом первого этапа

В таблице группы критерий личные видит только её матчи, а по регламенту обычно учитываются все матчи сезона между командами. Матчи первого этапа добавляются параметрами h2h_match1, h2h_match2… (без пропусков в нумерации) в тех же форматах, что и matchN. Такие матчи:

  • учитываются только в критерии личные, а в таблице положения по турам — как сыгранные до первого тура;
  • не влияют на показатели команд, шахматку и таблицу по турам: итоги первого этапа уже есть в start_КОД;
  • пропускаются, если в матче играет команда, которой нет в teams, или если матч не сыгран.

Проще всего скопировать в таблицу группы строки matchN из таблицы первого этапа и заменить в них match на h2h_match. Матчи с командами других групп модуль пропустит сам, а получившиеся из карточек и снятий очков строки вроде h2h_match5_yellow не читает — их можно удалить.

Оформление

Параметр Описание
heading_level Уровень заголовков «Результаты» и «Положение команд по турам»: от 2 до 6, по умолчанию 3 (=== Результаты ===). При значении 2 — == Результаты ==.
id Префикс якорей для сносок. По умолчанию вычисляется по списку команд, поэтому обычно его указывать не нужно. Задайте разные id, если на одной странице две таблицы с одинаковым списком команд и со снятием очков. Лучше использовать латиницу, цифры и дефис.

CSS-классы, которые выводит модуль:

Класс Где используется
football-tables Общий блок вокруг всех таблиц
football-standings, football-results, football-progress Итоговая таблица, шахматка, таблица по турам
football-info Строка критериев и блок примечаний
football-notes Блок примечаний
football-note-ref Сноска о снятии очков
football-deduction Ячейка таблицы по турам, в которой сняты очки
football-cards Ячейки столбцов карточек
football-card, football-card-yellow, football-card-yellowred, football-card-red Значки карточек в заголовке
highlight и класс зоны Строки цветовых зон и выделенные ячейки таблицы по турам
ui green, ui yellow, ui red Результаты в шахматке
football-corner, football-team-header, football-diagonal Угловая ячейка, названия команд и диагональ шахматки

Цвета зон и результатов модуль не задаёт — они берутся из стилей вики. Если таких стилей ещё нет, их можно добавить в MediaWiki:Common.css, например:

.football-tables .highlight.green { background-color: #bbf3bb; }
.football-tables .highlight.blue  { background-color: #bbebff; }
.football-tables .highlight.red   { background-color: #ffbbbb; }
.football-results td.ui.green     { background-color: #bbf3bb; }
.football-results td.ui.yellow    { background-color: #ffffbb; }
.football-results td.ui.red       { background-color: #ffbbbb; }
.football-progress td.football-deduction { font-weight: bold; }

Сообщения об ошибках

При ошибке во входных данных модуль вместо таблиц выводит сообщение, начинающееся со слова «Ошибка»:

Сообщение Что исправить
не указан параметр teams Добавьте список команд.
команда с кодом … указана более одного раза Уберите повтор из teams.
матч N имеет неправильный формат В matchN меньше трёх частей через «;».
параметр h2h_matchN имеет неправильный формат В h2h_matchN меньше трёх частей через «;».
в матче N неизвестная команда … Код не входит в teams. Проверьте написание, в том числе не попали ли латинские буквы вместо кириллических.
параметр matchN_yellow имеет неправильный формат … Карточки в матче записываются как число:число (то же для matchN_yellowred и matchN_red).
в параметре matchN_deduction должна быть указана команда матча … Код команды должен совпадать с хозяевами или гостями матча N.
в параметре matchN_deduction не указано, сколько очков снято Вторая часть должна быть ненулевым числом.
параметр yellow_КОД должен быть целым неотрицательным числом … Исправьте итог карточек команды (то же для yellowred_КОД и red_КОД).
параметр rounds может быть равен 1 … или 2 … Укажите 1 для однокругового турнира или уберите параметр.
параметр first_place должен быть целым положительным числом … Укажите номер первого места таблицы, например 7.
в параметре start_КОД больше 10 значений … Частей не больше десяти: И;В;Н;П;МЗ;МП;О;ЖК;2ЖК;КК.
в параметре start_КОД значение … должно быть целым неотрицательным числом … Исправьте названную часть. Дробными и отрицательными могут быть только очки.
в параметре start_КОД очки (О) должны быть числом … Седьмая часть — число, например 29 или 14,5.
в параметре start_КОД число игр … не равно сумме побед, ничьих и поражений … Проверьте И, В, Н и П или оставьте И пустым — тогда оно вычислится.

Нераспознанный счёт работу модуля не останавливает: такой матч не учитывается, а в шахматке на его месте видно «ошибка: …».

Пример

Четыре команды, шесть туров, карточки, снятие очков, цветовые зоны и таблица положения по турам. Без снятия очков первое место занял бы «Спартак».

{{#invoke:Турнирная таблица|main
| teams    = СПА;ЦСК;ЗЕН;ЛОК
| name_СПА = Спартак
| name_ЦСК = ЦСКА
| name_ЗЕН = Зенит
| name_ЛОК = Локомотив

| match1  = 1;1;СПА;ЦСК;2:1
| match2  = 1;1;ЗЕН;ЛОК;1:1
| match3  = 2;1;ЦСК;ЗЕН;0:2
| match4  = 2;1;ЛОК;СПА;1:3
| match5  = 3;1;СПА;ЗЕН;1:1
| match6  = 3;1;ЦСК;ЛОК;2:0
| match7  = 4;2;ЦСК;СПА;1:1
| match8  = 4;2;ЛОК;ЗЕН;0:1
| match9  = 5;2;ЗЕН;ЦСК;3:1
| match10 = 5;2;СПА;ЛОК;2:2
| match11 = 6;2;ЗЕН;СПА;0:2
| match12 = 6;2;ЛОК;ЦСК;перенесён

| match1_yellow     = 2:3
| match1_yellowred  = 0:1
| match4_yellow     = 3:1
| match7_yellow     = 1:2
| match7_red        = 1:0
| match9_yellow     = 2:2
| match11_yellow    = 1:3
| match11_yellowred = 1:0

| match7_deduction = СПА;3;за участие в матче дисквалифицированного игрока

| highlight1 = 1;1;green;Выход в финальный турнир
| highlight2 = 4;1;red;Выбывание

| show_progress       = yes
| progress_highlight1 = 1;green
| progress_highlight2 = 4;red
}}

Результат:

Турнирная таблица
МестоКомандаИВНПМЗМПРМОПовышение или выбывание
1Спартак6330116+512Выход в финальный турнир
2Зенит632185+311
3ЦСКА511358-34
4Локомотив502349-52Выбывание
Критерии определения мест: очки → разница → забитые → победы → название

Движение команд по турам
Команда123456
Спартак111121
ЦСКА443333
Зенит222212
Локомотив334444

Результаты
Дома \ На выездеСпартакЦСКАЗенитЛокомотив
Спартак—2:1
1:1
1:1
2:0
3:1
2:2
ЦСКА1:2
1:1
—0:2
1:3
2:0
перенесён
Зенит1:1
0:2
2:0
3:1
—1:1
1:0
Локомотив1:3
2:2
0:2
перенесён
1:1
0:1
—

Пример турнира из двух этапов

Шесть команд играют первый этап в один круг, затем три лучшие разыгрывают 1–3 места, а остальные — 4–6 места: в своих группах команды играют в два круга. Все результаты первого этапа сохраняются, а при равенстве очков места определяются по личным встречам за весь сезон.

Стартовые показатели в таблицах групп — это строки итоговой таблицы первого этапа, а в h2h_matchN — матчи первого этапа между командами группы. В группе за 4–6 места «Динамо» и ЦСКА набрали поровну очков и во втором этапе обменялись гостевыми победами 2:1, поэтому «Динамо» выше благодаря победе 2:0 в первом этапе. Без строк h2h_match выше оказался бы ЦСКА — у него лучше разница мячей.

=== Первый этап ===
{{#invoke:Турнирная таблица|main
| teams    = СПА;ЦСК;ЗЕН;ЛОК;ДИН;КРА
| name_СПА = Спартак
| name_ЦСК = ЦСКА
| name_ЗЕН = Зенит
| name_ЛОК = Локомотив
| name_ДИН = Динамо
| name_КРА = Краснодар
| rounds   = 1
| heading_level = 4

| tie1 = очки
| tie2 = личные
| tie3 = разница
| tie4 = забитые

| match1  = 1;1;СПА;КРА;1:1
| match2  = 1;1;ДИН;ЦСК;2:0
| match3  = 1;1;ЗЕН;ЛОК;1:2
| match4  = 2;1;ДИН;СПА;0:0
| match5  = 2;1;КРА;ЛОК;1:0
| match6  = 2;1;ЗЕН;ЦСК;1:1
| match7  = 3;1;СПА;ЛОК;0:1
| match8  = 3;1;ЗЕН;ДИН;3:1
| match9  = 3;1;КРА;ЦСК;2:1
| match10 = 4;1;ЗЕН;СПА;1:3
| match11 = 4;1;ЛОК;ЦСК;2:0
| match12 = 4;1;КРА;ДИН;3:0
| match13 = 5;1;СПА;ЦСК;0:2
| match14 = 5;1;КРА;ЗЕН;1:2
| match15 = 5;1;ЛОК;ДИН;2:1

| highlight1 = 1;3;green;Группа за 1–3 места
| highlight2 = 4;3;blue;Группа за 4–6 места
}}

=== Группа за 1–3 места ===
{{#invoke:Турнирная таблица|main
| teams    = ЛОК;КРА;ЗЕН
| name_ЛОК = Локомотив
| name_КРА = Краснодар
| name_ЗЕН = Зенит
| heading_level = 4

| tie1 = очки
| tie2 = личные
| tie3 = разница
| tie4 = забитые

| start_ЛОК = 5;4;0;1;7;3;12
| start_КРА = 5;3;1;1;8;4;10
| start_ЗЕН = 5;2;1;2;8;8;7

| h2h_match1 = 1;1;ЗЕН;ЛОК;1:2
| h2h_match2 = 2;1;КРА;ЛОК;1:0
| h2h_match3 = 5;1;КРА;ЗЕН;1:2

| match1 = 6;1;ЗЕН;КРА;2:2
| match2 = 7;1;ЗЕН;ЛОК;0:2
| match3 = 8;1;ЛОК;КРА;1:0
| match4 = 9;2;КРА;ЗЕН;0:1
| match5 = 10;2;ЛОК;ЗЕН;2:0
| match6 = 11;2;КРА;ЛОК;0:1

| highlight1 = 1;1;green;Чемпион

| show_progress       = yes
| progress_highlight1 = 1;green
}}

=== Группа за 4–6 места ===
{{#invoke:Турнирная таблица|main
| teams       = СПА;ДИН;ЦСК
| name_СПА    = Спартак
| name_ДИН    = Динамо
| name_ЦСК    = ЦСКА
| first_place = 4
| heading_level = 4

| tie1 = очки
| tie2 = личные
| tie3 = разница
| tie4 = забитые

| start_СПА = 5;1;2;2;4;5;5
| start_ДИН = 5;1;1;3;4;8;4
| start_ЦСК = 5;1;1;3;4;7;4

| h2h_match1 = 1;1;ДИН;ЦСК;2:0
| h2h_match2 = 2;1;ДИН;СПА;0:0
| h2h_match3 = 5;1;СПА;ЦСК;0:2

| match1 = 6;1;ЦСК;ДИН;1:2
| match2 = 7;1;ЦСК;СПА;0:1
| match3 = 8;1;СПА;ДИН;1:2
| match4 = 9;2;ДИН;ЦСК;1:2
| match5 = 10;2;СПА;ЦСК;0:2
| match6 = 11;2;ДИН;СПА;0:1

| highlight1 = 6;1;red;Выбывание

| show_progress       = yes
| progress_highlight1 = 6;red
}}

Результат:

Первый этап

Турнирная таблица
МестоКомандаИВНПМЗМПРМОПовышение или выбывание
1Локомотив540173+412Группа за 1–3 места
2Краснодар531184+410
3Зенит52128807
4Спартак512245-15Группа за 4–6 места
5Динамо511348-44
6ЦСКА511347-34
Критерии определения мест: очки → личные → разница → забитые

Результаты
Дома \ На выездеСпартакЦСКАЗенитЛокомотивДинамоКраснодар
Спартак—0:2
3:1
0:1
0:0
1:1
ЦСКА2:0
—1:1
0:2
0:2
1:2
Зенит1:3
1:1
—1:2
3:1
2:1
Локомотив1:0
2:0
2:1
—2:1
0:1
Динамо0:0
2:0
1:3
1:2
—0:3
Краснодар1:1
2:1
1:2
1:0
3:0
—

Группа за 1–3 места

Турнирная таблица
МестоКомандаИВНПМЗМПРМОПовышение или выбывание
1Локомотив440060+612Чемпион
2Зенит411236-34
3Краснодар401325-31
Критерии определения мест: очки → личные → разница → забитые

Движение команд по турам
Команда67891011
Локомотив311111
Краснодар222333
Зенит133222

Результаты
Дома \ На выездеЛокомотивКраснодарЗенит
Локомотив—1:0
1:0
2:0
2:0
Краснодар0:1
0:1
—2:2
0:1
Зенит0:2
0:2
2:2
1:0
—

Группа за 4–6 места

Турнирная таблица
МестоКомандаИВНПМЗМПРМОПовышение или выбывание
1ЦСКА420254+16
2Динамо42025506
3Спартак420234-16
Критерии определения мест: очки → личные → разница → забитые

Движение команд по турам
Команда67891011
Спартак222233
Динамо111112
ЦСКА333321

Результаты
Дома \ На выездеСпартакДинамоЦСКА
Спартак—1:2
1:0
1:0
0:2
Динамо2:1
0:1
—2:1
1:2
ЦСКА0:1
2:0
1:2
2:1
—

local p = {}

-- =========================================================
-- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
-- =========================================================

local function trim(value)
	if value == nil then
		return ""
	end

	return mw.text.trim(tostring(value))
end


local function split(value, separator)
	local result = {}

	for part in mw.text.gsplit(value, separator, true) do
		table.insert(result, trim(part))
	end

	return result
end


local function number(value, default)
	local n = tonumber(value)

	if n == nil then
		return default
	end

	return n
end


-- =========================================================
-- РАЗБОР СЧЁТА
-- =========================================================

local function parseScore(score)
	score = trim(score)

	-- Матч не сыгран
	if score == "" or score == "-" or score == "—" then
		return {
			played = false,
			status = ""
		}
	end

	-- Перенесён
	if score == "перенесён" or score == "перенесен" then
		return {
			played = false,
			status = "перенесён"
		}
	end

	-- Отменён
	if score == "отменён" or score == "отменен" then
		return {
			played = false,
			status = "отменён"
		}
	end

	-- Обычный счёт
	local home, away = score:match("^(%d+)%s*:%s*(%d+)$")

	if home and away then
		return {
			played = true,
			homeGoals = tonumber(home),
			awayGoals = tonumber(away),
			score = home .. ":" .. away
		}
	end

	-- Неизвестный формат
	return {
		played = false,
		status = "ошибка: " .. score
	}
end


-- =========================================================
-- СОЗДАНИЕ КОМАНДЫ
-- =========================================================

local function newTeam(code, name)
	return {
		code = code,
		name = name,

		played = 0,
		wins = 0,
		draws = 0,
		losses = 0,

		goalsFor = 0,
		goalsAgainst = 0,

		points = 0,

		goalDifference = 0,

		highlightColor = nil,
		highlightCount = nil,
		highlightText = nil
	}
end


-- =========================================================
-- СОЗДАНИЕ HTML-ЯЧЕЙКИ
-- =========================================================

local function cell(tag, content, class)
	local element = mw.html.create(tag)

	if class and class ~= "" then
		element:addClass(class)
	end

	if content ~= nil then
		element:wikitext(tostring(content))
	end

	return element
end


-- =========================================================
-- ПРИМЕНЕНИЕ РЕЗУЛЬТАТА К СТАТИСТИКЕ
-- =========================================================

local function applyMatch(teams, match, pointsWin, pointsDraw, pointsLoss)

	if not match.played then
		return
	end

	local homeTeam = teams[match.home]
	local awayTeam = teams[match.away]

	if not homeTeam or not awayTeam then
		return
	end

	homeTeam.played = homeTeam.played + 1
	awayTeam.played = awayTeam.played + 1

	homeTeam.goalsFor =
		homeTeam.goalsFor + match.homeGoals

	homeTeam.goalsAgainst =
		homeTeam.goalsAgainst + match.awayGoals

	awayTeam.goalsFor =
		awayTeam.goalsFor + match.awayGoals

	awayTeam.goalsAgainst =
		awayTeam.goalsAgainst + match.homeGoals


	if match.homeGoals > match.awayGoals then

		homeTeam.wins = homeTeam.wins + 1
		awayTeam.losses = awayTeam.losses + 1

		homeTeam.points =
			homeTeam.points + pointsWin

		awayTeam.points =
			awayTeam.points + pointsLoss


	elseif match.homeGoals < match.awayGoals then

		awayTeam.wins = awayTeam.wins + 1
		homeTeam.losses = homeTeam.losses + 1

		awayTeam.points =
			awayTeam.points + pointsWin

		homeTeam.points =
			homeTeam.points + pointsLoss


	else

		homeTeam.draws = homeTeam.draws + 1
		awayTeam.draws = awayTeam.draws + 1

		homeTeam.points =
			homeTeam.points + pointsDraw

		awayTeam.points =
			awayTeam.points + pointsDraw

	end
end


-- =========================================================
-- РАСЧЁТ РАЗНИЦЫ МЯЧЕЙ
-- =========================================================

local function calculateGoalDifference(teams, teamOrder)

	for _, code in ipairs(teamOrder) do

		local team = teams[code]

		team.goalDifference =
			team.goalsFor - team.goalsAgainst

	end
end


-- =========================================================
-- ЛИЧНЫЕ ВСТРЕЧИ
-- =========================================================

local function headToHead(
	code,
	group,
	matches,
	pointsWin,
	pointsDraw,
	pointsLoss
)

	local result = {
		points = 0,
		goalsFor = 0,
		goalsAgainst = 0,
		goalDifference = 0,
		wins = 0
	}

	for _, match in ipairs(matches) do

		if match.played
			and group[match.home]
			and group[match.away]
		then

			if match.home == code then

				result.goalsFor =
					result.goalsFor + match.homeGoals

				result.goalsAgainst =
					result.goalsAgainst + match.awayGoals


				if match.homeGoals > match.awayGoals then

					result.points =
						result.points + pointsWin

					result.wins =
						result.wins + 1

				elseif match.homeGoals == match.awayGoals then

					result.points =
						result.points + pointsDraw

				else

					result.points =
						result.points + pointsLoss

				end


			elseif match.away == code then

				result.goalsFor =
					result.goalsFor + match.awayGoals

				result.goalsAgainst =
					result.goalsAgainst + match.homeGoals


				if match.awayGoals > match.homeGoals then

					result.points =
						result.points + pointsWin

					result.wins =
						result.wins + 1

				elseif match.awayGoals == match.homeGoals then

					result.points =
						result.points + pointsDraw

				else

					result.points =
						result.points + pointsLoss

				end

			end

		end

	end

	result.goalDifference =
		result.goalsFor - result.goalsAgainst

	return result
end


-- =========================================================
-- СОРТИРОВКА ТАБЛИЦЫ
-- =========================================================

local function sortStandings(
	teams,
	teamOrder,
	matches,
	criteria,
	pointsWin,
	pointsDraw,
	pointsLoss
)

	local standings = {}

	for _, code in ipairs(teamOrder) do
		table.insert(standings, teams[code])
	end


	local function compareTeams(a, b)

		for _, criterion in ipairs(criteria) do

			-- ---------------------------------------------
			-- ОЧКИ
			-- ---------------------------------------------

			if criterion == "очки" then

				if a.points ~= b.points then
					return a.points > b.points
				end


			-- ---------------------------------------------
			-- РАЗНИЦА МЯЧЕЙ
			-- ---------------------------------------------

			elseif criterion == "разница" then

				if a.goalDifference ~= b.goalDifference then
					return a.goalDifference > b.goalDifference
				end


			-- ---------------------------------------------
			-- ЗАБИТЫЕ МЯЧИ
			-- ---------------------------------------------

			elseif criterion == "забитые" then

				if a.goalsFor ~= b.goalsFor then
					return a.goalsFor > b.goalsFor
				end


			-- ---------------------------------------------
			-- ПОБЕДЫ
			-- ---------------------------------------------

			elseif criterion == "победы" then

				if a.wins ~= b.wins then
					return a.wins > b.wins
				end


			-- ---------------------------------------------
			-- ИГРЫ
			-- ---------------------------------------------

			elseif criterion == "игры" then

				if a.played ~= b.played then
					return a.played > b.played
				end


			-- ---------------------------------------------
			-- ЛИЧНЫЕ ВСТРЕЧИ
			-- ---------------------------------------------

			elseif criterion == "личные" then

				-- Группа команд с одинаковым количеством очков

				local group = {}

				for _, code in ipairs(teamOrder) do

					local team = teams[code]

					if team.points == a.points then
						group[code] = true
					end

				end


				local ha =
					headToHead(
						a.code,
						group,
						matches,
						pointsWin,
						pointsDraw,
						pointsLoss
					)

				local hb =
					headToHead(
						b.code,
						group,
						matches,
						pointsWin,
						pointsDraw,
						pointsLoss
					)


				if ha.points ~= hb.points then
					return ha.points > hb.points
				end


				if ha.goalDifference ~= hb.goalDifference then
					return ha.goalDifference > hb.goalDifference
				end


				if ha.goalsFor ~= hb.goalsFor then
					return ha.goalsFor > hb.goalsFor
				end


				if ha.wins ~= hb.wins then
					return ha.wins > hb.wins
				end

			-- ---------------------------------------------
			-- НАЗВАНИЕ
			-- ---------------------------------------------

			elseif criterion == "название" then

				local aname =
					mw.text.unstrip(a.name)

				local bname =
					mw.text.unstrip(b.name)

				if aname ~= bname then
					return aname < bname
				end

			end

		end


		-- Последний резервный критерий
		return a.code < b.code
	end


	table.sort(standings, compareTeams)

	return standings
end


-- =========================================================
-- ОСНОВНАЯ ФУНКЦИЯ
-- =========================================================

function p.main(frame)

	local args = frame.args


	-- =====================================================
	-- НАСТРОЙКИ ОЧКОВ
	-- =====================================================

	local pointsWin =
		number(args.points_win, 3)

	local pointsDraw =
		number(args.points_draw, 1)

	local pointsLoss =
		number(args.points_loss, 0)


	-- =====================================================
	-- КОМАНДЫ
	-- =====================================================

	local teamsParameter =
		trim(args.teams)

	if teamsParameter == "" then
		return "'''Ошибка:''' не указан параметр ''teams''."
	end


	local teamCodes =
		split(teamsParameter, ";")


	local teams = {}
	local teamOrder = {}


	for _, code in ipairs(teamCodes) do

		if code ~= "" then

			if teams[code] then

				return
					"'''Ошибка:''' команда с кодом '''" ..
					code ..
					"''' указана более одного раза."

			end


			local name =
				trim(args["name_" .. code])


			if name == "" then
				name = code
			end


			teams[code] =
				newTeam(code, name)


			table.insert(
				teamOrder,
				code
			)

		end
	end


	-- =====================================================
	-- МАТЧИ
	-- =====================================================

	local matches = {}

	local matchNumber = 1


	while true do

		local value =
			args["match" .. matchNumber]


		if value == nil or trim(value) == "" then
			break
		end


		local parts =
			split(value, ";")


		-- Новый формат:
		--
		-- тур;круг;хозяева;гости;счёт
		--
		-- 1;1;НАР;АПР;2:1

		if #parts < 5 then

			return
				"'''Ошибка:''' матч " ..
				matchNumber ..
				" имеет неправильный формат. " ..
				"Используйте: '''тур;круг;хозяева;гости;счёт'''."

		end


		local tour =
			trim(parts[1])

		local round =
			trim(parts[2])

		local home =
			trim(parts[3])

		local away =
			trim(parts[4])

		local score =
			trim(parts[5])


		-- Проверяем номер тура

		if tonumber(tour) == nil then

			return
				"'''Ошибка:''' в матче " ..
				matchNumber ..
				" номер тура должен быть числом."

		end


		-- Проверяем номер круга

		if tonumber(round) == nil then

			return
				"'''Ошибка:''' в матче " ..
				matchNumber ..
				" номер круга должен быть числом."

		end


		-- Проверяем команды

		if not teams[home] then

			return
				"'''Ошибка:''' в матче " ..
				matchNumber ..
				" неизвестная команда '''" ..
				home ..
				"'''."

		end


		if not teams[away] then

			return
				"'''Ошибка:''' в матче " ..
				matchNumber ..
				" неизвестная команда '''" ..
				away ..
				"'''."

		end


		-- Разбираем счёт

		local parsed =
			parseScore(score)


		local match = {

			number = matchNumber,

			tour = tonumber(tour),

			round = tonumber(round),

			home = home,

			away = away,

			played = parsed.played,

			status = parsed.status,

			homeGoals = parsed.homeGoals,

			awayGoals = parsed.awayGoals,

			score = parsed.score
		}


		table.insert(
			matches,
			match
		)


		-- Добавляем матч в общую статистику

		applyMatch(
			teams,
			match,
			pointsWin,
			pointsDraw,
			pointsLoss
		)


		matchNumber =
			matchNumber + 1

	end


	-- =====================================================
	-- РАЗНИЦА МЯЧЕЙ
	-- =====================================================

	calculateGoalDifference(
		teams,
		teamOrder
	)


	-- =====================================================
	-- КРИТЕРИИ ОПРЕДЕЛЕНИЯ МЕСТ
	-- =====================================================

	local criteria = {}


	for i = 1, 10 do

		local criterion =
			trim(args["tie" .. i])


		if criterion ~= "" then

			table.insert(
				criteria,
				criterion
			)

		end

	end


	-- Значения по умолчанию

	if #criteria == 0 then

		criteria = {
			"очки",
			"разница",
			"забитые",
			"победы",
			"название"
		}

	end


	-- =====================================================
	-- ПОДСВЕТКА ТАБЛИЦЫ
	-- =====================================================

	local highlights = {}


	for i = 1, 10 do

		local hl =
			trim(args["highlight" .. i])


		if hl == "" then
			break
		end


		local parts =
			split(hl, ";")


		local highlight = {

			place =
				tonumber(parts[1]),

			count =
				tonumber(parts[2]) or 1,

			color =
				parts[3] or "",

			text =
				parts[4] or ""
		}


		if highlight.place then

			table.insert(
				highlights,
				highlight
			)

		end

	end


	local hasHighlightText = false


	for _, hl in ipairs(highlights) do

		if hl.text ~= "" then
			hasHighlightText = true
			break
		end

	end


	-- =====================================================
	-- ИТОГОВАЯ ТАБЛИЦА
	-- =====================================================

	local standings =
		sortStandings(
			teams,
			teamOrder,
			matches,
			criteria,
			pointsWin,
			pointsDraw,
			pointsLoss
		)


	-- =====================================================
	-- ПРИМЕНЯЕМ ПОДСВЕТКУ
	-- =====================================================

	for _, hl in ipairs(highlights) do

		for position = hl.place,
			hl.place + hl.count - 1 do

			if standings[position] then

				standings[position].highlightColor =
					hl.color

			end

		end


		if standings[hl.place] then

			standings[hl.place].highlightCount =
				hl.count

			standings[hl.place].highlightText =
				hl.text

		end

	end


	-- =====================================================
	-- КОРЕНЕВОЙ DIV
	-- =====================================================

	local root =
		mw.html.create("div")

	root:addClass("football-tables")


	-- =====================================================
	-- ТАБЛИЦА ЧЕМПИОНАТА
	-- =====================================================

	local standingsTitle =
		mw.html.create("div")
			:addClass("football-title")
			:wikitext(
				"'''Турнирная таблица'''"
			)


	root:node(
		standingsTitle
	)


	local standingsTable =
		mw.html.create("table")
			:addClass("wikitable")
			:addClass("football-standings")
			:css(
				"text-align",
				"center"
			)


	-- Заголовок

	local headings = {

		"Место",
		"Команда",
		"И",
		"В",
		"Н",
		"П",
		"МЗ",
		"МП",
		"РМ",
		"О"
	}


	if hasHighlightText then

		table.insert(
			headings,
			"Повышение или выбывание"
		)

	end


	local standingsHeader =
		mw.html.create("tr")


	for _, heading in ipairs(headings) do

		standingsHeader:node(
			cell("th", heading)
		)

	end


	standingsTable:node(
		standingsHeader
	)


	-- Строки

	for position, team in ipairs(standings) do

		local row =
			mw.html.create("tr")


		if team.highlightColor
			and team.highlightColor ~= ""
		then

			row:addClass("highlight")
			row:addClass(team.highlightColor)

		end


		row:node(
			cell(
				"td",
				position
			)
		)


		local teamName =
			cell(
				"td",
				team.name
			)

		teamName:css(
			"text-align",
			"left"
		)


		row:node(
			teamName
		)


		row:node(
			cell(
				"td",
				team.played
			)
		)


		row:node(
			cell(
				"td",
				team.wins
			)
		)


		row:node(
			cell(
				"td",
				team.draws
			)
		)


		row:node(
			cell(
				"td",
				team.losses
			)
		)


		row:node(
			cell(
				"td",
				team.goalsFor
			)
		)


		row:node(
			cell(
				"td",
				team.goalsAgainst
			)
		)


		local gd


		if team.goalDifference > 0 then

			gd =
				"+" ..
				team.goalDifference

		else

			gd =
				tostring(
					team.goalDifference
				)

		end


		row:node(
			cell(
				"td",
				gd
			)
		)


		row:node(
			cell(
				"td",
				team.points
			)
		)


		-- Текст повышения / выбывания

		if hasHighlightText
			and team.highlightText
			and team.highlightText ~= ""
		then

			local count =
				team.highlightCount or 1


			row:node(
				cell("td")
					:wikitext(
						team.highlightText
					)
					:attr(
						"rowspan",
						count
					)
			)

		end


		standingsTable:node(
			row
		)

	end


	root:node(
		standingsTable
	)


	-- =====================================================
	-- ИНФОРМАЦИЯ О КРИТЕРИЯХ
	-- =====================================================

	local criteriaText =
		table.concat(
			criteria,
			" → "
		)


	local info =
		mw.html.create("div")
			:addClass("football-info")
			:wikitext(
				"Критерии определения мест: " ..
				criteriaText
			)


	root:node(info)


	-- =====================================================
	-- ТАБЛИЦА ДВИЖЕНИЯ ПО ТУРАМ
	-- =====================================================

	local showProgress =
		trim(args.show_progress)


	if showProgress == "yes"
		or showProgress == "да"
	then

		-- -------------------------------------------------
		-- Получаем список всех туров
		-- -------------------------------------------------

		local tours = {}
		local tourExists = {}


		for _, match in ipairs(matches) do

			if not tourExists[match.tour] then

				tourExists[match.tour] = true

				table.insert(
					tours,
					match.tour
				)

			end

		end


		-- Сортируем туры по номеру

		table.sort(
			tours,
			function(a, b)
				return a < b
			end
		)


		-- -------------------------------------------------
		-- Создаём заголовок
		-- -------------------------------------------------

		root:node(
			mw.html.create("br")
		)


		local progressTitle =
			mw.html.create("div")
				:addClass("football-title")
				:wikitext(
					"'''Движение команд по турам'''"
				)


		root:node(
			progressTitle
		)


		local progressTable =
			mw.html.create("table")
				:addClass("wikitable")
				:addClass("football-progress")
				:css(
					"text-align",
					"center"
				)


		-- -------------------------------------------------
		-- Заголовок таблицы
		-- -------------------------------------------------

		local progressHeader =
			mw.html.create("tr")


		progressHeader:node(
			cell(
				"th",
				"Команда"
			)
		)


		for _, tour in ipairs(tours) do

			progressHeader:node(
				cell(
					"th",
					tostring(tour)
				)
			)

		end


		progressTable:node(
			progressHeader
		)


		-- -------------------------------------------------
		-- Состояние команд до начала чемпионата
		-- -------------------------------------------------

		local progressTeams = {}


		for _, code in ipairs(teamOrder) do

			progressTeams[code] =
				newTeam(
					code,
					teams[code].name
				)

		end


		-- -------------------------------------------------
		-- Здесь будем хранить позиции команд
		-- -------------------------------------------------

		local positions = {}


		for _, code in ipairs(teamOrder) do

			positions[code] = {}

		end


		-- -------------------------------------------------
		-- Последовательно обрабатываем каждый тур
		-- -------------------------------------------------

		for _, tour in ipairs(tours) do

			-- Добавляем результаты текущего тура

			for _, match in ipairs(matches) do

				if match.tour == tour
					and match.played
				then

					applyMatch(
						progressTeams,
						match,
						pointsWin,
						pointsDraw,
						pointsLoss
					)

				end

			end


			-- Пересчитываем разницу мячей

			calculateGoalDifference(
				progressTeams,
				teamOrder
			)


			-- Строим таблицу после этого тура

			local progressStandings =
				sortStandings(
					progressTeams,
					teamOrder,
					-- Только сыгранные к этому моменту
					-- матчи используются для "личных"
					-- встреч.
					(function()

						local previousMatches = {}

						for _, match in ipairs(matches) do

							if match.played
								and match.tour <= tour
							then

								table.insert(
									previousMatches,
									match
								)

							end

						end

						return previousMatches

					end)(),
					criteria,
					pointsWin,
					pointsDraw,
					pointsLoss
				)


			-- Запоминаем место каждой команды

			for place, team in ipairs(progressStandings) do

				positions[team.code][tour] =
					place

			end

		end


		-- -------------------------------------------------
		-- Строки команд
		-- -------------------------------------------------

		for _, code in ipairs(teamOrder) do

			local row =
				mw.html.create("tr")


			local teamName =
				cell(
					"td",
					teams[code].name
				)

			teamName:css(
				"text-align",
				"left"
			)


			row:node(
				teamName
			)


			for _, tour in ipairs(tours) do

				local position =
					positions[code][tour]


				if position then

					row:node(
						cell(
							"td",
							position
						)
					)

				else

					-- Теоретически сюда можно попасть,
					-- если команда ещё не участвовала
					-- ни в одном сыгранном матче.

					row:node(
						cell(
							"td",
							"—"
						)
					)

				end

			end


			progressTable:node(
				row
			)

		end


		root:node(
			progressTable
		)

	end


	-- =====================================================
	-- РЕЗУЛЬТАТЫ
	-- =====================================================

	root:node(
		mw.html.create("br")
	)


	local resultsTitle =
		mw.html.create("div")
			:addClass("football-title")
			:wikitext(
				"'''Результаты'''"
			)


	root:node(
		resultsTitle
	)


	local resultsTable =
		mw.html.create("table")
			:addClass("wikitable")
			:addClass("football-results")
			:css(
				"text-align",
				"center"
			)


	-- Заголовок

	local header =
		mw.html.create("tr")


	header:node(
		cell(
			"th",
			"Дома \\ На выезде",
			"football-corner"
		)
	)


	for _, code in ipairs(teamOrder) do

		header:node(
			cell(
				"th",
				teams[code].name,
				"football-team-header"
			)
		)

	end


	resultsTable:node(
		header
	)


	-- -----------------------------------------------------
	-- Матрица результатов
	-- -----------------------------------------------------

	for _, homeCode in ipairs(teamOrder) do

		local row =
			mw.html.create("tr")


		local teamHeader =
			cell(
				"th",
				teams[homeCode].name,
				"football-team-header"
			)


		teamHeader:css(
			"text-align",
			"right"
		)


		row:node(
			teamHeader
		)


		for _, awayCode in ipairs(teamOrder) do

			if homeCode == awayCode then

				row:node(
					cell(
						"td",
						"—",
						"football-diagonal"
					)
				)

			else

				local resultCell =
					mw.html.create("td")


				local found = false


				-- -------------------------------------------------
				-- Ищем ВСЕ матчи между командами
				-- -------------------------------------------------

				for _, match in ipairs(matches) do


					-- Хозяева этой строки являются хозяевами матча

					if match.home == homeCode
						and match.away == awayCode
					then

						found = true


						local text


						if match.played then

							text =
								match.score


							if match.homeGoals >
								match.awayGoals
							then

								resultCell
									:addClass("ui")
									:addClass("green")

							elseif match.homeGoals <
								match.awayGoals
							then

								resultCell
									:addClass("ui")
									:addClass("red")

							else

								resultCell
									:addClass("ui")
									:addClass("yellow")

							end


						elseif match.status ~= "" then

							text =
								match.status

						else

							text =
								"—"

						end


						resultCell
							:wikitext(
								text
							)
							:tag("br")
							:done()


					-- -------------------------------------------------
					-- Обратный матч
					-- -------------------------------------------------

					elseif match.home == awayCode
						and match.away == homeCode
					then

						found = true


						local text


						if match.played then

							-- Переворачиваем счёт,
							-- потому что строка показывает
							-- результат с точки зрения homeCode.

							text =
								tostring(
									match.awayGoals
								) ..
								":" ..
								tostring(
									match.homeGoals
								)


							if match.awayGoals >
								match.homeGoals
							then

								resultCell
									:addClass("ui")
									:addClass("green")

							elseif match.awayGoals <
								match.homeGoals
							then

								resultCell
									:addClass("ui")
									:addClass("red")

							else

								resultCell
									:addClass("ui")
									:addClass("yellow")

							end


						elseif match.status ~= "" then

							text =
								match.status

						else

							text =
								"—"

						end


						resultCell
							:wikitext(
								text
							)
							:tag("br")
							:done()

					end

				end


				if not found then

					resultCell:wikitext(
						"—"
					)

				end


				row:node(
					resultCell
				)

			end

		end


		resultsTable:node(
			row
		)

	end


	root:node(
		resultsTable
	)


	return tostring(root)
end


return p