Finer granularity points

This commit is contained in:
Joey Yakimowich-Payne 2026-01-13 17:03:35 -07:00
commit a7e37024f5
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
3 changed files with 16 additions and 17 deletions

View file

@ -27,9 +27,9 @@ export const GameScreen: React.FC<GameScreenProps> = ({
}) => { }) => {
const isClient = role === 'CLIENT'; const isClient = role === 'CLIENT';
const displayOptions = question?.options || []; const displayOptions = question?.options || [];
const timeLeftSeconds = Math.ceil(timeLeft / 1000);
// Timer styling logic const isUrgent = timeLeftSeconds <= 5 && timeLeftSeconds > 0;
const isUrgent = timeLeft < 5 && timeLeft > 0;
const timerBorderColor = isUrgent ? 'border-red-500' : 'border-white'; const timerBorderColor = isUrgent ? 'border-red-500' : 'border-white';
const timerTextColor = isUrgent ? 'text-red-500' : 'text-theme-primary'; const timerTextColor = isUrgent ? 'text-red-500' : 'text-theme-primary';
const timerAnimation = isUrgent ? 'animate-ping' : ''; const timerAnimation = isUrgent ? 'animate-ping' : '';
@ -45,9 +45,9 @@ export const GameScreen: React.FC<GameScreenProps> = ({
{/* Whimsical Timer */} {/* Whimsical Timer */}
<div className="relative"> <div className="relative">
<div className="absolute inset-0 bg-white/20 rounded-full blur-xl animate-pulse"></div> <div className="absolute inset-0 bg-white/20 rounded-full blur-xl animate-pulse"></div>
<div className={`bg-white ${timerTextColor} rounded-full w-20 h-20 flex items-center justify-center text-4xl font-black shadow-[0_6px_0_rgba(0,0,0,0.2)] border-4 ${timerBorderColor} ${timerAnimation} relative z-10 transition-colors duration-300`}> <div className={`bg-white ${timerTextColor} rounded-full w-20 h-20 flex items-center justify-center text-4xl font-black shadow-[0_6px_0_rgba(0,0,0,0.2)] border-4 ${timerBorderColor} ${timerAnimation} relative z-10 transition-colors duration-300`}>
{timeLeft} {timeLeftSeconds}
</div> </div>
</div> </div>
<div className="bg-white/20 backdrop-blur-md px-6 py-2 rounded-2xl font-black text-xl shadow-sm border-2 border-white/10"> <div className="bg-white/20 backdrop-blur-md px-6 py-2 rounded-2xl font-black text-xl shadow-sm border-2 border-white/10">

View file

@ -22,6 +22,7 @@ export const BOT_NAMES = [
]; ];
export const QUESTION_TIME = 20; // seconds export const QUESTION_TIME = 20; // seconds
export const QUESTION_TIME_MS = 20000; // milliseconds
export const POINTS_PER_QUESTION = 1000; export const POINTS_PER_QUESTION = 1000;
export const PLAYER_COLORS = [ export const PLAYER_COLORS = [

View file

@ -1,7 +1,7 @@
import { useState, useEffect, useRef, useCallback } from 'react'; import { useState, useEffect, useRef, useCallback } from 'react';
import { Quiz, Player, GameState, GameRole, NetworkMessage, AnswerOption, Question } from '../types'; import { Quiz, Player, GameState, GameRole, NetworkMessage, AnswerOption, Question } from '../types';
import { generateQuiz } from '../services/geminiService'; import { generateQuiz } from '../services/geminiService';
import { POINTS_PER_QUESTION, QUESTION_TIME, PLAYER_COLORS } from '../constants'; import { POINTS_PER_QUESTION, QUESTION_TIME, QUESTION_TIME_MS, PLAYER_COLORS } from '../constants';
import { Peer, DataConnection } from 'peerjs'; import { Peer, DataConnection } from 'peerjs';
export const useGame = () => { export const useGame = () => {
@ -147,7 +147,7 @@ export const useGame = () => {
if (!currentPlayer || currentPlayer.lastAnswerCorrect !== null) return; if (!currentPlayer || currentPlayer.lastAnswerCorrect !== null) return;
const points = isCorrect ? Math.round(POINTS_PER_QUESTION * (timeLeftRef.current / QUESTION_TIME)) : 0; const points = isCorrect ? Math.round(POINTS_PER_QUESTION * (timeLeftRef.current / QUESTION_TIME_MS)) : 0;
const newScore = currentPlayer.score + points; const newScore = currentPlayer.score + points;
setPlayers(prev => prev.map(p => { setPlayers(prev => prev.map(p => {
@ -197,16 +197,14 @@ export const useGame = () => {
setHasAnswered(false); setHasAnswered(false);
setLastPointsEarned(null); setLastPointsEarned(null);
setSelectedOption(null); setSelectedOption(null);
setTimeLeft(QUESTION_TIME); setTimeLeft(QUESTION_TIME_MS);
setPlayers(prev => prev.map(p => ({ ...p, lastAnswerCorrect: null }))); setPlayers(prev => prev.map(p => ({ ...p, lastAnswerCorrect: null })));
// Use refs to get the latest state inside this async callback
const currentQuiz = quizRef.current; const currentQuiz = quizRef.current;
const currentIndex = currentQuestionIndexRef.current; const currentIndex = currentQuestionIndexRef.current;
if (currentQuiz) { if (currentQuiz) {
const currentQ = currentQuiz.questions[currentIndex]; const currentQ = currentQuiz.questions[currentIndex];
// Ensure options exist
const options = currentQ.options || []; const options = currentQ.options || [];
const correctOpt = options.find(o => o.isCorrect); const correctOpt = options.find(o => o.isCorrect);
const correctShape = correctOpt?.shape || 'triangle'; const correctShape = correctOpt?.shape || 'triangle';
@ -214,7 +212,7 @@ export const useGame = () => {
const optionsForClient = options.map(o => ({ const optionsForClient = options.map(o => ({
...o, ...o,
isCorrect: false // Masked isCorrect: false
})); }));
broadcast({ broadcast({
@ -233,10 +231,10 @@ export const useGame = () => {
if (timerRef.current) clearInterval(timerRef.current); if (timerRef.current) clearInterval(timerRef.current);
timerRef.current = setInterval(() => { timerRef.current = setInterval(() => {
setTimeLeft(prev => { setTimeLeft(prev => {
if (prev <= 1) { endQuestion(); return 0; } if (prev <= 100) { endQuestion(); return 0; }
return prev - 1; return prev - 100;
}); });
}, 1000); }, 100);
}; };
const endQuestion = () => { const endQuestion = () => {
@ -303,7 +301,7 @@ export const useGame = () => {
setLastPointsEarned(null); setLastPointsEarned(null);
setSelectedOption(null); setSelectedOption(null);
setCurrentQuestionIndex(data.payload.currentQuestionIndex); setCurrentQuestionIndex(data.payload.currentQuestionIndex);
setTimeLeft(data.payload.timeLimit); setTimeLeft(data.payload.timeLimit * 1000);
setCurrentCorrectShape(data.payload.correctShape); setCurrentCorrectShape(data.payload.correctShape);
setQuiz(prev => { setQuiz(prev => {
@ -321,7 +319,7 @@ export const useGame = () => {
}); });
if (timerRef.current) clearInterval(timerRef.current); if (timerRef.current) clearInterval(timerRef.current);
timerRef.current = setInterval(() => setTimeLeft(prev => Math.max(0, prev - 1)), 1000); timerRef.current = setInterval(() => setTimeLeft(prev => Math.max(0, prev - 100)), 100);
} }
if (data.type === 'RESULT') { if (data.type === 'RESULT') {
@ -356,7 +354,7 @@ export const useGame = () => {
const option = arg as AnswerOption; const option = arg as AnswerOption;
const isCorrect = option.isCorrect; const isCorrect = option.isCorrect;
setSelectedOption(option); setSelectedOption(option);
const points = isCorrect ? Math.round(POINTS_PER_QUESTION * (timeLeftRef.current / QUESTION_TIME)) : 0; const points = isCorrect ? Math.round(POINTS_PER_QUESTION * (timeLeftRef.current / QUESTION_TIME_MS)) : 0;
setLastPointsEarned(points); setLastPointsEarned(points);
const hostPlayer = playersRef.current.find(p => p.id === 'host'); const hostPlayer = playersRef.current.find(p => p.id === 'host');