// Import the audio manager import audioManager from './audio.js'; // Initialize variables let players = []; let currentPlayerIndex = 0; let gameState = 'setup'; // setup, running, paused, over let carouselPosition = 0; let startX = 0; let currentX = 0; // DOM Elements const carousel = document.getElementById('carousel'); const gameButton = document.getElementById('gameButton'); const setupButton = document.getElementById('setupButton'); const addPlayerButton = document.getElementById('addPlayerButton'); const resetButton = document.getElementById('resetButton'); const playerModal = document.getElementById('playerModal'); const resetModal = document.getElementById('resetModal'); const playerForm = document.getElementById('playerForm'); const cancelButton = document.getElementById('cancelButton'); const deletePlayerButton = document.getElementById('deletePlayerButton'); const resetCancelButton = document.getElementById('resetCancelButton'); const resetConfirmButton = document.getElementById('resetConfirmButton'); const playerImage = document.getElementById('playerImage'); const imagePreview = document.getElementById('imagePreview'); const playerTimeContainer = document.getElementById('playerTimeContainer'); const remainingTimeContainer = document.getElementById('remainingTimeContainer'); const playerRemainingTime = document.getElementById('playerRemainingTime'); // Add sound toggle button const createSoundToggleButton = () => { const headerButtons = document.querySelector('.header-buttons'); const soundButton = document.createElement('button'); soundButton.id = 'soundToggleButton'; soundButton.className = 'header-button'; soundButton.title = 'Toggle Sound'; soundButton.innerHTML = ''; soundButton.addEventListener('click', () => { const isMuted = audioManager.toggleMute(); soundButton.innerHTML = isMuted ? '' : ''; // Play feedback sound if unmuting if (!isMuted) { audioManager.play('buttonClick'); } }); headerButtons.prepend(soundButton); // Set initial icon state based on mute setting if (audioManager.muted) { soundButton.innerHTML = ''; } }; // Create the sound toggle button when page loads createSoundToggleButton(); // Load data from localStorage or use defaults function loadData() { const savedData = localStorage.getItem('chessTimerData'); if (savedData) { const parsedData = JSON.parse(savedData); players = parsedData.players; gameState = parsedData.gameState || 'setup'; currentPlayerIndex = parsedData.currentPlayerIndex || 0; } else { // Default players if no saved data players = [ { id: 1, name: 'Player 1', timeInSeconds: 300, remainingTime: 300, image: null }, { id: 2, name: 'Player 2', timeInSeconds: 300, remainingTime: 300, image: null } ]; saveData(); } renderPlayers(); updateGameButton(); } // Save data to localStorage function saveData() { const dataToSave = { players, gameState, currentPlayerIndex }; localStorage.setItem('chessTimerData', JSON.stringify(dataToSave)); } // Render players to carousel function renderPlayers() { carousel.innerHTML = ''; players.forEach((player, index) => { const card = document.createElement('div'); card.className = `player-card ${index === currentPlayerIndex ? 'active-player' : 'inactive-player'}`; const minutes = Math.floor(player.remainingTime / 60); const seconds = player.remainingTime % 60; const timeString = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; // Create timer element with appropriate classes const timerClasses = []; // Add timer-active class if this is current player and game is running if (index === currentPlayerIndex && gameState === 'running') { timerClasses.push('timer-active'); } // Add timer-finished class if player has no time left if (player.remainingTime <= 0) { timerClasses.push('timer-finished'); } card.innerHTML = `
${player.image ? `${player.name}` : ''}
${player.name}
${timeString}
`; carousel.appendChild(card); }); // Update carousel position carousel.style.transform = `translateX(${-100 * currentPlayerIndex}%)`; } // Update game button text based on game state function updateGameButton() { switch (gameState) { case 'setup': gameButton.textContent = 'Start Game'; break; case 'running': gameButton.textContent = 'Pause Game'; break; case 'paused': gameButton.textContent = 'Resume Game'; break; case 'over': gameButton.textContent = 'Game Over'; break; } } // Handle game button click gameButton.addEventListener('click', () => { // Play button click sound audioManager.play('buttonClick'); if (players.length < 2) { alert('You need at least 2 players to start a game.'); return; } switch (gameState) { case 'setup': gameState = 'running'; audioManager.play('gameStart'); startTimer(); break; case 'running': gameState = 'paused'; audioManager.play('gamePause'); stopTimer(); break; case 'paused': gameState = 'running'; audioManager.play('gameResume'); startTimer(); break; case 'over': // Reset timers and start new game players.forEach(player => { player.remainingTime = player.timeInSeconds; }); gameState = 'setup'; // No specific sound for this state change break; } updateGameButton(); renderPlayers(); // Make sure to re-render after state change saveData(); }); // Timer variables let timerInterval = null; // Check if all timers have reached zero function areAllTimersFinished() { return players.every(player => player.remainingTime <= 0); } // Find a player who still has time left function findNextPlayerWithTime() { const startIndex = (currentPlayerIndex + 1) % players.length; let index = startIndex; do { if (players[index].remainingTime > 0) { return index; } index = (index + 1) % players.length; } while (index !== startIndex); return -1; // No player has time left } // Start the timer for current player function startTimer() { if (timerInterval) clearInterval(timerInterval); // Stop any ongoing sounds when starting timer audioManager.stopAllSounds(); // Immediately render to show the active timer effect renderPlayers(); timerInterval = setInterval(() => { const currentPlayer = players[currentPlayerIndex]; // Only decrease time if the current player has time left if (currentPlayer.remainingTime > 0) { currentPlayer.remainingTime--; // Play appropriate timer sounds based on remaining time audioManager.playTimerSound(currentPlayer.remainingTime); } // Check if current player's time is up if (currentPlayer.remainingTime <= 0) { currentPlayer.remainingTime = 0; // Play time expired sound audioManager.playTimerExpired(); // Check if all timers are at zero if (areAllTimersFinished()) { gameState = 'over'; audioManager.play('gameOver'); updateGameButton(); stopTimer(); } else { // Find the next player who still has time const nextPlayerIndex = findNextPlayerWithTime(); if (nextPlayerIndex !== -1) { currentPlayerIndex = nextPlayerIndex; // Play switch player sound audioManager.play('playerSwitch'); } } } renderPlayers(); saveData(); }, 1000); } // Stop the timer function stopTimer() { clearInterval(timerInterval); timerInterval = null; renderPlayers(); // Make sure to re-render after stopping timer } // Carousel touch events let isDragging = false; carousel.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; currentX = startX; isDragging = true; }); carousel.addEventListener('touchmove', (e) => { if (!isDragging) return; currentX = e.touches[0].clientX; const diff = currentX - startX; const currentTranslate = -100 * currentPlayerIndex + (diff / carousel.offsetWidth * 100); carousel.style.transform = `translateX(${currentTranslate}%)`; }); carousel.addEventListener('touchend', (e) => { if (!isDragging) return; isDragging = false; const diff = currentX - startX; // If dragged more than 10% of width, change player if (Math.abs(diff) > carousel.offsetWidth * 0.1) { const previousIndex = currentPlayerIndex; // Only change players that have remaining time during a running game if (gameState === 'running') { let newIndex; if (diff < 0) { // Try to go to next player with time newIndex = findNextPlayerWithTimeCircular(currentPlayerIndex, 1); } else { // Try to go to previous player with time newIndex = findNextPlayerWithTimeCircular(currentPlayerIndex, -1); } if (newIndex !== -1) { currentPlayerIndex = newIndex; } } else { // Normal navigation when game not running if (diff < 0) { currentPlayerIndex = (currentPlayerIndex + 1) % players.length; } else if (diff > 0) { currentPlayerIndex = (currentPlayerIndex - 1 + players.length) % players.length; } } // Play player switch sound if player actually changed if (previousIndex !== currentPlayerIndex) { audioManager.play('playerSwitch'); } } // Reset carousel to proper position carousel.style.transform = `translateX(${-100 * currentPlayerIndex}%)`; renderPlayers(); saveData(); }); // Find next player with time in specified direction function findNextPlayerWithTimeCircular(startIndex, direction) { let index = startIndex; for (let i = 0; i < players.length; i++) { index = (index + direction + players.length) % players.length; if (players[index].remainingTime > 0) { return index; } } return -1; // No player has time left } // Setup button setupButton.addEventListener('click', () => { audioManager.play('buttonClick'); if (gameState === 'running') { alert('Please pause the game before editing players.'); return; } const currentPlayer = players[currentPlayerIndex]; document.getElementById('modalTitle').textContent = 'Edit Player'; document.getElementById('playerName').value = currentPlayer.name; document.getElementById('playerTime').value = currentPlayer.timeInSeconds / 60; // Show or hide remaining time edit field based on game state if (gameState === 'paused' || gameState === 'over') { remainingTimeContainer.style.display = 'block'; const minutes = Math.floor(currentPlayer.remainingTime / 60); const seconds = currentPlayer.remainingTime % 60; playerRemainingTime.value = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; } else { remainingTimeContainer.style.display = 'none'; } if (currentPlayer.image) { imagePreview.innerHTML = `${currentPlayer.name}`; } else { imagePreview.innerHTML = ''; } playerModal.classList.add('active'); deletePlayerButton.style.display = 'block'; // Play modal open sound audioManager.play('modalOpen'); }); // Add player button addPlayerButton.addEventListener('click', () => { audioManager.play('buttonClick'); if (gameState === 'running') { alert('Please pause the game before adding players.'); return; } document.getElementById('modalTitle').textContent = 'Add New Player'; document.getElementById('playerName').value = `Player ${players.length + 1}`; document.getElementById('playerTime').value = 5; remainingTimeContainer.style.display = 'none'; imagePreview.innerHTML = ''; playerModal.classList.add('active'); deletePlayerButton.style.display = 'none'; // Play modal open sound audioManager.play('modalOpen'); }); // Reset button resetButton.addEventListener('click', () => { audioManager.play('buttonClick'); if (gameState === 'running') { alert('Please pause the game before resetting.'); return; } resetModal.classList.add('active'); audioManager.play('modalOpen'); }); // Cancel reset resetCancelButton.addEventListener('click', () => { audioManager.play('buttonClick'); resetModal.classList.remove('active'); audioManager.play('modalClose'); }); // Confirm reset resetConfirmButton.addEventListener('click', () => { audioManager.play('buttonClick'); players = [ { id: 1, name: 'Player 1', timeInSeconds: 300, remainingTime: 300, image: null }, { id: 2, name: 'Player 2', timeInSeconds: 300, remainingTime: 300, image: null } ]; gameState = 'setup'; currentPlayerIndex = 0; renderPlayers(); updateGameButton(); saveData(); resetModal.classList.remove('active'); // Play reset sound (use game over as it's a complete reset) audioManager.play('gameOver'); }); // Player image upload preview playerImage.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (event) => { imagePreview.innerHTML = `Player`; }; reader.readAsDataURL(file); } }); // Parse time string (MM:SS) to seconds function parseTimeString(timeString) { const [minutes, seconds] = timeString.split(':').map(part => parseInt(part, 10)); return (minutes * 60) + seconds; } // Player form submit playerForm.addEventListener('submit', (e) => { e.preventDefault(); const name = document.getElementById('playerName').value; const timeInMinutes = parseInt(document.getElementById('playerTime').value); const timeInSeconds = timeInMinutes * 60; // Get remaining time if it's visible let remainingTimeValue = timeInSeconds; if (remainingTimeContainer.style.display === 'block') { const remainingTimeString = playerRemainingTime.value; // Validate the time format if (!/^\d{2}:\d{2}$/.test(remainingTimeString)) { alert('Please enter time in MM:SS format (e.g., 05:30)'); return; } remainingTimeValue = parseTimeString(remainingTimeString); } // Get image if uploaded const imageFile = document.getElementById('playerImage').files[0]; let playerImageData = null; // Function to proceed with saving player const savePlayer = () => { const isNewPlayer = document.getElementById('modalTitle').textContent === 'Add New Player'; if (isNewPlayer) { // Add new player const newId = Date.now(); players.push({ id: newId, name: name, timeInSeconds: timeInSeconds, remainingTime: timeInSeconds, image: playerImageData }); currentPlayerIndex = players.length - 1; // Play player added sound audioManager.play('playerAdded'); } else { // Update existing player const player = players[currentPlayerIndex]; player.name = name; player.timeInSeconds = timeInSeconds; // Update remaining time based on game state and form input if (gameState === 'setup') { player.remainingTime = timeInSeconds; } else if (gameState === 'paused' || gameState === 'over') { player.remainingTime = remainingTimeValue; } if (playerImageData !== null) { player.image = playerImageData; } // Play player edited sound audioManager.play('playerEdited'); } renderPlayers(); saveData(); playerModal.classList.remove('active'); document.getElementById('playerImage').value = ''; // Also play modal close sound audioManager.play('modalClose'); }; // Process image if there is one if (imageFile) { const reader = new FileReader(); reader.onload = (event) => { playerImageData = event.target.result; savePlayer(); }; reader.readAsDataURL(imageFile); } else { // Current player's existing image or null if (document.getElementById('modalTitle').textContent !== 'Add New Player') { playerImageData = players[currentPlayerIndex].image; } savePlayer(); } }); // Cancel button cancelButton.addEventListener('click', () => { audioManager.play('buttonClick'); playerModal.classList.remove('active'); document.getElementById('playerImage').value = ''; audioManager.play('modalClose'); }); // Delete player button deletePlayerButton.addEventListener('click', () => { audioManager.play('buttonClick'); if (players.length <= 2) { alert('You need at least 2 players. Add another player before deleting this one.'); return; } players.splice(currentPlayerIndex, 1); if (currentPlayerIndex >= players.length) { currentPlayerIndex = players.length - 1; } renderPlayers(); saveData(); playerModal.classList.remove('active'); // Play player deleted sound audioManager.play('playerDeleted'); // Also play modal close sound audioManager.play('modalClose'); }); // Service Worker Registration if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') .then(registration => { console.log('ServiceWorker registered: ', registration); }) .catch(error => { console.log('ServiceWorker registration failed: ', error); }); }); } // Initialize the app loadData();