Scoreboard ui stuff

This commit is contained in:
Joey Yakimowich-Payne 2026-01-15 08:21:38 -07:00
commit 279dc7f2c3
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
12 changed files with 1558 additions and 77 deletions

View file

@ -1,4 +1,5 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { Quiz, Player, GameState, GameRole, NetworkMessage, AnswerOption, Question, GenerateQuizOptions, ProcessedDocument, GameConfig, DEFAULT_GAME_CONFIG, PointsBreakdown } from '../types';
import { generateQuiz } from '../services/geminiService';
import { QUESTION_TIME, QUESTION_TIME_MS, PLAYER_COLORS, calculatePointsWithBreakdown, getPlayerRank } from '../constants';
@ -35,6 +36,9 @@ const clearStoredSession = () => {
};
export const useGame = () => {
const navigate = useNavigate();
const location = useLocation();
const [role, setRole] = useState<GameRole>('HOST');
const [gameState, setGameState] = useState<GameState>('LANDING');
const [quiz, setQuiz] = useState<Quiz | null>(null);
@ -87,6 +91,53 @@ export const useGame = () => {
useEffect(() => { firstCorrectPlayerIdRef.current = firstCorrectPlayerId; }, [firstCorrectPlayerId]);
useEffect(() => { currentCorrectShapeRef.current = currentCorrectShape; }, [currentCorrectShape]);
const isInitializingFromUrl = useRef(false);
useEffect(() => {
if (isInitializingFromUrl.current) return;
if (location.pathname === '/callback') return;
const getTargetPath = () => {
switch (gameState) {
case 'LANDING':
if (gamePin && location.pathname.startsWith('/play/')) {
return `/play/${gamePin}`;
}
return '/';
case 'CREATING':
case 'GENERATING':
return '/create';
case 'EDITING':
return '/edit';
case 'LOBBY':
case 'COUNTDOWN':
case 'QUESTION':
case 'REVEAL':
case 'SCOREBOARD':
case 'PODIUM':
case 'HOST_RECONNECTED':
if (gamePin) {
return role === 'HOST' ? `/host/${gamePin}` : `/play/${gamePin}`;
}
return '/';
case 'DISCONNECTED':
case 'WAITING_TO_REJOIN':
if (gamePin) {
return `/play/${gamePin}`;
}
return '/';
default:
return '/';
}
};
const targetPath = getTargetPath();
if (location.pathname !== targetPath) {
const useReplace = ['COUNTDOWN', 'QUESTION', 'REVEAL', 'SCOREBOARD'].includes(gameState);
navigate(targetPath + location.search, { replace: useReplace });
}
}, [gameState, gamePin, role, navigate, location.pathname, location.search]);
const generateGamePin = () => Math.floor(Math.random() * 900000) + 100000 + "";
const generateRandomName = (): string => {
@ -391,14 +442,69 @@ export const useGame = () => {
};
useEffect(() => {
const session = getStoredSession();
if (session) {
if (session.role === 'HOST') {
reconnectAsHost(session);
} else {
reconnectAsClient(session);
const initializeFromUrl = async () => {
const path = location.pathname;
if (path === '/callback') {
return;
}
}
const hostMatch = path.match(/^\/host\/(\d+)$/);
const playMatch = path.match(/^\/play\/(\d+)$/);
if (hostMatch) {
const pin = hostMatch[1];
const session = getStoredSession();
if (session && session.pin === pin && session.role === 'HOST') {
isInitializingFromUrl.current = true;
await reconnectAsHost(session);
isInitializingFromUrl.current = false;
} else {
navigate('/', { replace: true });
}
return;
}
if (playMatch) {
const pin = playMatch[1];
const session = getStoredSession();
if (session && session.pin === pin && session.role === 'CLIENT') {
isInitializingFromUrl.current = true;
await reconnectAsClient(session);
isInitializingFromUrl.current = false;
} else {
setGamePin(pin);
}
return;
}
if (path === '/create') {
isInitializingFromUrl.current = true;
setGameState('CREATING');
setRole('HOST');
isInitializingFromUrl.current = false;
return;
}
if (path === '/edit') {
const session = getStoredSession();
if (!session) {
navigate('/', { replace: true });
}
return;
}
const session = getStoredSession();
if (session) {
if (session.role === 'HOST') {
reconnectAsHost(session);
} else {
reconnectAsClient(session);
}
}
};
initializeFromUrl();
return () => {
if (timerRef.current) clearInterval(timerRef.current);
@ -465,6 +571,10 @@ export const useGame = () => {
setGameState('CREATING');
};
const cancelCreation = () => {
setGameState('LANDING');
};
const finalizeManualQuiz = (manualQuiz: Quiz, saveToLibrary: boolean = false) => {
if (saveToLibrary) {
setPendingQuizToSave({ quiz: manualQuiz, topic: '' });
@ -940,6 +1050,7 @@ export const useGame = () => {
if (peerRef.current) {
peerRef.current.destroy();
}
setGamePin(null);
setGameState('LANDING');
setError(null);
};
@ -1175,7 +1286,11 @@ export const useGame = () => {
if (timerRef.current) clearInterval(timerRef.current);
if (syncTimerRef.current) clearInterval(syncTimerRef.current);
if (peerRef.current) peerRef.current.destroy();
window.location.reload();
setGamePin(null);
setQuiz(null);
setPlayers([]);
setGameState('LANDING');
navigate('/', { replace: true });
};
useEffect(() => {
@ -1187,7 +1302,7 @@ export const useGame = () => {
return {
role, gameState, quiz, players, currentQuestionIndex, timeLeft, error, gamePin, hasAnswered, lastPointsEarned, lastAnswerCorrect, currentCorrectShape, selectedOption, currentPlayerScore, currentStreak, currentPlayerId, gameConfig,
pendingQuizToSave, dismissSavePrompt, sourceQuizId, isReconnecting, currentPlayerName,
startQuizGen, startManualCreation, finalizeManualQuiz, loadSavedQuiz, joinGame, startGame: startHostGame, handleAnswer, nextQuestion, showScoreboard,
startQuizGen, startManualCreation, cancelCreation, finalizeManualQuiz, loadSavedQuiz, joinGame, startGame: startHostGame, handleAnswer, nextQuestion, showScoreboard,
updateQuizFromEditor, startGameFromEditor, backFromEditor, endGame, attemptReconnect, goHomeFromDisconnected, resumeGame
};
};