Модуль:Football: различия между версиями
Andrew (обсуждение | вклад) Нет описания правки |
Andrew (обсуждение | вклад) Добавлен вывод таблицы с движением по турам Метка: отменено |
||
| Строка 104: | Строка 104: | ||
goalsAgainst = 0, | goalsAgainst = 0, | ||
points = 0 | points = 0, | ||
goalDifference = 0, | |||
highlightColor = nil, | |||
highlightCount = nil, | |||
highlightText = nil | |||
} | } | ||
end | end | ||
| Строка 110: | Строка 116: | ||
-- ========================================================= | -- ========================================================= | ||
-- СОЗДАНИЕ HTML | -- СОЗДАНИЕ HTML-ЯЧЕЙКИ | ||
-- ========================================================= | -- ========================================================= | ||
| Строка 117: | Строка 123: | ||
if class and class ~= "" then | if class and class ~= "" then | ||
element | element:addClass(class) | ||
end | end | ||
if content ~= nil then | if content ~= nil then | ||
element | element:wikitext(tostring(content)) | ||
end | end | ||
| Строка 131: | Строка 135: | ||
-- ========================================================= | -- ========================================================= | ||
-- | -- ПРИМЕНЕНИЕ РЕЗУЛЬТАТА К СТАТИСТИКЕ | ||
-- ========================================================= | -- ========================================================= | ||
function | local function applyMatch(teams, match, pointsWin, pointsDraw, pointsLoss) | ||
if | if not match.played then | ||
return | return | ||
end | end | ||
local | local homeTeam = teams[match.home] | ||
local awayTeam = teams[match.away] | |||
if not homeTeam or not awayTeam then | |||
return | |||
end | 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 | for _, code in ipairs(teamOrder) do | ||
| Строка 360: | Строка 220: | ||
end | end | ||
end | |||
-- ========================================================= | |||
-- ЛИЧНЫЕ ВСТРЕЧИ | |||
-- ========================================================= | |||
local function headToHead( | |||
code, | |||
group, | |||
matches, | |||
pointsWin, | |||
pointsDraw, | |||
pointsLoss | |||
) | |||
local | 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 | 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 | ||
| Строка 448: | Строка 314: | ||
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 | end | ||
local function compareTeams(a, b) | local function compareTeams(a, b) | ||
| Строка 585: | Строка 404: | ||
-- --------------------------------------------- | -- --------------------------------------------- | ||
-- ЛИЧНЫЕ | -- ЛИЧНЫЕ ВСТРЕЧИ | ||
-- --------------------------------------------- | -- --------------------------------------------- | ||
elseif criterion == "личные" then | elseif criterion == "личные" then | ||
-- | -- Группа команд с одинаковым количеством очков | ||
local group = {} | local group = {} | ||
| Строка 605: | Строка 423: | ||
end | end | ||
local ha = headToHead(a.code, group) | |||
local hb = headToHead(b.code, group) | 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 | if ha.points ~= hb.points then | ||
return ha.points > hb.points | return ha.points > hb.points | ||
end | end | ||
if ha.goalDifference ~= hb.goalDifference then | if ha.goalDifference ~= hb.goalDifference then | ||
return ha.goalDifference > hb.goalDifference | return ha.goalDifference > hb.goalDifference | ||
end | end | ||
if ha.goalsFor ~= hb.goalsFor then | if ha.goalsFor ~= hb.goalsFor then | ||
return ha.goalsFor > hb.goalsFor | return ha.goalsFor > hb.goalsFor | ||
end | end | ||
if ha.wins ~= hb.wins then | if ha.wins ~= hb.wins then | ||
return ha.wins > hb.wins | return ha.wins > hb.wins | ||
end | end | ||
-- --------------------------------------------- | -- --------------------------------------------- | ||
| Строка 646: | Строка 485: | ||
-- Последний резервный критерий | -- Последний резервный критерий | ||
-- | 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 | end | ||
-- ===================================================== | -- ===================================================== | ||
-- | -- РАЗНИЦА МЯЧЕЙ | ||
-- ===================================================== | -- ===================================================== | ||
local | 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 | 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 | for _, hl in ipairs(highlights) do | ||
if hl.text ~= "" then | |||
hasHighlightText = true | |||
break | |||
end | end | ||
end | |||
-- ===================================================== | |||
-- ИТОГОВАЯ ТАБЛИЦА | |||
-- ===================================================== | |||
local standings = | |||
sortStandings( | |||
teams, | |||
teamOrder, | |||
if | 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 | ||
end | |||
if standings[hl.place] then | |||
standings[hl.place].highlightCount = | |||
hl.count | |||
standings[hl.place].highlightText = | |||
hl.text | |||
end | end | ||
end | end | ||
-- ===================================================== | |||
-- КОРЕНЕВОЙ DIV | |||
-- ===================================================== | |||
local root = | |||
mw.html.create("div") | |||
root:addClass("football-tables") | |||
-- ===================================================== | -- ===================================================== | ||
-- ТАБЛИЦА ЧЕМПИОНАТА | -- ТАБЛИЦА ЧЕМПИОНАТА | ||
-- ===================================================== | -- ===================================================== | ||
local standingsTitle = | local standingsTitle = | ||
mw.html.create("div") | mw.html.create("div") | ||
:addClass("football-title") | :addClass("football-title") | ||
:wikitext("'''Турнирная таблица'''") | :wikitext( | ||
"'''Турнирная таблица'''" | |||
) | |||
root:node(standingsTitle) | root:node( | ||
standingsTitle | |||
) | |||
| Строка 713: | Строка 927: | ||
:addClass("wikitable") | :addClass("wikitable") | ||
:addClass("football-standings") | :addClass("football-standings") | ||
:css("text-align", "center") | :css( | ||
"text-align", | |||
"center" | |||
) | |||
-- Заголовок | -- Заголовок | ||
local | local headings = { | ||
"Место", | "Место", | ||
"Команда", | "Команда", | ||
| Строка 733: | Строка 948: | ||
"О" | "О" | ||
} | } | ||
if | |||
table.insert(headings, "Повышение или выбывание") | if hasHighlightText then | ||
table.insert( | |||
headings, | |||
"Повышение или выбывание" | |||
) | |||
end | end | ||
local standingsHeader = | |||
mw.html.create("tr") | |||
for _, heading in ipairs(headings) do | for _, heading in ipairs(headings) do | ||
| Строка 746: | Строка 972: | ||
end | end | ||
standingsTable:node(standingsHeader) | |||
standingsTable:node( | |||
standingsHeader | |||
) | |||
| Строка 755: | Строка 984: | ||
local row = | local row = | ||
mw.html.create("tr") | mw.html.create("tr") | ||
if team. | |||
row:addClass("highlight"):addClass(team.highlightColor) | if team.highlightColor | ||
and team.highlightColor ~= "" | |||
then | |||
row:addClass("highlight") | |||
row:addClass(team.highlightColor) | |||
end | end | ||
row:node( | row:node( | ||
cell("td", position) | cell( | ||
"td", | |||
position | |||
) | |||
) | |||
local teamName = | |||
cell( | |||
"td", | |||
team.name | |||
) | |||
teamName:css( | |||
"text-align", | |||
"left" | |||
) | ) | ||
row:node( | row:node( | ||
teamName | teamName | ||
| Строка 773: | Строка 1022: | ||
row:node( | row:node( | ||
cell("td", team.played) | cell( | ||
"td", | |||
team.played | |||
) | |||
) | ) | ||
row:node( | row:node( | ||
cell("td", team.wins) | cell( | ||
"td", | |||
team.wins | |||
) | |||
) | ) | ||
row:node( | row:node( | ||
cell("td", team.draws) | cell( | ||
"td", | |||
team.draws | |||
) | |||
) | ) | ||
row:node( | row:node( | ||
cell("td", team.losses) | cell( | ||
"td", | |||
team.losses | |||
) | |||
) | ) | ||
row:node( | row:node( | ||
cell( "td", team.goalsFor) | cell( | ||
"td", | |||
team.goalsFor | |||
) | |||
) | ) | ||
row:node( | row:node( | ||
cell("td",team.goalsAgainst) | cell( | ||
"td", | |||
team.goalsAgainst | |||
) | |||
) | ) | ||
local gd | local gd | ||
if team.goalDifference > 0 then | if team.goalDifference > 0 then | ||
gd = "+" .. team.goalDifference | |||
gd = | |||
"+" .. | |||
team.goalDifference | |||
else | else | ||
gd = tostring(team.goalDifference) | |||
gd = | |||
tostring( | |||
team.goalDifference | |||
) | |||
end | end | ||
row:node( | row:node( | ||
cell("td", gd) | cell( | ||
"td", | |||
gd | |||
) | |||
) | ) | ||
row:node( | row:node( | ||
cell("td", team.points) | cell( | ||
"td", | |||
team.points | |||
) | |||
) | ) | ||
if | |||
-- Текст повышения / выбывания | |||
if hasHighlightText | |||
and team.highlightText | |||
and team.highlightText ~= "" | |||
then | |||
local count = | |||
team.highlightCount or 1 | |||
row:node( | row:node( | ||
cell("td") | cell("td") | ||
:wikitext(team.highlightText) | :wikitext( | ||
:attr("rowspan", | team.highlightText | ||
) | |||
:attr( | |||
"rowspan", | |||
count | |||
) | |||
) | |||
end | end | ||
standingsTable:node(row) | standingsTable:node( | ||
row | |||
) | |||
end | end | ||
root:node(standingsTable) | root:node( | ||
standingsTable | |||
) | |||
| Строка 839: | Строка 1145: | ||
-- ===================================================== | -- ===================================================== | ||
local criteriaText = table.concat(criteria, " → ") | local criteriaText = | ||
table.concat( | |||
criteria, | |||
" → " | |||
) | |||
local info = | local info = | ||
| Строка 848: | Строка 1159: | ||
criteriaText | criteriaText | ||
) | ) | ||
root:node(info) | 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 | |||
-- ===================================================== | -- ===================================================== | ||
| Строка 855: | Строка 1470: | ||
-- ===================================================== | -- ===================================================== | ||
root:node(mw.html.create("br" | root:node( | ||
mw.html.create("br") | |||
) | |||
local resultsTitle = | local resultsTitle = | ||
mw.html.create("div") | mw.html.create("div") | ||
:addClass("football-title") | :addClass("football-title") | ||
:wikitext("'''Результаты'''") | :wikitext( | ||
"'''Результаты'''" | |||
) | |||
root:node(resultsTitle) | |||
root:node( | |||
resultsTitle | |||
) | |||
| Строка 871: | Строка 1492: | ||
:addClass("wikitable") | :addClass("wikitable") | ||
:addClass("football-results") | :addClass("football-results") | ||
:css( | |||
"text-align", | |||
"center" | |||
) | |||
| Строка 878: | Строка 1502: | ||
local header = | local header = | ||
mw.html.create("tr") | mw.html.create("tr") | ||
header:node( | header:node( | ||
cell("th", "Дома \\ На выезде", "football-corner") | cell( | ||
"th", | |||
"Дома \\ На выезде", | |||
"football-corner" | |||
) | |||
) | ) | ||
for _, code in ipairs(teamOrder) do | for _, code in ipairs(teamOrder) do | ||
| Строка 895: | Строка 1525: | ||
end | end | ||
resultsTable:node( | |||
header | |||
) | |||
-- ----------------------------------------------------- | |||
-- Матрица результатов | -- Матрица результатов | ||
-- ----------------------------------------------------- | |||
for _, homeCode in ipairs(teamOrder) do | for _, homeCode in ipairs(teamOrder) do | ||
| Строка 904: | Строка 1539: | ||
local row = | local row = | ||
mw.html.create("tr") | mw.html.create("tr") | ||
local teamHeader = | local teamHeader = | ||
cell( | cell( | ||
"th", | "th", | ||
| Строка 912: | Строка 1547: | ||
"football-team-header" | "football-team-header" | ||
) | ) | ||
teamHeader:css("text-align", "right") | |||
row:node(teamHeader) | teamHeader:css( | ||
"text-align", | |||
"right" | |||
) | |||
row:node( | |||
teamHeader | |||
) | |||
| Строка 922: | Строка 1565: | ||
row:node( | row:node( | ||
cell("td", "—", "football-diagonal") | cell( | ||
"td", | |||
"—", | |||
"football-diagonal" | |||
) | |||
) | ) | ||
| Строка 929: | Строка 1576: | ||
local resultCell = | local resultCell = | ||
mw.html.create("td") | mw.html.create("td") | ||
local found = false | local found = false | ||
-- ------------------------------------------------- | |||
-- Ищем ВСЕ матчи между командами | -- Ищем ВСЕ матчи между командами | ||
-- ------------------------------------------------- | |||
for _, match in ipairs(matches) do | for _, match in ipairs(matches) do | ||
if match.home == homeCode and | |||
-- Хозяева этой строки являются хозяевами матча | |||
if match.home == homeCode | |||
and match.away == awayCode | |||
then | |||
found = true | found = true | ||
local text | local text | ||
if match.played then | if match.played then | ||
text = match.score | |||
if match.homeGoals > match.awayGoals then | text = | ||
match.score | |||
if match.homeGoals > | |||
match.awayGoals | |||
then | |||
resultCell | resultCell | ||
:addClass("ui") | :addClass("ui") | ||
:addClass("green") | :addClass("green") | ||
elseif match.homeGoals < match.awayGoals then | |||
elseif match.homeGoals < | |||
match.awayGoals | |||
then | |||
resultCell | resultCell | ||
:addClass("ui") | :addClass("ui") | ||
:addClass("red") | :addClass("red") | ||
else | |||
else | |||
resultCell | resultCell | ||
:addClass("ui") | :addClass("ui") | ||
:addClass("yellow") | :addClass("yellow") | ||
end | end | ||
elseif match.status ~= "" then | elseif match.status ~= "" then | ||
text = match.status | |||
text = | |||
match.status | |||
else | else | ||
text = "—" | |||
text = | |||
"—" | |||
end | end | ||
resultCell | resultCell | ||
:wikitext(text) | :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() | :done() | ||
| Строка 974: | Строка 1733: | ||
if not found then | if not found then | ||
resultCell:wikitext("—") | |||
resultCell:wikitext( | |||
"—" | |||
) | |||
end | end | ||
row:node(resultCell) | row:node( | ||
resultCell | |||
) | |||
end | end | ||
| Строка 984: | Строка 1749: | ||
end | end | ||
resultsTable:node(row) | |||
resultsTable:node( | |||
row | |||
) | |||
end | end | ||
root:node(resultsTable) | root:node( | ||
resultsTable | |||
) | |||
Версия от 14:20, 14 сентября 2026
Модуль строит для кругового футбольного турнира — двухкругового или однокругового — турнирную таблицу, шахматку результатов и, по желанию, таблицу положения команд по турам. Очки, разница мячей, места, карточки и снятия очков считаются автоматически по данным о матчах.
Турнир, в котором после первого этапа таблица делится на группы, оформляется несколькими таблицами: таблицы групп начинаются с итогов первого этапа — см. «Турнир из нескольких этапов».
Использование
Модуль вызывается напрямую из статьи:
{{#invoke:Football|main| … }}
Модуль читает только параметры самого вызова #invoke. Если нужен шаблон-обёртка, он должен явно передавать в модуль каждый параметр.
Модуль выводит по порядку:
- турнирную таблицу — без собственного заголовка (заголовок раздела ставится в статье);
- строку «Критерии определения мест» и, если с какой-то команды снимались очки, блок «Примечания»;
- раздел «Результаты» с шахматкой;
- раздел «Положение команд по турам» — только при
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 места, — и команды доигрывают в своих группах. Такой турнир оформляется несколькими таблицами:
- первый этап — обычная таблица всех команд (если этап однокруговой — с
rounds = 1); - каждая группа второго этапа — отдельная таблица только из команд группы и матчей второго этапа. Итоги первого этапа задаются стартовыми показателями
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 | Спартак | 6 | 3 | 3 | 0 | 11 | 6 | +5 | 12 | Выход в финальный турнир |
| 2 | Зенит | 6 | 3 | 2 | 1 | 8 | 5 | +3 | 11 | |
| 3 | ЦСКА | 5 | 1 | 1 | 3 | 5 | 8 | -3 | 4 | |
| 4 | Локомотив | 5 | 0 | 2 | 3 | 4 | 9 | -5 | 2 | Выбывание |
| Команда | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| Спартак | 1 | 1 | 1 | 1 | 2 | 1 |
| ЦСКА | 4 | 4 | 3 | 3 | 3 | 3 |
| Зенит | 2 | 2 | 2 | 2 | 1 | 2 |
| Локомотив | 3 | 3 | 4 | 4 | 4 | 4 |
| Дома \ На выезде | Спартак | ЦСКА | Зенит | Локомотив |
|---|---|---|---|---|
| Спартак | — | 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 | Локомотив | 5 | 4 | 0 | 1 | 7 | 3 | +4 | 12 | Группа за 1–3 места |
| 2 | Краснодар | 5 | 3 | 1 | 1 | 8 | 4 | +4 | 10 | |
| 3 | Зенит | 5 | 2 | 1 | 2 | 8 | 8 | 0 | 7 | |
| 4 | Спартак | 5 | 1 | 2 | 2 | 4 | 5 | -1 | 5 | Группа за 4–6 места |
| 5 | Динамо | 5 | 1 | 1 | 3 | 4 | 8 | -4 | 4 | |
| 6 | ЦСКА | 5 | 1 | 1 | 3 | 4 | 7 | -3 | 4 |
| Дома \ На выезде | Спартак | ЦСКА | Зенит | Локомотив | Динамо | Краснодар |
|---|---|---|---|---|---|---|
| Спартак | — | 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 | Локомотив | 4 | 4 | 0 | 0 | 6 | 0 | +6 | 12 | Чемпион |
| 2 | Зенит | 4 | 1 | 1 | 2 | 3 | 6 | -3 | 4 | |
| 3 | Краснодар | 4 | 0 | 1 | 3 | 2 | 5 | -3 | 1 |
| Команда | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|
| Локомотив | 3 | 1 | 1 | 1 | 1 | 1 |
| Краснодар | 2 | 2 | 2 | 3 | 3 | 3 |
| Зенит | 1 | 3 | 3 | 2 | 2 | 2 |
| Дома \ На выезде | Локомотив | Краснодар | Зенит |
|---|---|---|---|
| Локомотив | — | 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 | ЦСКА | 4 | 2 | 0 | 2 | 5 | 4 | +1 | 6 | |
| 2 | Динамо | 4 | 2 | 0 | 2 | 5 | 5 | 0 | 6 | |
| 3 | Спартак | 4 | 2 | 0 | 2 | 3 | 4 | -1 | 6 |
| Команда | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|
| Спартак | 2 | 2 | 2 | 2 | 3 | 3 |
| Динамо | 1 | 1 | 1 | 1 | 1 | 2 |
| ЦСКА | 3 | 3 | 3 | 3 | 2 | 1 |
| Дома \ На выезде | Спартак | Динамо | ЦСКА |
|---|---|---|---|
| Спартак | — | 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