Matiks-UI / src /hooks /useGameEngine.ts
Abhisingh-18's picture
Mirror of github.com/Abhisingh18/Matiks-UI
0bedb61 verified
Raw
History Blame Contribute Delete
6.39 kB
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<typeof useSharedValue>;
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<ReturnType<typeof setTimeout> | 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,
};
};