// /server/game/instance/TurnTimer.js class TurnTimer { /** * Конструктор таймера хода. * @param {number} turnDurationMs - Длительность хода в миллисекундах. * @param {number} updateIntervalMs - Интервал для отправки обновлений времени клиентам (в мс). * @param {function} onTimeoutCallback - Колбэк, вызываемый при истечении времени хода. * @param {function} onTickCallback - Колбэк, вызываемый на каждом тике обновления (передает remainingTime, isPlayerTurnForTimer). */ constructor(turnDurationMs, updateIntervalMs, onTimeoutCallback, onTickCallback) { this.turnDurationMs = turnDurationMs; this.updateIntervalMs = updateIntervalMs; this.onTimeoutCallback = onTimeoutCallback; this.onTickCallback = onTickCallback; this.timerId = null; // ID для setTimeout (обработка таймаута) this.updateIntervalId = null; // ID для setInterval (обновление клиента) this.startTime = 0; // Время начала текущего отсчета (Date.now()) this.isRunning = false; this.isCurrentPlayerActualTurnForTick = false; // Храним, для чьего хода запущен таймер (для onTickCallback) this.isAiCurrentlyMakingMove = false; // Флаг, что сейчас ход AI (таймер не тикает для игрока) // console.log(`[TurnTimer] Initialized with duration: ${turnDurationMs}ms, update interval: ${updateIntervalMs}ms`); } /** * Запускает или перезапускает таймер хода. * @param {boolean} isPlayerTurn - true, если сейчас ход слота 'player', false - если ход слота 'opponent'. * @param {boolean} isAiTurn - true, если текущий ход делает AI (в этом случае таймер для реального игрока не тикает). */ start(isPlayerTurn, isAiTurn = false) { this.clear(); // Сначала очищаем предыдущие таймеры this.isCurrentPlayerActualTurnForTick = isPlayerTurn; // Сохраняем, чей ход для onTick this.isAiCurrentlyMakingMove = isAiTurn; // Таймер и отсчет времени запускаются только если это НЕ ход AI if (this.isAiCurrentlyMakingMove) { this.isRunning = false; // console.log(`[TurnTimer] Start called, but it's AI's turn. Timer not started for player.`); // Уведомляем один раз, что таймер неактивен (ход AI) if (this.onTickCallback) { this.onTickCallback(null, this.isCurrentPlayerActualTurnForTick); } return; } this.startTime = Date.now(); this.isRunning = true; // console.log(`[TurnTimer] Started for ${isPlayerTurn ? 'Player' : 'Opponent'} at ${new Date(this.startTime).toLocaleTimeString()}. AI turn: ${isAiTurn}`); // Таймер на истечение общего времени хода this.timerId = setTimeout(() => { // console.log(`[TurnTimer] Timeout occurred! Was running: ${this.isRunning}`); if (this.isRunning) { // Дополнительная проверка, что таймер все еще должен был работать this.isRunning = false; // Помечаем, что таймер больше не работает if (this.onTimeoutCallback) { this.onTimeoutCallback(); } this.clear(); // Очищаем и интервал обновления после таймаута } }, this.turnDurationMs); // Интервал для отправки обновлений клиентам this.updateIntervalId = setInterval(() => { if (!this.isRunning) { // Если таймер был остановлен (например, ход сделан или игра окончена) this.clear(); // Убедимся, что интервал тоже очищен return; } const elapsedTime = Date.now() - this.startTime; const remainingTime = Math.max(0, this.turnDurationMs - elapsedTime); if (this.onTickCallback) { // Передаем isCurrentPlayerActualTurnForTick, чтобы клиент знал, для чьего хода это время this.onTickCallback(remainingTime, this.isCurrentPlayerActualTurnForTick); } if (remainingTime <= 0 && this.isRunning) { // Если время вышло по интервалу (на всякий случай, setTimeout должен сработать) // console.log(`[TurnTimer] Remaining time reached 0 in interval. Forcing timeout logic.`); // Не вызываем onTimeoutCallback здесь напрямую, чтобы избежать двойного вызова, // setTimeout должен это обработать. Просто очищаем интервал. this.clear(); // Очищаем интервал, setTimeout сработает для onTimeoutCallback } }, this.updateIntervalMs); // Отправляем начальное значение немедленно if (this.onTickCallback) { this.onTickCallback(this.turnDurationMs, this.isCurrentPlayerActualTurnForTick); } } /** * Очищает (останавливает) все активные таймеры (setTimeout и setInterval). */ clear() { if (this.timerId) { clearTimeout(this.timerId); this.timerId = null; } if (this.updateIntervalId) { clearInterval(this.updateIntervalId); this.updateIntervalId = null; } this.isRunning = false; this.startTime = 0; // console.log(`[TurnTimer] Cleared.`); } /** * Проверяет, активен ли таймер в данный момент. * @returns {boolean} */ isActive() { return this.isRunning; } } module.exports = TurnTimer;