import { createFileRoute } from "@tanstack/react-router"; import { useEffect, useState, useCallback } from "react"; import { useWsClient, useWsConnected } from "../../lib/ws-hooks.js"; import { QuestionCard } from "../../components/game/QuestionCard.js"; import { MultiplayerScoreScreen } from "../../components/multiplayer/MultiplayerScoreScreen.js"; import { GameRouteSearchSchema } from "@lila/shared"; import type { WsGameQuestion, WsGameAnswerResult, WsGameFinished, WsError, } from "@lila/shared"; export const Route = createFileRoute("/multiplayer/game/$code")({ component: GamePage, validateSearch: GameRouteSearchSchema, }); function GamePage() { const { code } = Route.useParams(); const { lobbyId } = Route.useSearch(); const { session } = Route.useRouteContext(); const currentUserId = session.user.id; const client = useWsClient(); const isConnected = useWsConnected(); const [currentQuestion, setCurrentQuestion] = useState( null, ); const [answerResult, setAnswerResult] = useState( null, ); const [gameFinished, setGameFinished] = useState(null); const [error, setError] = useState(null); const [hasAnswered, setHasAnswered] = useState(false); const handleGameQuestion = useCallback((msg: WsGameQuestion) => { setCurrentQuestion(msg); setAnswerResult(null); setHasAnswered(false); setError(null); }, []); const handleAnswerResult = useCallback((msg: WsGameAnswerResult) => { setAnswerResult(msg); }, []); const handleGameFinished = useCallback((msg: WsGameFinished) => { setGameFinished(msg); }, []); const handleWsError = useCallback((msg: WsError) => { setError(msg.message); }, []); useEffect(() => { if (!isConnected) return; client.on("game:question", handleGameQuestion); client.on("game:answer_result", handleAnswerResult); client.on("game:finished", handleGameFinished); client.on("error", handleWsError); client.send({ type: "game:ready", lobbyId }); return () => { client.off("game:question", handleGameQuestion); client.off("game:answer_result", handleAnswerResult); client.off("game:finished", handleGameFinished); client.off("error", handleWsError); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [isConnected]); const handleAnswer = useCallback( (optionId: number) => { if (hasAnswered || !currentQuestion) return; setHasAnswered(true); client.send({ type: "game:answer", lobbyId, questionId: currentQuestion.question.questionId, selectedOptionId: optionId, }); }, [hasAnswered, currentQuestion, client, lobbyId], ); // Phase: finished if (gameFinished) { return ( ); } // Phase: loading if (!isConnected || !currentQuestion) { return (

{error ?? (isConnected ? "Loading game..." : "Connecting...")}

); } // Phase: playing return (
{/* Progress */}

Question {currentQuestion.questionNumber} of{" "} {currentQuestion.totalQuestions}

{/* Question */} r.userId === currentUserId) ?.isCorrect ?? false, correctOptionId: answerResult.correctOptionId, selectedOptionId: answerResult.results.find((r) => r.userId === currentUserId) ?.selectedOptionId ?? 0, } : null } onAnswer={handleAnswer} onNext={() => { setAnswerResult(null); }} /> {/* Error */} {error &&

{error}

} {/* Round results */} {answerResult && (

Round results

{answerResult.players.map((player) => { const result = answerResult.results.find( (r) => r.userId === player.userId, ); return (
{player.user.name} {result?.selectedOptionId === null ? "Timed out" : result?.isCorrect ? "✓ Correct" : "✗ Wrong"} {player.score} pts
); })}
)}
); }