Svara - это карточная игра на ставки для 2-6 игроков, основанная на принципах покера с уникальной механикой "свары" (повторной игры при ничьей). Игра использует специальную колоду из 32 карт (без 2-6) с джокером (7 треф).
waiting- Ожидание игроковante- Фаза входных ставокblind_betting- Фаза ставок вслепуюbetting- Фаза обычных ставокshowdown- Вскрытие картsvara_pending- Ожидание решения по свареsvara- Фаза сварыfinished- Игра завершена
waiting → ante → blind_betting → betting → showdown → svara_pending → svara → finished
↓ ↓ ↓
finished finished finished
Цель: Каждый игрок делает обязательную ставку для участия в игре.
Механика:
- Все игроки делают ставку равную
minBet - Ставка добавляется в общий банк (
pot) - После всех ставок игра переходит в
blind_betting
Технические детали:
- Проверка баланса игрока:
player.balance >= minBet - Обновление:
player.balance -= minBet,gameState.pot += minBet - Установка дилера: случайный выбор или следующий после предыдущего победителя
Цель: Игроки могут делать ставки не глядя на карты или посмотреть карты.
Доступные действия:
look- посмотреть карты (переводит в обычную фазу ставок)blind- ставка вслепую (удваивает предыдущую ставку)
Механика ставок вслепую:
- Первая ставка:
minBet - Каждая следующая ставка:
previousBlindBet * 2 - Игрок может смотреть карты в любой момент
- После
lookигрок переходит в обычную фазу ставок
Технические детали:
- Якорь: последний игрок, делавший blind ставку (
lastBlindBettorIndex) - Переход в
betting: когда игрок делаетlook+call/raise - Проверка баланса:
player.balance >= nextBlindBet
Цель: Игроки делают ставки, зная свои карты.
Доступные действия:
fold- сбросить карты (выход из игры)call- уравнять ставкуraise- повысить ставкуcheck- пропустить ход (если нечего уравнивать)
Механика ставок:
- Минимальный raise:
currentBet + minBet - Максимальный raise: весь баланс игрока
- Якорь: последний игрок, делавший raise (
lastRaiseIndex)
Завершение круга ставок:
- Ход возвращается к якорю
- Все активные игроки уравняли ставки
- Менее 2 активных игроков
Технические детали:
- Расчет суммы для call:
getAmountToCall(gameState, playerId) - Расчет минимального raise:
getMinRaise(gameState) - Расчет максимального raise:
getMaxRaise(gameState, playerId) - Проверка завершения:
isBettingRoundComplete(gameState)
Цель: Определить победителя на основе очков карт.
Механика подсчета очков:
- Каждая карта имеет числовое значение
- Джокер (7 треф) = 0 очков
- Сумма очков всех карт в руке
- Игрок с наибольшим количеством очков выигрывает
Возможные исходы:
- Один победитель → переход в
finished - Несколько победителей с одинаковыми очками → переход в
svara_pending
Цель: Игроки решают, участвовать ли в сваре.
Механика свары:
- Участники свары: игроки с одинаковыми очками
- Время на решение:
TURN_DURATION_SECONDS(30 секунд) - Другие игроки могут присоединиться, заплатив взнос
Взнос за вход в свару:
- Размер взноса = изначальный банк свары (
svaraOriginalPot) - Все игроки платят одинаковую сумму
- Взнос добавляется к банку свары
Технические детали:
- Таймер свары:
svaraTimers.set(roomId, timer) - Автоматическое разрешение:
resolveSvara(roomId) - Проверка всех решений:
_checkSvaraCompletion(roomId, gameState)
Цель: Повторная игра с теми же участниками и увеличенным банком.
Механика свары:
- Новый раунд игры с участниками свары
- Банк переносится из предыдущей игры
- Обычные правила игры (ante → blind_betting → betting → showdown)
- При повторной ничьей - новая свара
Обработка игроков с нулевым балансом:
- 2 игрока в сваре, у одного 0$ → сразу showdown
- 2+ игроков в сваре, у некоторых 0$ → автоматический fold игроков без денег
GameState:
interface GameState {
status: GamePhase;
players: Player[];
pot: number;
currentPlayerIndex: number;
dealerIndex: number;
lastRaiseIndex?: number;
lastBlindBettorIndex?: number;
svaraOriginalPot?: number;
// ... другие поля
}Player:
interface Player {
id: string;
balance: number;
cards: Card[];
isActive: boolean;
hasFolded: boolean;
hasLooked: boolean;
totalBet: number;
// ... другие поля
}Определение якоря:
- В
blind_betting:lastBlindBettorIndex(последний blind bettor) - В
betting:lastRaiseIndex(последний raise) - Fallback:
dealerIndex(дилер)
Установка якоря:
look → callустанавливает якорь (только если якорь не установлен)raiseвсегда устанавливает новый якорьcallпосле установленного якоря не изменяет якорь
Таймер хода:
- Длительность:
TURN_DURATION_SECONDS(30 секунд) - Автоматический fold при истечении времени
- Очистка при смене фазы игры
Таймер свары:
- Длительность:
TURN_DURATION_SECONDS(30 секунд) - Автоматическое разрешение свары
- Очистка при завершении свары
Игроки с нулевым балансом:
- Блокировка действий (кроме fold и raise)
- Автоматический fold по таймеру
- Завершение игры при отсутствии игроков с деньгами
Защита от бесконечных циклов:
- Флаг
endGameInProgressдляendGameWithWinner - Проверка фазы в
processAction - Валидация переходов между фазами
findNextActivePlayer(players: Player[], currentIndex: number): number {
const activePlayers = players.filter(p => p.isActive && !p.hasFolded);
if (activePlayers.length <= 1) return -1;
// Поиск следующего активного игрока по кругу
for (let i = 1; i <= players.length; i++) {
const nextIndex = (currentIndex + i) % players.length;
const player = players[nextIndex];
if (player.isActive && !player.hasFolded) {
return nextIndex;
}
}
return -1;
}isBettingRoundComplete(gameState: GameState): boolean {
const activePlayers = gameState.players.filter(p => p.isActive && !p.hasFolded);
const playersWhoCanAct = activePlayers.filter(p => p.balance > 0);
if (playersWhoCanAct.length < 2) return true;
const anchorIndex = getAnchorPlayerIndex(gameState);
if (gameState.currentPlayerIndex === anchorIndex) {
const playersWithMoney = activePlayers.filter(p => p.balance > 0);
if (playersWithMoney.length === 0) return true;
const firstBet = playersWithMoney[0].totalBet;
return playersWithMoney.every(p => p.totalBet === firstBet);
}
return false;
}determineWinners(players: Player[]): Player[] {
const activePlayers = players.filter(p => p.isActive && !p.hasFolded);
if (activePlayers.length === 0) return [];
const maxScore = Math.max(...activePlayers.map(p => p.score || 0));
const winners = activePlayers.filter(p => (p.score || 0) === maxScore);
return winners;
}- 32 карты: A, K, Q, J, 10, 9, 8, 7 (все масти)
- Джокер: 7 треф = 0 очков
- Значения: A=11, K=10, Q=10, J=10, 10=10, 9=9, 8=8, 7=7, 7♣=0
chip_fly- полет фишек при ставкахwin_animation- анимация победы- Блокировка действий во время анимаций
- Все действия записываются в
gameState.log - Типы действий:
join,leave,ante,blind_bet,look,bet,call,raise,fold,win,svara - Временные метки для всех действий
- Валидация всех действий игроков
- Проверка баланса перед ставками
- Защита от повторных вызовов критических функций
- Валидация переходов между фазами
Игра Svara представляет собой сложную систему с множественными состояниями, требующую точной координации между различными компонентами. Ключевые аспекты включают правильное управление якорями, обработку свары, таймеры и защиту от ошибок. Все механизмы спроектированы для обеспечения честной и увлекательной игры.