From 6369f319a86e34207ddfc3516d712771e1d8a5d6 Mon Sep 17 00:00:00 2001 From: Aditya8369 Date: Fri, 19 Jun 2026 15:08:57 +0530 Subject: [PATCH] Adaptive Quiz Difficulty --- quiz/adaptiveQuiz.js | 145 ++++++++++++++++++++++++++++++++++ quiz/motionquiz.html | 3 + quiz/motionquiz.js | 122 +++++++++++++++++++++++------ quiz/nlmquiz.html | 2 + quiz/nlmquiz.js | 165 ++++++++++++++++++++++++++++++++------- quiz/projectilequiz.html | 2 + quiz/projectilequiz.js | 17 ++-- quiz/rayquiz.html | 2 + 8 files changed, 396 insertions(+), 62 deletions(-) create mode 100644 quiz/adaptiveQuiz.js diff --git a/quiz/adaptiveQuiz.js b/quiz/adaptiveQuiz.js new file mode 100644 index 0000000..3d8bed8 --- /dev/null +++ b/quiz/adaptiveQuiz.js @@ -0,0 +1,145 @@ +/** + * adaptiveQuiz.js + * Shared helper for Adaptive Quiz Difficulty (client-side only). + * + * Usage (per quiz page JS): + * import/require is not used here (no bundler). Include this file via: + * + * Then call: + * const adaptive = buildAdaptiveQuiz({ questions }); + */ + +(function () { + 'use strict'; + + function normalizeDifficulty(d) { + const val = String(d || '').toLowerCase(); + if (val === 'easy' || val === 'e') return 0; + if (val === 'medium' || val === 'med' || val === 'm') return 1; + if (val === 'hard' || val === 'h') return 2; + + // Default: medium + return 1; + } + + function clamp(n, min, max) { + return Math.max(min, Math.min(max, n)); + } + + /** + * Build a per-difficulty bucket of question copies. + * @param {Array<{difficulty?: string|number}>} questions + */ + function buildBuckets(questions) { + const buckets = { 0: [], 1: [], 2: [] }; + questions.forEach((q, idx) => { + const di = normalizeDifficulty(q.difficulty); + // keep original index for tracking + buckets[di].push({ ...q, __qid: idx }); + }); + + // Shuffle each bucket for variety. + Object.keys(buckets).forEach(k => { + const arr = buckets[k]; + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [arr[i], arr[j]] = [arr[j], arr[i]]; + } + }); + + return buckets; + } + + function createAdaptiveQuiz({ questions, startingDifficultyIndex = 1 }) { + const buckets = buildBuckets(questions); + const totalSteps = questions.length; + + let difficultyIndex = clamp(startingDifficultyIndex, 0, 2); + let consecutiveCorrect = 0; + let consecutiveIncorrect = 0; + + // Flattened remaining counts for quick fallback. + function countRemainingAt(di) { + return buckets[di].length; + } + + function totalRemaining() { + return buckets[0].length + buckets[1].length + buckets[2].length; + } + + /** + * Pick next question from the current difficulty if possible. + * Otherwise, fallback to nearest difficulty that still has questions. + */ + function takeNext() { + if (totalRemaining() <= 0) return null; + + // Try current difficulty first. + if (countRemainingAt(difficultyIndex) > 0) { + return buckets[difficultyIndex].shift(); + } + + // Fallback: nearest difficulty. + for (let dist = 1; dist <= 2; dist++) { + const lower = difficultyIndex - dist; + const upper = difficultyIndex + dist; + if (lower >= 0 && countRemainingAt(lower) > 0) return buckets[lower].shift(); + if (upper <= 2 && countRemainingAt(upper) > 0) return buckets[upper].shift(); + } + + // Final fallback: any remaining. + for (let di = 0; di <= 2; di++) { + if (countRemainingAt(di) > 0) return buckets[di].shift(); + } + + return null; + } + + /** + * Update difficulty after an answer. + */ + function updateDifficulty({ isCorrect }) { + if (isCorrect) { + consecutiveCorrect += 1; + consecutiveIncorrect = 0; + + // After 2 consecutive correct: go harder. + if (consecutiveCorrect >= 2) { + difficultyIndex = clamp(difficultyIndex + 1, 0, 2); + consecutiveCorrect = 0; + } + } else { + consecutiveIncorrect += 1; + consecutiveCorrect = 0; + + // After 2 consecutive incorrect: go easier. + if (consecutiveIncorrect >= 2) { + difficultyIndex = clamp(difficultyIndex - 1, 0, 2); + consecutiveIncorrect = 0; + } + } + + return difficultyIndex; + } + + return { + takeNext, + updateDifficulty, + getDifficultyIndex: () => difficultyIndex, + getTotalSteps: () => totalSteps, + }; + } + + function getStartingDifficultyFromAccuracy({ accuracy }) { + if (typeof accuracy !== 'number' || !isFinite(accuracy)) return 1; // medium + if (accuracy >= 0.8) return 2; // hard + if (accuracy <= 0.5) return 0; // easy + return 1; // medium + } + + // Export to window. + window.createAdaptiveQuiz = createAdaptiveQuiz; + window.getStartingDifficultyFromAccuracy = getStartingDifficultyFromAccuracy; + +})(); + diff --git a/quiz/motionquiz.html b/quiz/motionquiz.html index d2cca23..ae7b58a 100644 --- a/quiz/motionquiz.html +++ b/quiz/motionquiz.html @@ -63,8 +63,11 @@

🎉 Quiz Completed!

+ + + + + + \ No newline at end of file diff --git a/quiz/projectilequiz.js b/quiz/projectilequiz.js index 24a7ba5..bca88b4 100644 --- a/quiz/projectilequiz.js +++ b/quiz/projectilequiz.js @@ -1,15 +1,18 @@ const questions = [ - { question: "What is the path of a projectile in ideal conditions?", options: ["Straight line", "Circular", "Parabolic", "Elliptical"], answer: "Parabolic" }, - { question: "What is the horizontal acceleration of a projectile in the absence of air resistance?", options: ["9.8 m/s²", "0 m/s²", "Depends on initial velocity", "Constant"], answer: "0 m/s²" }, - { question: "At the highest point of its trajectory, what is the vertical velocity of a projectile?", options: ["Maximum", "Zero", "Equal to initial velocity", "Depends on mass"], answer: "Zero" }, - { question: "Which factor affects the range of a projectile the most?", options: ["Mass", "Launch angle", "Time of flight", "Shape"], answer: "Launch angle" }, - { question: "What is the optimal angle for maximum range in projectile motion (neglecting air resistance)?", options: ["30°", "45°", "60°", "90°"], answer: "45°" } + { difficulty: "easy", question: "What is the path of a projectile in ideal conditions?", options: ["Straight line", "Circular", "Parabolic", "Elliptical"], answer: "Parabolic" }, + { difficulty: "easy", question: "What is the horizontal acceleration of a projectile in the absence of air resistance?", options: ["9.8 m/s²", "0 m/s²", "Depends on initial velocity", "Constant"], answer: "0 m/s²" }, + { difficulty: "medium", question: "At the highest point of its trajectory, what is the vertical velocity of a projectile?", options: ["Maximum", "Zero", "Equal to initial velocity", "Depends on mass"], answer: "Zero" }, + { difficulty: "medium", question: "Which factor affects the range of a projectile the most?", options: ["Mass", "Launch angle", "Time of flight", "Shape"], answer: "Launch angle" }, + { difficulty: "hard", question: "What is the optimal angle for maximum range in projectile motion (neglecting air resistance)?", options: ["30°", "45°", "60°", "90°"], answer: "45°" } ]; -let currentQuestionIndex = 0; +let adaptiveQuiz = null; +let adaptiveSteps = []; +let currentStepIndex = 0; let score = 0; let selectedOption = null; -let userAnswers = new Array(questions.length).fill(null); +let userSelectionsByStep = []; + function loadQuestion() { let questionData = questions[currentQuestionIndex]; diff --git a/quiz/rayquiz.html b/quiz/rayquiz.html index 3e700ec..cd294ad 100644 --- a/quiz/rayquiz.html +++ b/quiz/rayquiz.html @@ -51,7 +51,9 @@

🎉 Quiz Completed!

+ + \ No newline at end of file