/** * This file is part of the SplendidBear Websites' projects. * * Copyright (c) 2026 @ www.splendidbear.org * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ import React, { useEffect, useRef, useState } from 'react'; import { useGame } from '@mine-contexts'; const GameTimer = () => { const { overlay, connectionLost, endRef, activePlayer, webPlayer } = useGame(); const [redTime, setRedTime] = useState(0); const [blueTime, setBlueTime] = useState(0); const [isRunning, setIsRunning] = useState(false); const timerIntervalRef = useRef(null); const gameStartedRef = useRef(false); // Use timestamps instead of counters for more reliable background tracking const redStartTimeRef = useRef(null); const blueStartTimeRef = useRef(null); const lastActivePlayerRef = useRef(null); const pausedRedTimeRef = useRef(0); const pausedBlueTimeRef = useRef(0); // Start timer when overlay is hidden (both players connected and game started) useEffect(() => { if (!overlay && !gameStartedRef.current) { gameStartedRef.current = true; setIsRunning(true); setRedTime(0); setBlueTime(0); redStartTimeRef.current = Date.now(); blueStartTimeRef.current = Date.now(); pausedRedTimeRef.current = 0; pausedBlueTimeRef.current = 0; lastActivePlayerRef.current = activePlayer; } }, [overlay]); // Stop timer on game end (resign/win) useEffect(() => { if (endRef.current) { setIsRunning(false); } }, [endRef.current]); // Stop timer on connection loss useEffect(() => { if (connectionLost) { setIsRunning(false); } }, [connectionLost]); // Handle player switch - pause one timer, resume the other useEffect(() => { if (!isRunning) return; if (lastActivePlayerRef.current !== activePlayer) { // Player switched, save current accumulated time for whoever was active const startRef = lastActivePlayerRef.current ? blueStartTimeRef.current : redStartTimeRef.current; if (startRef) { const elapsed = Math.floor((Date.now() - startRef) / 1000); if (lastActivePlayerRef.current) { pausedBlueTimeRef.current += elapsed; } else { pausedRedTimeRef.current += elapsed; } } // Start the new active player's timer if (activePlayer) { blueStartTimeRef.current = Date.now(); } else { redStartTimeRef.current = Date.now(); } lastActivePlayerRef.current = activePlayer; } }, [activePlayer, isRunning]); // Main timer effect - update display every 100ms useEffect(() => { if (!isRunning) { if (timerIntervalRef.current) { clearInterval(timerIntervalRef.current); } return; } timerIntervalRef.current = setInterval(() => { let currentRedTime = pausedRedTimeRef.current; let currentBlueTime = pausedBlueTimeRef.current; // Add elapsed time for the active player if (!activePlayer && redStartTimeRef.current) { currentRedTime += Math.floor((Date.now() - redStartTimeRef.current) / 1000); } else if (activePlayer && blueStartTimeRef.current) { currentBlueTime += Math.floor((Date.now() - blueStartTimeRef.current) / 1000); } setRedTime(currentRedTime); setBlueTime(currentBlueTime); }, 100); return () => { if (timerIntervalRef.current) { clearInterval(timerIntervalRef.current); } }; }, [isRunning, activePlayer]); // Handle focus/blur to synchronize timer when tab regains focus useEffect(() => { const handleFocus = () => { // Force update when tab regains focus to sync any background drift if (isRunning) { let currentRedTime = pausedRedTimeRef.current; let currentBlueTime = pausedBlueTimeRef.current; if (!activePlayer && redStartTimeRef.current) { currentRedTime += Math.floor((Date.now() - redStartTimeRef.current) / 1000); } else if (activePlayer && blueStartTimeRef.current) { currentBlueTime += Math.floor((Date.now() - blueStartTimeRef.current) / 1000); } setRedTime(currentRedTime); setBlueTime(currentBlueTime); } }; window.addEventListener('focus', handleFocus); return () => window.removeEventListener('focus', handleFocus); }, [isRunning, activePlayer]); // Cleanup on unmount useEffect(() => () => { if (timerIntervalRef.current) { clearInterval(timerIntervalRef.current); } }, []); const formatTime = seconds => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; }; return (
{formatTime(redTime)}
{formatTime(blueTime)}
); }; export default GameTimer;