fix: 计时器改为双方独立象棋钟 + 超时自动判负

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 13:53:47 +08:00
parent 063a181a6e
commit a16041cd67
+64 -8
View File
@@ -1,29 +1,85 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useRef, useCallback } from 'react';
import { useGameStore } from '../../store/gameStore'; import { useGameStore } from '../../store/gameStore';
import { invoke } from '@tauri-apps/api/core';
export default function TimerDisplay() { export default function TimerDisplay() {
const config = useGameStore((s) => s.config); const config = useGameStore((s) => s.config);
const currentColor = useGameStore((s) => s.currentColor); const currentColor = useGameStore((s) => s.currentColor);
const status = useGameStore((s) => s.status); const status = useGameStore((s) => s.status);
const [time, setTime] = useState(config.timeLimitSecs); const refreshBoard = useGameStore((s) => s.refreshBoard);
const [blackTime, setBlackTime] = useState(config.timeLimitSecs);
const [whiteTime, setWhiteTime] = useState(config.timeLimitSecs);
const lastColorRef = useRef(currentColor);
const hasTimedOutRef = useRef(false);
// 初始化/重置时钟
useEffect(() => {
setBlackTime(config.timeLimitSecs);
setWhiteTime(config.timeLimitSecs);
hasTimedOutRef.current = false;
lastColorRef.current = 'Black';
}, [config.timeLimitSecs, status === 'waiting' ? status : null]);
const handleTimeout = useCallback(async () => {
if (hasTimedOutRef.current) return;
hasTimedOutRef.current = true;
try {
await invoke('resign');
await refreshBoard();
} catch {
// 忽略错误
}
}, [refreshBoard]);
useEffect(() => { useEffect(() => {
if (!config.useTimer || status !== 'playing') return; if (!config.useTimer || status !== 'playing') return;
setTime(config.timeLimitSecs);
const timer = setInterval(() => { const timer = setInterval(() => {
setTime((t) => { if (currentColor === 'Black') {
if (t <= 1) { clearInterval(timer); return 0; } setBlackTime((t) => {
if (t <= 1) {
clearInterval(timer);
handleTimeout();
return 0;
}
return t - 1; return t - 1;
}); });
} else {
setWhiteTime((t) => {
if (t <= 1) {
clearInterval(timer);
handleTimeout();
return 0;
}
return t - 1;
});
}
}, 1000); }, 1000);
lastColorRef.current = currentColor;
return () => clearInterval(timer); return () => clearInterval(timer);
}, [currentColor, config.useTimer, config.timeLimitSecs, status]); }, [currentColor, config.useTimer, status, handleTimeout]);
if (!config.useTimer) return null; if (!config.useTimer) return null;
const displayTime = currentColor === 'Black' ? blackTime : whiteTime;
const isWarning = displayTime <= 10;
return ( return (
<div className={`timer-display ${time <= 10 ? 'timer-warning' : ''}`}> <div className="timer-display">
{Math.floor(time / 60)}:{(time % 60).toString().padStart(2, '0')} <div className={isWarning ? 'timer-warning' : ''} style={{ fontSize: 28, fontFamily: 'monospace' }}>
{Math.floor(displayTime / 60)}:{(displayTime % 60).toString().padStart(2, '0')}
</div>
<div style={{ display: 'flex', gap: 20, fontSize: 14, opacity: 0.7 }}>
<span style={{ fontWeight: currentColor === 'Black' ? 'bold' : 'normal' }}>
: {Math.floor(blackTime / 60)}:{String(blackTime % 60).padStart(2, '0')}
</span>
<span style={{ fontWeight: currentColor === 'White' ? 'bold' : 'normal' }}>
: {Math.floor(whiteTime / 60)}:{String(whiteTime % 60).padStart(2, '0')}
</span>
</div>
</div> </div>
); );
} }