Fix client answer validation bug and improve scoreboard UX
Bug Fix: - Fix stale closure bug in client answer validation where correct answers were sometimes marked incorrect. The issue occurred when players answered quickly after a question started - React's async state updates meant currentCorrectShape could hold the previous question's value. Added currentCorrectShapeRef to ensure the latest value is always used. Scoreboard Improvements: - Unified desktop/mobile layout: avatar, name, and points on first line; progress bar on second line; bonus pills on third line - Removed 5-player limit to show all players - Added vertical scrolling when player list exceeds viewport - Fixed layout to prevent content overflow issues
This commit is contained in:
parent
3d6081823c
commit
62281e1124
2 changed files with 66 additions and 31 deletions
|
|
@ -74,6 +74,7 @@ export const useGame = () => {
|
|||
const hostSecretRef = useRef<string | null>(null);
|
||||
const gameStateRef = useRef<GameState>("LANDING");
|
||||
const firstCorrectPlayerIdRef = useRef<string | null>(null);
|
||||
const currentCorrectShapeRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => { timeLeftRef.current = timeLeft; }, [timeLeft]);
|
||||
useEffect(() => { playersRef.current = players; }, [players]);
|
||||
|
|
@ -84,6 +85,7 @@ export const useGame = () => {
|
|||
useEffect(() => { hostSecretRef.current = hostSecret; }, [hostSecret]);
|
||||
useEffect(() => { gameStateRef.current = gameState; }, [gameState]);
|
||||
useEffect(() => { firstCorrectPlayerIdRef.current = firstCorrectPlayerId; }, [firstCorrectPlayerId]);
|
||||
useEffect(() => { currentCorrectShapeRef.current = currentCorrectShape; }, [currentCorrectShape]);
|
||||
|
||||
const generateGamePin = () => Math.floor(Math.random() * 900000) + 100000 + "";
|
||||
|
||||
|
|
@ -748,6 +750,48 @@ export const useGame = () => {
|
|||
|
||||
const endQuestion = () => {
|
||||
if (timerRef.current) clearInterval(timerRef.current);
|
||||
|
||||
const unansweredPlayers = playersRef.current.filter(p => p.lastAnswerCorrect === null);
|
||||
if (unansweredPlayers.length > 0) {
|
||||
const updatedPlayers = playersRef.current.map(p => {
|
||||
if (p.lastAnswerCorrect !== null) return p;
|
||||
|
||||
const playerRank = getPlayerRank(p.id, playersRef.current);
|
||||
const breakdown = calculatePointsWithBreakdown({
|
||||
isCorrect: false,
|
||||
timeLeftMs: 0,
|
||||
questionTimeMs: QUESTION_TIME_MS,
|
||||
streak: 0,
|
||||
playerRank,
|
||||
isFirstCorrect: false,
|
||||
config: gameConfigRef.current,
|
||||
});
|
||||
const newScore = Math.max(0, p.score + breakdown.total);
|
||||
|
||||
if (p.id === 'host') {
|
||||
setLastPointsEarned(breakdown.total);
|
||||
setLastAnswerCorrect(false);
|
||||
setCurrentPlayerScore(newScore);
|
||||
setCurrentStreak(0);
|
||||
} else {
|
||||
const conn = connectionsRef.current.get(p.id);
|
||||
if (conn?.open) {
|
||||
conn.send({ type: 'RESULT', payload: { isCorrect: false, scoreAdded: breakdown.total, newScore, breakdown } });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...p,
|
||||
score: newScore,
|
||||
previousScore: p.score,
|
||||
streak: 0,
|
||||
lastAnswerCorrect: false,
|
||||
pointsBreakdown: breakdown,
|
||||
};
|
||||
});
|
||||
setPlayers(updatedPlayers);
|
||||
}
|
||||
|
||||
setGameState('REVEAL');
|
||||
broadcast({ type: 'TIME_UP', payload: {} });
|
||||
};
|
||||
|
|
@ -1116,8 +1160,9 @@ export const useGame = () => {
|
|||
} else {
|
||||
const option = arg as AnswerOption;
|
||||
setSelectedOption(option);
|
||||
const isCorrect = option.shape === currentCorrectShape;
|
||||
console.log('[CLIENT] Answering:', { selectedShape: option.shape, currentCorrectShape, isCorrect });
|
||||
// Use ref to avoid stale closure - currentCorrectShape state may not be updated yet
|
||||
const isCorrect = option.shape === currentCorrectShapeRef.current;
|
||||
console.log('[CLIENT] Answering:', { selectedShape: option.shape, currentCorrectShape: currentCorrectShapeRef.current, isCorrect });
|
||||
hostConnectionRef.current?.send({ type: 'ANSWER', payload: { playerId: peerRef.current?.id, isCorrect, selectedShape: option.shape } });
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue