import { useEffect, useRef, useCallback, useState } from 'react'; import { useSharedValue, withTiming, Easing, runOnJS } from 'react-native-reanimated'; import { MathQuestion } from '../types'; import { generateQuestions } from '../constants/gameData'; interface GameEngineConfig { totalQuestions?: number; timePerQuestion?: number; // seconds basePoints?: number; comboMultiplier?: number; } interface GameEngineReturn { questions: MathQuestion[]; currentQuestionIndex: number; score: number; combo: number; maxCombo: number; correctAnswers: number; isGameOver: boolean; timerProgress: ReturnType; answerFeedback: (boolean | null)[]; handleAnswer: (selectedAnswer: number) => void; totalQuestions: number; avgTime: number; } export const useGameEngine = ( config: GameEngineConfig = {}, onGameOver?: (results: { score: number; combo: number; correctAnswers: number; totalQuestions: number; accuracy: number; avgTime: number; }) => void ): GameEngineReturn => { const { totalQuestions = 10, timePerQuestion = 15, basePoints = 100, comboMultiplier = 0.5, } = config; const [questions] = useState(() => generateQuestions(totalQuestions)); const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); const [score, setScore] = useState(0); const [combo, setCombo] = useState(0); const [maxCombo, setMaxCombo] = useState(0); const [correctAnswers, setCorrectAnswers] = useState(0); const [isGameOver, setIsGameOver] = useState(false); const [answerFeedback, setAnswerFeedback] = useState<(boolean | null)[]>( Array(4).fill(null) ); const [answerDisabled, setAnswerDisabled] = useState(false); const timerProgress = useSharedValue(1); const questionStartTime = useRef(Date.now()); const totalTimeSpent = useRef(0); const timerRef = useRef | null>(null); // Start timer for current question const startTimer = useCallback(() => { timerProgress.value = 1; timerProgress.value = withTiming(0, { duration: timePerQuestion * 1000, easing: Easing.linear, }); questionStartTime.current = Date.now(); // Auto timeout if (timerRef.current) clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { handleTimeout(); }, timePerQuestion * 1000); }, [timePerQuestion]); useEffect(() => { if (!isGameOver) { startTimer(); } return () => { if (timerRef.current) clearTimeout(timerRef.current); }; }, [currentQuestionIndex, isGameOver]); const handleTimeout = () => { if (isGameOver) return; setCombo(0); moveToNextQuestion(); }; const moveToNextQuestion = () => { if (timerRef.current) clearTimeout(timerRef.current); const timeSpent = (Date.now() - questionStartTime.current) / 1000; totalTimeSpent.current += timeSpent; if (currentQuestionIndex >= totalQuestions - 1) { // Game over setIsGameOver(true); const accuracy = Math.round((correctAnswers / totalQuestions) * 100); const avgTime = Math.round((totalTimeSpent.current / totalQuestions) * 10) / 10; if (onGameOver) { setTimeout(() => { onGameOver({ score, combo: maxCombo, correctAnswers, totalQuestions, accuracy, avgTime, }); }, 800); } } else { setTimeout(() => { setCurrentQuestionIndex((prev) => prev + 1); setAnswerFeedback(Array(4).fill(null)); setAnswerDisabled(false); }, 600); } }; const handleAnswer = (selectedAnswer: number) => { if (answerDisabled || isGameOver) return; setAnswerDisabled(true); if (timerRef.current) clearTimeout(timerRef.current); const question = questions[currentQuestionIndex]; const isCorrect = selectedAnswer === question.correctAnswer; // Set feedback for each option const feedback = question.options.map((opt) => { if (opt === question.correctAnswer) return true; if (opt === selectedAnswer && !isCorrect) return false; return null; }); setAnswerFeedback(feedback); const timeSpent = (Date.now() - questionStartTime.current) / 1000; totalTimeSpent.current += timeSpent; if (isCorrect) { const newCombo = combo + 1; const comboBonus = Math.floor(basePoints * comboMultiplier * (newCombo - 1)); const timeBonus = Math.floor(Math.max(0, (timePerQuestion - timeSpent) * 10)); const points = basePoints + comboBonus + timeBonus; setScore((prev) => prev + points); setCombo(newCombo); setMaxCombo((prev) => Math.max(prev, newCombo)); setCorrectAnswers((prev) => prev + 1); } else { setCombo(0); } // Move to next question after short delay if (currentQuestionIndex >= totalQuestions - 1) { // Last question - game over setTimeout(() => { setIsGameOver(true); const finalCorrect = isCorrect ? correctAnswers + 1 : correctAnswers; const accuracy = Math.round((finalCorrect / totalQuestions) * 100); const avgTime = Math.round((totalTimeSpent.current / totalQuestions) * 10) / 10; if (onGameOver) { onGameOver({ score: isCorrect ? score + basePoints + Math.floor(basePoints * comboMultiplier * combo) : score, combo: Math.max(maxCombo, isCorrect ? combo + 1 : combo), correctAnswers: finalCorrect, totalQuestions, accuracy, avgTime, }); } }, 800); } else { setTimeout(() => { setCurrentQuestionIndex((prev) => prev + 1); setAnswerFeedback(Array(4).fill(null)); setAnswerDisabled(false); }, 600); } }; return { questions, currentQuestionIndex, score, combo, maxCombo, correctAnswers, isGameOver, timerProgress, answerFeedback, handleAnswer, totalQuestions, avgTime: totalTimeSpent.current > 0 ? Math.round((totalTimeSpent.current / Math.max(currentQuestionIndex, 1)) * 10) / 10 : 0, }; };