157 lines
No EOL
4.6 KiB
TypeScript
157 lines
No EOL
4.6 KiB
TypeScript
import React from 'react';
|
|
import { useGame } from './hooks/useGame';
|
|
import { Landing } from './components/Landing';
|
|
import { Lobby } from './components/Lobby';
|
|
import { GameScreen } from './components/GameScreen';
|
|
import { Scoreboard } from './components/Scoreboard';
|
|
import { Podium } from './components/Podium';
|
|
import { QuizCreator } from './components/QuizCreator';
|
|
import { RevealScreen } from './components/RevealScreen';
|
|
|
|
const seededRandom = (seed: number) => {
|
|
const x = Math.sin(seed * 9999) * 10000;
|
|
return x - Math.floor(x);
|
|
};
|
|
|
|
const FLOATING_SHAPES = [...Array(15)].map((_, i) => ({
|
|
left: `${seededRandom(i * 1) * 100}%`,
|
|
width: `${seededRandom(i * 2) * 100 + 40}px`,
|
|
height: `${seededRandom(i * 3) * 100 + 40}px`,
|
|
animationDuration: `${seededRandom(i * 4) * 20 + 15}s`,
|
|
animationDelay: `-${seededRandom(i * 5) * 20}s`,
|
|
borderRadius: seededRandom(i * 6) > 0.5 ? '50%' : '20%',
|
|
background: 'rgba(255, 255, 255, 0.05)',
|
|
}));
|
|
|
|
const FloatingShapes = React.memo(() => {
|
|
return (
|
|
<>
|
|
{FLOATING_SHAPES.map((style, i) => (
|
|
<div key={i} className="floating-shape" style={style} />
|
|
))}
|
|
</>
|
|
);
|
|
});
|
|
|
|
function App() {
|
|
const {
|
|
role,
|
|
gameState,
|
|
quiz,
|
|
players,
|
|
currentQuestionIndex,
|
|
timeLeft,
|
|
error,
|
|
gamePin,
|
|
startQuizGen,
|
|
startManualCreation,
|
|
finalizeManualQuiz,
|
|
joinGame,
|
|
startGame,
|
|
handleAnswer,
|
|
hasAnswered,
|
|
lastPointsEarned,
|
|
nextQuestion,
|
|
currentCorrectShape,
|
|
selectedOption,
|
|
currentPlayerScore,
|
|
currentStreak,
|
|
currentPlayerId
|
|
} = useGame();
|
|
|
|
const currentQ = quiz?.questions[currentQuestionIndex];
|
|
|
|
// Logic to find correct option, handling both Host (has isCorrect flag) and Client (masked, needs shape)
|
|
const correctOpt = currentQ?.options.find(o => {
|
|
if (role === 'HOST') return o.isCorrect;
|
|
return o.shape === currentCorrectShape;
|
|
});
|
|
|
|
return (
|
|
<div className="min-h-screen text-white relative">
|
|
<FloatingShapes />
|
|
|
|
<div className="relative z-10">
|
|
{gameState === 'LANDING' || gameState === 'GENERATING' ? (
|
|
<Landing
|
|
onGenerate={startQuizGen}
|
|
onCreateManual={startManualCreation}
|
|
onJoin={joinGame}
|
|
isLoading={gameState === 'GENERATING'}
|
|
error={error}
|
|
/>
|
|
) : null}
|
|
|
|
{gameState === 'CREATING' ? (
|
|
<QuizCreator
|
|
onFinalize={finalizeManualQuiz}
|
|
onCancel={() => window.location.reload()}
|
|
/>
|
|
) : null}
|
|
|
|
{gameState === 'LOBBY' ? (
|
|
<Lobby
|
|
quizTitle={quiz?.title || 'Kaboot'}
|
|
players={players}
|
|
gamePin={gamePin}
|
|
role={role}
|
|
onStart={startGame}
|
|
/>
|
|
) : null}
|
|
|
|
{(gameState === 'COUNTDOWN' || gameState === 'QUESTION') && quiz ? (
|
|
gameState === 'COUNTDOWN' ? (
|
|
<div className="flex flex-col items-center justify-center h-screen animate-bounce">
|
|
<div className="text-4xl font-display font-bold mb-4">Get Ready!</div>
|
|
<div className="text-[12rem] font-black leading-none drop-shadow-[0_10px_0_rgba(0,0,0,0.3)]">
|
|
{timeLeft}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<GameScreen
|
|
question={quiz.questions[currentQuestionIndex]}
|
|
timeLeft={timeLeft}
|
|
totalQuestions={quiz.questions.length}
|
|
currentQuestionIndex={currentQuestionIndex}
|
|
gameState={gameState}
|
|
role={role}
|
|
onAnswer={handleAnswer}
|
|
hasAnswered={hasAnswered}
|
|
lastPointsEarned={lastPointsEarned}
|
|
/>
|
|
)
|
|
) : null}
|
|
|
|
{gameState === 'REVEAL' && correctOpt ? (
|
|
<RevealScreen
|
|
isCorrect={lastPointsEarned !== null && lastPointsEarned > 0}
|
|
pointsEarned={lastPointsEarned || 0}
|
|
newScore={currentPlayerScore}
|
|
streak={currentStreak}
|
|
correctOption={correctOpt}
|
|
selectedOption={selectedOption}
|
|
role={role}
|
|
/>
|
|
) : null}
|
|
|
|
{gameState === 'SCOREBOARD' ? (
|
|
<Scoreboard
|
|
players={players}
|
|
onNext={nextQuestion}
|
|
isHost={role === 'HOST'}
|
|
currentPlayerId={currentPlayerId}
|
|
/>
|
|
) : null}
|
|
|
|
{gameState === 'PODIUM' ? (
|
|
<Podium
|
|
players={players}
|
|
onRestart={() => window.location.reload()}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App; |