keyBy('id'); $quizzes = Quiz::with('subject')->where('active',true) ->withCount('questions')->get()->groupBy('subject_id'); $bestScores = QuizAttempt::where('user_id',auth()->id()) ->where('status','completed') ->selectRaw('quiz_id, MAX(score) as best') ->groupBy('quiz_id')->pluck('best','quiz_id'); return view('child.quiz.index', compact('subjects','quizzes','bestScores')); } public function start(Quiz $quiz) { abort_unless($quiz->active, 403); $attempt = QuizAttempt::firstOrCreate( ['user_id'=>auth()->id(),'quiz_id'=>$quiz->id,'status'=>'in_progress'], ['score'=>0,'current_question'=>0,'started_at'=>now()] ); return redirect()->route('quiz.question', $attempt); } public function question(QuizAttempt $attempt) { abort_unless($attempt->user_id === auth()->id(), 403); if ($attempt->status === 'completed') return redirect()->route('quiz.result', $attempt); $question = $attempt->quiz->questions()->skip($attempt->current_question)->first(); $pointsIfCorrect = QuizAttempt::POINTS[$attempt->current_question]; return view('child.quiz.question', compact('attempt','question','pointsIfCorrect')); } public function answer(Request $r, QuizAttempt $attempt) { abort_unless($attempt->user_id === auth()->id(), 403); abort_if($attempt->status === 'completed', 400); $question = $attempt->quiz->questions()->skip($attempt->current_question)->first(); $possible = QuizAttempt::POINTS[$attempt->current_question]; $timedOut = $r->input('timeout') === '1'; $answerGiven = $r->input('answer',''); $isCorrect = false; $pointsEarned = 0; if (!$timedOut && $answerGiven !== '') { $isCorrect = $this->check($question, $answerGiven); $pointsEarned = $isCorrect ? $possible : 0; } QuizAttemptAnswer::create([ 'quiz_attempt_id' => $attempt->id, 'quiz_question_id' => $question->id, 'answer_given' => $timedOut ? '__timeout__' : $answerGiven, 'is_correct' => $isCorrect, 'points_earned' => $pointsEarned, ]); session()->flash('last_answer', [ 'correct' => $isCorrect, 'points' => $pointsEarned, 'possible' => $possible, 'timeout' => $timedOut, ]); $next = $attempt->current_question + 1; $totalQuestions = $attempt->quiz->questions()->count(); if ($next >= $totalQuestions || $next >= 10) { $total = QuizAttemptAnswer::where('quiz_attempt_id',$attempt->id)->sum('points_earned'); $attempt->update(['score'=>$total,'current_question'=>$next,'status'=>'completed','completed_at'=>now()]); auth()->user()->increment('points', $total); return redirect()->route('quiz.result', $attempt); } $attempt->update(['current_question'=>$next]); return redirect()->route('quiz.question', $attempt); } public function result(QuizAttempt $attempt) { abort_unless($attempt->user_id === auth()->id(), 403); abort_unless($attempt->status === 'completed', 400); $answers = $attempt->answers()->with('question.answerOptions')->get(); return view('child.quiz.result', compact('attempt','answers')); } private function check(QuizQuestion $q, string $answer): bool { return match($q->type) { 'multiple_choice','exclusion' => ($opt = $q->answerOptions()->where('is_correct',true)->first()) && (string)$opt->id === $answer, 'true_false','free_text' => strtolower(trim($answer)) === strtolower(trim((string)$q->correct_answer)), default => false, }; } }