120 lines
6.5 KiB
JavaScript
120 lines
6.5 KiB
JavaScript
// /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; |