Private
Public Access
1
0
Files
MineSeeker/assets/js/mine-seeker/components/GameTimer.jsx

166 lines
5.3 KiB
React
Raw Normal View History

/**
* 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 (
<div className="game-timer-container">
<div className={`game-timer red-timer ${!activePlayer ? 'active' : ''}`}>
<i className={`fa ${!activePlayer ? 'fa-hourglass-half' : 'fa-hourglass-start'} timer-icon`} />
<span className="timer-display">{formatTime(redTime)}</span>
</div>
<div className={`game-timer blue-timer ${activePlayer ? 'active' : ''}`}>
<i className={`fa ${activePlayer ? 'fa-hourglass-half' : 'fa-hourglass-start'} timer-icon`} />
<span className="timer-display">{formatTime(blueTime)}</span>
</div>
</div>
);
};
export default GameTimer;