Live-цикл и реалтайм
Модуль live в poker-api. Все операции ведут owner и td. Особенность
раздела в том, что почти каждая операция защищена от конкурентного нажатия —
пульт стоит в зале, и по нему бьют пальцем, а не курсором.
Требует минимум двух checked_in, иначе 409 NOT_ENOUGH_PLAYERS. В одной
операции:
- оставшиеся
registered→no_showплюс страйк; - рассадка;
chipsInPlay = startingStack × число вошедших;- часы с нулевого отрезка.
Переход в running — атомарный guarded-update; повторный старт получает
409 TOURNAMENT_ALREADY_STARTED.
Рассадка
Заголовок раздела «Рассадка»src/live/seating.ts, SEATS_PER_TABLE = 9. Число столов — ⌈игроков / 9⌉,
перемешивание Фишера–Йейтса с инъектируемым rng (поэтому рассадка
тестируема), раздача по кругу:
tableNo = (i mod столов) + 1seatNo = ⌊i / столов⌋ + 1Отсюда разница размеров столов не больше одного и места подряд с первого.
pause, resume, level (next/prev). Все три — compare-and-swap:
конкурентные нажатия не задваивают аудит, не шлют лишние broadcast’ы и не теряют
уровень. Смена уровня на паузе начинает новый отрезок «в момент паузы».
Источник истины — серверные поля Tournament: currentLevelIdx,
levelStartedAt, pausedAt. Клиент считает остаток сам, но от этих значений.
POST /eliminations гасит место (TableSeat.active = false) и штампует
eliminatedAt строго возрастающими метками.
Место = число активных игроков до вылета: первый вылетевший из N получает место N.
Рядом три операции:
| Операция | Что делает |
|---|---|
undo |
отменяет последний вылет; если исходное место занято, сажает на первое свободное |
reorder |
перераспределяет уже снятые метки времени между игроками, новых не создаёт |
reseat |
ручная пересадка |
reorder именно перераспределяет, а не переписывает номера: (tournamentId, place)
в Result уникальна, и набор мест обязан остаться прежним.
Балансировка
Заголовок раздела «Балансировка»src/live/balance.ts. Подсказка считается после каждого вылета и приходит в
ответе на вылет, а не отдельным запросом:
- разница между самым большим и самым маленьким живым столом ≥ 2 → одна
пересадка: игрок с максимальным
seatNoбольшого стола на первое свободное место маленького (при равенстве размеров выигрывает стол с меньшим номером); - активных ≤ 9 →
finalTable, пакет пересадок на самый большой стол; - иначе
null.
Под капотом кнопки «Пересадить» — те же вызовы reseat. Сервер сам никого не
двигает.
Подтверждение результатов
Заголовок раздела «Подтверждение результатов»Разрешено только для running и когда активных игроков осталось не больше одного.
N (размер поля) = число чек-инувшихся; неявки в поле не входят. Порядок мест:
не выбывший — место 1, дальше по убыванию времени вылета, тай-брейки по
createdAt и id.
Гейт атомарный: конкурентный второй вызов получает
409 RESULTS_ALREADY_CONFIRMED до начисления, а не после.
Пересчёт
Заголовок раздела «Пересчёт»POST /admin/tournaments/{id}/recount, только для finished. Удаляет и
пересоздаёт Result, сохраняя исходные createdAt, учитывает уже
заклеймленных гостей и пересобирает профили всех задетых игроков по хронологии.
Сохранение createdAt здесь не косметика: сила считается экспоненциальным
сглаживанием, то есть зависит от порядка турниров. Пересчёт не последнего турнира
без этого сдвинул бы его в конец истории и дал бы дрейф силы.
LiveState
Заголовок раздела «LiveState»Что получают клиенты: статус, часы (индекс, старт отрезка, пауза, полный список
уровней), remaining, entrants, chipsInPlay, avgStack, столы с местами и
именами, список вылетов с местами.
Телефоны наружу не отдаются.
Подписка — namespace /live через socket.io, полный LiveState рассылается в
комнату турнира после каждого изменения. Handshake требует JWT.
Что рядом
Заголовок раздела «Что рядом»- Старт, рассадка и баланс — то же словами продукта.
- Рейтинг: пакет формул — что считается при подтверждении.
- Результаты и аудит — что пишется на финише.