From 287ae2d3924c7be49bf8775b580ebc54ccdb97e4 Mon Sep 17 00:00:00 2001
From: hudazaan
Date: Tue, 7 Oct 2025 18:09:09 +0530
Subject: [PATCH] Implemented Adaptive Difficulty Level
---
src/App.jsx | 399 ++++++++++++++++++++++++++++++++++----------------
src/index.css | 53 +++++++
2 files changed, 328 insertions(+), 124 deletions(-)
diff --git a/src/App.jsx b/src/App.jsx
index 247c000..89dc58a 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -331,6 +331,21 @@ export default function App() {
// }
// ]
// };
+
+ // REAL Adaptive difficulty state
+ const [userPerformance, setUserPerformance] = useState({
+ correctStreak: 0,
+ totalCorrect: 0,
+ totalAnswered: 0,
+ currentDifficulty: "Medium",
+ questionPool: {
+ Easy: [],
+ Medium: [],
+ Hard: []
+ },
+ currentQuestionIndex: 0
+ });
+
const [loading, setLoading] = useState(false);
const [quiz, setQuiz] = useState([]);
const [answers, setAnswers] = useState({});
@@ -365,33 +380,16 @@ export default function App() {
localStorage.setItem("darkMode", JSON.stringify(isDarkMode));
}, [isDarkMode]);
- const toggleDarkMode = () => {
- setIsDarkMode(!isDarkMode);
- };
-
- // Fetch Quiz
- async function fetchQuiz() {
- setLoading(true);
- setSubmitted(false);
- setAnswers({});
- setShowStartScreen(false);
-
+ // Generate questions for a specific difficulty
+ async function generateQuestions(difficulty, count = 5) {
try {
- // Use custom questions if available for the selected category
- // if (customQuestions[selectedCategory]) {
- // const questions = customQuestions[selectedCategory].slice(0, numQuestions);
- // setQuiz(questions);
- // setTimeLeft(questions.length * 30);
- // setLoading(false);
- // return;
- // }
- // Otherwise, use AI-generated questions
const genAI = new GoogleGenerativeAI(import.meta.env.VITE_GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });
+
const prompt = `
- Generate ${numQuestions} multiple-choice questions focused on ${selectedCategory}, tailored for Indian government exam preparation (e.g., UPSC, SSC, or similar competitive exams). Ensure questions are exam-oriented: they should cover key topics, historical events, policies, figures, or concepts relevant to the category, with a focus on factual accuracy, analytical depth, and real-world application where appropriate.
+ Generate ${count} multiple-choice questions focused on ${selectedCategory} at ${difficulty} difficulty level.
- Adhere to the selected difficulty level which is ${selectedDifficulty}:
+ DIFFICULTY SPECIFICS:
- Easy: Basic recall of facts, straightforward questions with obvious distractors.
- Medium: Require moderate understanding, including connections between concepts, with plausible distractors.
- Hard: In-depth analysis, nuanced details, or application-based questions, with closely related distractors that test deep knowledge.
@@ -402,6 +400,7 @@ export default function App() {
- Options: Provide exactly 4 options per question. Distractors must be plausible and based on common misconceptions or related facts.
- Answer: Must be factually correct and exactly match one option (case-sensitive, including spacing).
- Explanation: Provide a detailed, educational explanation (2-4 sentences) citing why the answer is correct and why others are not, to aid learning.
+ - Focus on ${selectedCategory} topics
Respond strictly in valid JSON array format (no extra text, code blocks, or markdown). Example:
[
@@ -411,36 +410,183 @@ export default function App() {
"answer": "Narendra Modi",
"explanation": "Narendra Modi has been the Prime Minister of India since 2014, leading the BJP government. The other options are prominent politicians but not the current PM."
}
- ]
+ ]`;
- Ensure the entire response is parseable as JSON.`;
const result = await model.generateContent(prompt);
let text = await result.response.text();
text = text.replace(/```json|```/g, "").trim();
- console.log("Raw AI response:", text); // Debug log
-
const questions = JSON.parse(text);
-
- // Validate and clean the data
- const cleanedQuestions = questions.map((q) => ({
+ return questions.map(q => ({
...q,
answer: q.answer?.trim(),
- options: q.options?.map((opt) => opt?.trim()),
+ options: q.options?.map(opt => opt?.trim()),
+ difficulty: difficulty // Tag each question with its difficulty
}));
+ } catch (err) {
+ console.error(`Error generating ${difficulty} questions:`, err);
+ return [];
+ }
+ }
+
+ // Pre-load questions for ALL difficulty levels
+ async function preloadQuestionPools() {
+ setLoading(true);
+
+ try {
+ // Generate questions for all difficulty levels in parallel
+ const [easyQuestions, mediumQuestions, hardQuestions] = await Promise.all([
+ generateQuestions("Easy", 8),
+ generateQuestions("Medium", 8),
+ generateQuestions("Hard", 8)
+ ]);
- console.log("Cleaned questions:", cleanedQuestions); // Debug log
+ setUserPerformance(prev => ({
+ ...prev,
+ questionPool: {
+ Easy: easyQuestions,
+ Medium: mediumQuestions,
+ Hard: hardQuestions
+ },
+ currentDifficulty: selectedDifficulty
+ }));
- setQuiz(cleanedQuestions);
- // Set timer based on number of questions (30 seconds per question)
- setTimeLeft(cleanedQuestions.length * 30);
+ // Start with medium difficulty questions
+ setQuiz(mediumQuestions.slice(0, numQuestions));
+ setTimeLeft(numQuestions * 30);
+
} catch (err) {
- console.error("Error generating quiz:", err);
- alert("Failed to generate quiz. Check console.");
+ console.error("Error preloading questions:", err);
+ alert("Failed to load questions. Please try again.");
}
setLoading(false);
}
+ // Get next question based on current performance
+ const getNextQuestion = (currentPerformance) => {
+ const { questionPool, currentDifficulty, currentQuestionIndex } = currentPerformance;
+
+ // If we have questions left in current difficulty pool
+ if (currentQuestionIndex < questionPool[currentDifficulty].length - 1) {
+ return questionPool[currentDifficulty][currentQuestionIndex];
+ }
+
+ // If we're out of questions in current difficulty, try to get from other pools
+ const availableQuestions = [
+ ...questionPool[currentDifficulty],
+ ...questionPool.Medium,
+ ...questionPool.Easy,
+ ...questionPool.Hard
+ ].filter(q => !answers[Object.keys(answers).length]); // Not already answered
+
+ return availableQuestions[0] || questionPool.Medium[0]; // Fallback
+ };
+
+ // REAL adaptive difficulty calculation
+ const calculateRealAdaptiveDifficulty = (performance) => {
+ const { correctStreak, totalCorrect, totalAnswered, currentDifficulty } = performance;
+
+ if (totalAnswered < 2) return currentDifficulty; // Wait for some data
+
+ const accuracy = totalCorrect / totalAnswered;
+
+ // Adaptive logic:
+ if (currentDifficulty === "Easy" && correctStreak >= 3 && accuracy >= 0.7) {
+ return "Medium";
+ } else if (currentDifficulty === "Medium") {
+ if (correctStreak >= 3 && accuracy >= 0.8) {
+ return "Hard";
+ } else if (accuracy < 0.4) {
+ return "Easy";
+ }
+ } else if (currentDifficulty === "Hard" && accuracy < 0.5) {
+ return "Medium";
+ }
+
+ return currentDifficulty;
+ };
+
+ // Handle option selection with REAL adaptation
+ function handleOptionSelect(qIndex, option) {
+ if (!submitted) {
+ const currentQuestion = quiz[qIndex];
+ const isCorrect = option?.trim().toLowerCase() === currentQuestion.answer?.trim().toLowerCase();
+
+ setAnswers((prev) => ({ ...prev, [qIndex]: option }));
+
+ // Update performance and potentially change difficulty
+ setUserPerformance(prev => {
+ const newTotalAnswered = prev.totalAnswered + 1;
+ const newTotalCorrect = prev.totalCorrect + (isCorrect ? 1 : 0);
+ const newCorrectStreak = isCorrect ? prev.correctStreak + 1 : 0;
+ const newQuestionIndex = prev.currentQuestionIndex + 1;
+
+ const newPerformance = {
+ ...prev,
+ correctStreak: newCorrectStreak,
+ totalCorrect: newTotalCorrect,
+ totalAnswered: newTotalAnswered,
+ currentQuestionIndex: newQuestionIndex
+ };
+
+ // Check if we should change difficulty
+ const newDifficulty = calculateRealAdaptiveDifficulty(newPerformance);
+
+ // If difficulty changed, update the quiz with new questions
+ if (newDifficulty !== prev.currentDifficulty) {
+ setTimeout(() => {
+ updateQuizWithNewDifficulty(newDifficulty);
+ }, 500); // Small delay for smooth transition
+ }
+
+ return {
+ ...newPerformance,
+ currentDifficulty: newDifficulty
+ };
+ });
+ }
+ }
+
+ // Update quiz when difficulty changes
+ const updateQuizWithNewDifficulty = (newDifficulty) => {
+ setUserPerformance(prev => {
+ const newQuestions = prev.questionPool[newDifficulty].slice(0, numQuestions);
+
+ // Update the displayed quiz
+ setQuiz(newQuestions);
+
+ // Reset answers for new questions (keep old ones for scoring)
+ setAnswers({});
+
+ return prev;
+ });
+ };
+
+ const toggleDarkMode = () => {
+ setIsDarkMode(!isDarkMode);
+ };
+
+ // Fetch Quiz
+ async function fetchQuiz() {
+ setLoading(true);
+ setSubmitted(false);
+ setAnswers({});
+ setShowStartScreen(false);
+
+ // Reset performance tracking
+ setUserPerformance({
+ correctStreak: 0,
+ totalCorrect: 0,
+ totalAnswered: 0,
+ currentDifficulty: selectedDifficulty,
+ questionPool: { Easy: [], Medium: [], Hard: [] },
+ currentQuestionIndex: 0
+ });
+
+ // Pre-load questions for all difficulty levels
+ await preloadQuestionPools();
+ }
+
// Timer
useEffect(() => {
if (timeLeft > 0 && !submitted && quiz.length > 0) {
@@ -450,10 +596,6 @@ export default function App() {
if (timeLeft === 0 && quiz.length > 0 && !submitted) handleSubmit();
}, [timeLeft, submitted, quiz]);
- function handleOptionSelect(qIndex, option) {
- if (!submitted) setAnswers((prev) => ({ ...prev, [qIndex]: option }));
- }
-
function handleSubmit() {
setSubmitted(true);
}
@@ -486,6 +628,14 @@ export default function App() {
setSubmitted(false);
setTimeLeft(0);
setShowStartScreen(true);
+ setUserPerformance({
+ correctStreak: 0,
+ totalCorrect: 0,
+ totalAnswered: 0,
+ currentDifficulty: selectedDifficulty,
+ questionPool: { Easy: [], Medium: [], Hard: [] },
+ currentQuestionIndex: 0
+ });
}
const score = submitted ? calculateScore() : null;
@@ -503,6 +653,26 @@ export default function App() {
const progress = calculateProgress();
+ // Get difficulty badge color
+ const getDifficultyColor = (difficulty) => {
+ switch (difficulty) {
+ case "Easy": return "bg-green-500";
+ case "Medium": return "bg-yellow-500";
+ case "Hard": return "bg-red-500";
+ default: return "bg-blue-500";
+ }
+ };
+
+ // Get difficulty emoji
+ const getDifficultyEmoji = (difficulty) => {
+ switch (difficulty) {
+ case "Easy": return "😊";
+ case "Medium": return "😐";
+ case "Hard": return "🔥";
+ default: return "🎯";
+ }
+ };
+
// Close mobile menu when clicking outside
useEffect(() => {
const handleClickOutside = (event) => {
@@ -562,27 +732,7 @@ export default function App() {
className="ml-2 p-2 rounded-lg bg-white/10 hover:bg-white/20 backdrop-blur-sm transition-all duration-300 border border-white/20 hover:border-white/30"
aria-label="Toggle dark mode"
>
- {isDarkMode ? (
-
- ) : (
-
- )}
+ {isDarkMode ? "🌙" : "☀️"}
@@ -593,27 +743,7 @@ export default function App() {
className="p-2 rounded-lg bg-white/10 hover:bg-white/20 backdrop-blur-sm transition-all duration-300 border border-white/20 hover:border-white/30"
aria-label="Toggle dark mode"
>
- {isDarkMode ? (
-
- ) : (
-
- )}
+ {isDarkMode ? "🌙" : "☀️"}
@@ -735,7 +869,7 @@ export default function App() {
className="start-btn"
>
🚀
- Start Quiz
+ {loading ? "Loading Questions..." : "Start Quiz"}
@@ -772,23 +906,8 @@ export default function App() {
Generating Your Quiz
- Crafting {numQuestions} questions on {selectedCategory}...
+ Generating Easy, Medium & Hard questions...
-
-
@@ -801,15 +920,34 @@ export default function App() {
{selectedCategory} Quiz
+
- {selectedDifficulty} • {quiz.length} Questions
+ {getDifficultyEmoji(userPerformance.currentDifficulty)} {userPerformance.currentDifficulty} • {quiz.length} Questions
+ {/* REAL Adaptive difficulty indicator */}
+
+ 🔄 Live Adaptive: {userPerformance.currentDifficulty}
+
+
+ {/* Performance stats */}
+
+ 🔥 Streak: {userPerformance.correctStreak} • ✅ {userPerformance.totalCorrect}/{userPerformance.totalAnswered}
+
+
+
+ {/* NEW: Difficulty progression info */}
+ {userPerformance.totalAnswered > 0 && (
+
+ {userPerformance.currentDifficulty === "Easy" && "Get 3+ correct in a row to advance to Medium!"}
+ {userPerformance.currentDifficulty === "Medium" && userPerformance.correctStreak >= 2 && "Keep going! 1 more correct for Hard level!"}
+ {userPerformance.currentDifficulty === "Hard" && "You're at the highest level! Maintain >50% accuracy."}
+
+ )}
⏱️
- {Math.floor(timeLeft / 60)}:
- {(timeLeft % 60).toString().padStart(2, "0")}
+ {Math.floor(timeLeft / 60)}:{(timeLeft % 60).toString().padStart(2, "0")}
@@ -817,6 +955,9 @@ export default function App() {
Progress: {progress.answered} of {progress.total} questions answered
+ {userPerformance.totalAnswered > 0 && (
+ • Accuracy: {Math.round((userPerformance.totalCorrect / userPerformance.totalAnswered) * 100)}%
+ )}
{progress.percentage}%
@@ -828,11 +969,17 @@ export default function App() {
-
+
{quiz.map((q, idx) => (
-
Q{idx + 1}
+
+ Q{idx + 1}
+ {/* Show actual question difficulty */}
+
+ {q.difficulty}
+
+
{q.question}
@@ -871,6 +1018,12 @@ export default function App() {
🎉
Quiz Completed!
+
+ Your journey: Started at {selectedDifficulty} • Reached:{" "}
+
+ {userPerformance.currentDifficulty}
+
+
@@ -890,35 +1043,37 @@ export default function App() {
{score.total - score.correct}
+
+
+ Best Streak
+ {userPerformance.correctStreak}
- Total
- {score.total}
+ Final Level
+
+ {userPerformance.currentDifficulty}
+
-
+
📝 Answer Review
{quiz.map((q, idx) => {
const userAnswer = answers[idx];
const isCorrect = userAnswer === q.answer;
return (
-
+
-
Q{idx + 1}
-
+
+ Q{idx + 1}
+
+ {q.difficulty}
+
+
+
{isCorrect ? "✅" : "❌"}
@@ -928,11 +1083,7 @@ export default function App() {
Your Answer:
-
+
{userAnswer || "Not answered"}
diff --git a/src/index.css b/src/index.css
index 5f0c28b..91244f8 100644
--- a/src/index.css
+++ b/src/index.css
@@ -948,3 +948,56 @@
border: 1px solid #ddd !important;
}
}
+
+/* Adaptive difficulty badge styles */
+.bg-green-500 {
+ background: linear-gradient(135deg, #10b981, #059669);
+}
+
+.bg-yellow-500 {
+ background: linear-gradient(135deg, #f59e0b, #d97706);
+}
+
+.bg-red-500 {
+ background: linear-gradient(135deg, #ef4444, #dc2626);
+}
+
+.bg-blue-500 {
+ background: linear-gradient(135deg, #3b82f6, #1d4ed8);
+}
+
+.text-green-500 {
+ color: #10b981;
+}
+
+.text-yellow-500 {
+ color: #f59e0b;
+}
+
+.text-red-500 {
+ color: #ef4444;
+}
+
+.text-blue-500 {
+ color: #3b82f6;
+}
+
+.performance-stats {
+ @apply flex items-center gap-4 text-sm;
+}
+
+.streak-indicator {
+ @apply px-2 py-1 rounded-full font-semibold;
+ background: rgba(255, 255, 255, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+@media (max-width: 768px) {
+ .quiz-info .flex-wrap {
+ @apply flex-col items-start gap-2;
+ }
+
+ .performance-stats {
+ @apply flex-col items-start gap-1;
+ }
+}
\ No newline at end of file