Перейти к содержимому

Модуль live в poker-api. Все операции ведут owner и td. Особенность раздела в том, что почти каждая операция защищена от конкурентного нажатия — пульт стоит в зале, и по нему бьют пальцем, а не курсором.

Требует минимум двух checked_in, иначе 409 NOT_ENOUGH_PLAYERS. В одной операции:

  • оставшиеся registeredno_show плюс страйк;
  • рассадка;
  • chipsInPlay = startingStack × число вошедших;
  • часы с нулевого отрезка.

Переход в runningатомарный guarded-update; повторный старт получает 409 TOURNAMENT_ALREADY_STARTED.

src/live/seating.ts, SEATS_PER_TABLE = 9. Число столов — ⌈игроков / 9⌉, перемешивание Фишера–Йейтса с инъектируемым rng (поэтому рассадка тестируема), раздача по кругу:

tableNo = (i mod столов) + 1
seatNo = ⌊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 здесь не косметика: сила считается экспоненциальным сглаживанием, то есть зависит от порядка турниров. Пересчёт не последнего турнира без этого сдвинул бы его в конец истории и дал бы дрейф силы.

Что получают клиенты: статус, часы (индекс, старт отрезка, пауза, полный список уровней), remaining, entrants, chipsInPlay, avgStack, столы с местами и именами, список вылетов с местами.

Телефоны наружу не отдаются.

Подписка — namespace /live через socket.io, полный LiveState рассылается в комнату турнира после каждого изменения. Handshake требует JWT.