// timer.js import * as state from './state.js'; import { GAME_STATES } from '../config.js'; import audioManager from '../ui/audio.js'; let timerInterval = null; let onTimerTickCallback = null; // Callback for UI updates let onPlayerSwitchCallback = null; // Callback for when player switches due to time running out let onGameOverCallback = null; // Callback for when all players run out of time let timeExpiredFlagsById = new Map(); // Track which players have had their timeout sound played export function initTimer(options) { onTimerTickCallback = options.onTimerTick; onPlayerSwitchCallback = options.onPlayerSwitch; onGameOverCallback = options.onGameOver; timeExpiredFlagsById.clear(); // Reset flags on init } export function startTimer() { if (timerInterval) clearInterval(timerInterval); // Clear existing interval if any // Stop any previous sounds (like low time warning) before starting fresh audioManager.stopAllSounds(); // Reset the expired sound flags when starting a new timer timeExpiredFlagsById.clear(); timerInterval = setInterval(() => { const currentPlayerIndex = state.getCurrentPlayerIndex(); const currentPlayer = state.getCurrentPlayer(); // Get player data after index if (!currentPlayer) { console.warn("Timer running but no current player found."); stopTimer(); return; } // Only decrease time if the current player has time left if (currentPlayer.remainingTime > 0) { const newTime = currentPlayer.remainingTime - 1; state.updatePlayerTime(currentPlayerIndex, newTime); // Update state // Play timer sounds - ensure we're not leaking audio resources audioManager.playTimerSound(newTime); // Notify UI to update if (onTimerTickCallback) onTimerTickCallback(); } else { // Current player's time just hit 0 or was already 0 // Ensure time is exactly 0 if it somehow went negative if(currentPlayer.remainingTime < 0) { state.updatePlayerTime(currentPlayerIndex, 0); } // Play time expired sound (only once per player per game) if (!timeExpiredFlagsById.has(currentPlayer.id)) { audioManager.playTimerExpired(); timeExpiredFlagsById.set(currentPlayer.id, true); } // Check if the game should end or switch player if (state.areAllTimersFinished()) { stopTimer(); if (onGameOverCallback) onGameOverCallback(); } else { // Find the *next* player who still has time const nextPlayerIndex = state.findNextPlayerWithTime(); // This finds ANY player with time if (nextPlayerIndex !== -1 && nextPlayerIndex !== currentPlayerIndex) { // Switch player and ensure we stop any sounds from current player audioManager.stopTimerSounds(); // Stop specific timer sounds before switching if (onPlayerSwitchCallback) onPlayerSwitchCallback(nextPlayerIndex); // Immediately update UI after switch if (onTimerTickCallback) onTimerTickCallback(); } else if (nextPlayerIndex === -1) { // This case shouldn't be reached if areAllTimersFinished is checked first, but as a safeguard: console.warn("Timer tick: Current player out of time, but no next player found, yet not all timers finished?"); stopTimer(); // Stop timer if state is inconsistent if (onGameOverCallback) onGameOverCallback(); // Treat as game over } // If nextPlayerIndex is the same as currentPlayerIndex, means others are out of time, let this timer continue } } }, 1000); } export function stopTimer() { clearInterval(timerInterval); timerInterval = null; // Stop all timer-related sounds to prevent them from continuing to play audioManager.stopTimerSounds(); } export function isTimerRunning() { return timerInterval !== null; } // Clean up resources when the application is closing or component unmounts export function cleanup() { stopTimer(); timeExpiredFlagsById.clear(); audioManager.stopAllSounds(); }