Разработка
Настольный теннис, борьба, баскетбол: один движок для трёх совершенно разных видов спорта
Наглядный разбор того, как одна и та же инфраструктура подсчёта справляется с розыгрышным видом спорта, единоборством и видом спорта с игровым временем и не превращается в безликую кашу.
Мы писали о парадигмах подсчёта очков в общих чертах. Эта статья — конкретная версия: три вида спорта, которые мы действительно обслуживаем, чего требует каждый и где заканчивается общий движок и начинается работа под конкретный спорт.
Настольный теннис: розыгрышный вид спорта с проблемой ротации
Подсчёт выглядит просто. До трёх или четырёх побед, партии до 11, разница в два очка, без потолка. Вся сложность — в подаче.
Подача меняется каждые два очка, кроме счёта 10-10 (равный счёт), когда она меняется после каждого очка — сколько угодно долго. В парах меняется и порядок приёма, и он меняется между партиями. Ошибётесь с ротацией — и судье придётся вручную исправлять порядок подач посреди партии, а это как раз то трение, которое система призвана убрать.
Поэтому модель настольного тенниса отслеживает состояние подачи как полноценную часть счёта, выводя его из последовательности очков, а не получая от пользователя. Раз оно выводится, судья, исправляющий очко трёхрозыгрышной давности, автоматически исправляет и ротацию подач — одна из тех деталей, которых не замечаешь, пока всё работает, и которые бесят, когда не работает.
Есть и особенность данных, на которую мы наткнулись в продакшене и задокументировали на горьком опыте: в некоторых внедрениях по каждому розыгрышу записывались очки только одной стороны, а очки соперника выводились. Для итогового счёта этого достаточно, а для аналитики по розыгрышам бесполезно: половина событий отсутствует. Запись обеих сторон в каждом розыгрыше была обязательным условием для любого анализа матчей в настольном теннисе.
Производное состояние лучше введённого
Общий принцип: всё, что правила позволяют вычислить из последовательности очков, нужно вычислять, а не вводить. Ротация подач, гейм-пойнт, матчбол, действует ли правило двух очков, кто принимает следующим в парах. Каждое поле, которое судья должен вести вручную, рано или поздно окажется неверным в восемь вечера на третий день.
Борьба: непрерывные баллы и внезапное завершение
Борьба полностью ломает допущение о розыгрыше. Нет отдельного события, выигрывающего очко, которое можно посчитать: баллы начисляются непрерывно в рамках периодов, в разных значениях, за действия разного рода, любому борцу, в любой момент.
И поединок может закончиться мгновенно. Туше завершает его независимо от счёта. Техническое превосходство завершает его в ту секунду, когда достигнут разрыв, посреди периода. Это не крайние случаи, которые обрабатываются постфактум; это условия, которые движок обязан проверять после каждого события, потому что матч уже мог закончиться.
Предупреждения и пассивность добавляют ещё один слой: состояние, которое влияет на поединок, но не является очком, и которое позже может превратиться в баллы. Поэтому интерфейсу подсчёта нужны баллы, учёт предупреждений, кнопка туше и часы периода — и ничего лишнего на экране, потому что у судьи по борьбе практически нет времени искать нужную кнопку.
Баскетбол: сложность в часах
Арифметика баскетбола — самая простая из трёх: два, три или одно очко, приписанное игроку и команде. Сложными её делают часы.
Игровое время — это состояние, которое идёт, останавливается и должно совпадать на устройстве подсчёта, табло зала и экране каждого зрителя. Часы, синхронизируемые опросом, заметно расходятся уже за четверть. Работает другой подход: передавать переходы часов — запущены в эту секунду, остановлены в ту — и позволять каждому клиенту считать текущее значение локально. Тогда все экраны выводят одно и то же время из одних и тех же событий, а клиент, переподключившийся после обрыва, сразу вычисляет верное время, а не продолжает с устаревшего значения.
Что действительно общее
Учитывая, насколько различаются эти три вида спорта, справедливо спросить, что вообще даёт общий движок. Оказывается, немало — и ничто из этого не является логикой подсчёта.
- Поток событий: упорядоченный, только с добавлением, устойчивый к потере связи, воспроизводимый. Одинаковый для всех трёх.
- Повторное проигрывание на сервере как источник истины, чтобы исправления везде работали одинаково.
- Рассылка на лайв-страницы, сетки, оверлеи трансляций и аналитику — единый путь публикации.
- Структура турнира: жеребьёвки, сетки, расписание, результаты и рейтинги, которым не важно, в каком виде спорта получен результат.
- Офлайн-очередь и сверка, написанные один раз и унаследованные каждым видом спорта.
Специфично для вида спорта только вычисление правил и интерфейс подсчёта — то, что и должно быть специфичным. Всё остальное общее, и поэтому тридцать первый вид спорта — это вопрос недель, а не переписывания.