Skip to content

Commit e666ff3

Browse files
Merge pull request #166 from Glynis1314/quiz-updated
Quiz features updated
2 parents e8f6373 + 93a0f75 commit e666ff3

File tree

3 files changed

+316
-46
lines changed

3 files changed

+316
-46
lines changed

projects/quiz/index.html

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,67 @@ <h1>Quiz</h1>
1515
<div class="controls">
1616
<button id="theme-toggle" aria-pressed="false" title="Toggle theme">🌙</button>
1717
<div class="timer">
18-
Time Left: <span id="time">15</span>s
18+
Time Left: <span id="time">20</span>s
1919
</div>
2020
</div>
2121
</div>
22-
<div id="q"></div>
23-
<div id="answers"></div>
24-
<div id="result"></div>
25-
<p class="notes">Add question sets, scoring, and categories.</p>
22+
23+
<!-- Settings Panel -->
24+
<div id="settings" class="settings-panel">
25+
<div class="setting-group">
26+
<label for="category">Category:</label>
27+
<select id="category">
28+
<option value="any">Any Category</option>
29+
<option value="9">General Knowledge</option>
30+
<option value="17">Science & Nature</option>
31+
<option value="18">Computers</option>
32+
<option value="19">Mathematics</option>
33+
<option value="22">Geography</option>
34+
<option value="23">History</option>
35+
</select>
36+
</div>
37+
38+
<div class="setting-group">
39+
<label>Difficulty:</label>
40+
<div class="radio-group">
41+
<label><input type="radio" name="difficulty" value="any" checked> Any</label>
42+
<label><input type="radio" name="difficulty" value="easy"> Easy</label>
43+
<label><input type="radio" name="difficulty" value="medium"> Medium</label>
44+
<label><input type="radio" name="difficulty" value="hard"> Hard</label>
45+
</div>
46+
</div>
47+
48+
<div class="setting-group">
49+
<label for="question-count">Questions:</label>
50+
<select id="question-count">
51+
<option value="5">5 Questions</option>
52+
<option value="10">10 Questions</option>
53+
<option value="15">15 Questions</option>
54+
<option value="20">20 Questions</option>
55+
</select>
56+
</div>
57+
58+
<div class="setting-group">
59+
<label><input type="checkbox" id="shuffle-answers" checked> Shuffle Answers</label>
60+
<label><input type="checkbox" id="shuffle-questions" checked> Shuffle Questions</label>
61+
</div>
62+
63+
<button id="start-quiz" class="start-btn">Start Quiz</button>
64+
</div>
65+
66+
<!-- Quiz Content -->
67+
<div id="quiz-content" class="hidden">
68+
<div class="progress-container">
69+
<div class="progress-bar">
70+
<div class="progress-fill" id="progress-fill"></div>
71+
</div>
72+
<div class="progress-text" id="progress-text">Question 1 of 5</div>
73+
</div>
74+
75+
<div id="q"></div>
76+
<div id="answers"></div>
77+
<div id="result"></div>
78+
</div>
2679
</main>
2780
<script type="module" src="./main.js"></script>
2881
</body>

projects/quiz/main.js

Lines changed: 158 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
const TIME_LIMIT = 15; // seconds per question
1+
const TIME_LIMIT = 20; // seconds per question
22
let timeLeft = TIME_LIMIT;
33
let timerInterval = null;
44

55
const timerElement = document.getElementById("time");
6+
const settingsPanel = document.getElementById("settings");
7+
const quizContent = document.getElementById("quiz-content");
8+
const progressFill = document.getElementById("progress-fill");
9+
const progressText = document.getElementById("progress-text");
10+
const startButton = document.getElementById("start-quiz");
611

712
let questions = []; // API-loaded questions
13+
let currentQuestions = []; // Questions for current session
814
let i = 0, score = 0;
15+
let totalQuestions = 5;
916

1017
const q = document.getElementById('q'),
1118
answers = document.getElementById('answers'),
@@ -35,38 +42,95 @@ const stored = safeGet('quiz-theme');
3542
const prefersLight = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches;
3643
applyTheme(stored ? stored : (prefersLight ? 'light' : 'dark'));
3744

45+
/** Fisher-Yates shuffle algorithm */
46+
function shuffleArray(array) {
47+
const newArray = [...array];
48+
for (let i = newArray.length - 1; i > 0; i--) {
49+
const j = Math.floor(Math.random() * (i + 1));
50+
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
51+
}
52+
return newArray;
53+
}
54+
3855
/** Decode HTML entities from API */
3956
function decodeHTML(str) {
4057
const txt = document.createElement('textarea');
4158
txt.innerHTML = str;
4259
return txt.value;
4360
}
4461

45-
/** Shuffle array */
46-
function shuffle(arr) {
47-
return arr.sort(() => Math.random() - 0.5);
62+
/** Get settings from UI */
63+
function getSettings() {
64+
const category = document.getElementById('category').value;
65+
const difficulty = document.querySelector('input[name="difficulty"]:checked').value;
66+
const questionCount = parseInt(document.getElementById('question-count').value);
67+
const shuffleAnswers = document.getElementById('shuffle-answers').checked;
68+
const shuffleQuestions = document.getElementById('shuffle-questions').checked;
69+
70+
return { category, difficulty, questionCount, shuffleAnswers, shuffleQuestions };
4871
}
4972

50-
/** Fetch questions from Open Trivia DB API */
73+
/** Fetch questions from Open Trivia DB API with filters */
5174
async function loadQuestions() {
5275
try {
53-
const res = await fetch('https://opentdb.com/api.php?amount=5&type=multiple');
76+
const settings = getSettings();
77+
totalQuestions = settings.questionCount;
78+
79+
let apiUrl = `https://opentdb.com/api.php?amount=20&type=multiple`;
80+
if (settings.category !== 'any') {
81+
apiUrl += `&category=${settings.category}`;
82+
}
83+
if (settings.difficulty !== 'any') {
84+
apiUrl += `&difficulty=${settings.difficulty}`;
85+
}
86+
87+
const res = await fetch(apiUrl);
5488
const data = await res.json();
89+
90+
if (data.response_code !== 0 || !data.results.length) {
91+
throw new Error('No questions available with selected filters');
92+
}
93+
5594
questions = data.results.map(q => ({
5695
q: decodeHTML(q.question),
57-
a: shuffle([decodeHTML(q.correct_answer), ...q.incorrect_answers.map(decodeHTML)]),
58-
c: null, // correct answer index
59-
correctAnswer: decodeHTML(q.correct_answer)
96+
a: [decodeHTML(q.correct_answer), ...q.incorrect_answers.map(decodeHTML)],
97+
c: 0, // correct answer index will be set after shuffling
98+
correctAnswer: decodeHTML(q.correct_answer),
99+
difficulty: q.difficulty,
100+
category: q.category
60101
}));
61-
// Compute correct answer index
62-
questions.forEach(qObj => {
63-
qObj.c = qObj.a.findIndex(ans => ans === qObj.correctAnswer);
102+
103+
// Prepare questions for current session
104+
currentQuestions = settings.shuffleQuestions ?
105+
shuffleArray(questions).slice(0, totalQuestions) :
106+
questions.slice(0, totalQuestions);
107+
108+
// Process each question
109+
currentQuestions.forEach(qObj => {
110+
if (settings.shuffleAnswers) {
111+
const correctIndex = qObj.a.findIndex(ans => ans === qObj.correctAnswer);
112+
const shuffledAnswers = shuffleArray(qObj.a);
113+
qObj.a = shuffledAnswers;
114+
qObj.c = shuffledAnswers.findIndex(ans => ans === qObj.correctAnswer);
115+
} else {
116+
qObj.c = 0; // Correct answer is always first if not shuffled
117+
}
64118
});
119+
65120
} catch (err) {
66121
console.error('Failed to load questions', err);
67-
q.textContent = 'Failed to load questions 😢';
122+
q.textContent = 'Failed to load questions. Please try different filters. 😢';
68123
answers.innerHTML = '';
124+
return false;
69125
}
126+
return true;
127+
}
128+
129+
/** Update progress bar */
130+
function updateProgress() {
131+
const progress = ((i + 1) / currentQuestions.length) * 100;
132+
progressFill.style.width = `${progress}%`;
133+
progressText.textContent = `Question ${i + 1} of ${currentQuestions.length}`;
70134
}
71135

72136
/** Start timer for each question */
@@ -87,33 +151,73 @@ function startTimer() {
87151

88152
if (timeLeft <= 0) {
89153
clearInterval(timerInterval);
90-
handleNextQuestion();
154+
handleTimeUp();
91155
}
92156
}, 1000);
93157
}
94158

159+
/** Handle when time runs out */
160+
function handleTimeUp() {
161+
const currentQuestion = currentQuestions[i];
162+
Array.from(answers.children).forEach(btn => {
163+
btn.disabled = true;
164+
if (parseInt(btn.dataset.index) === currentQuestion.c) {
165+
btn.classList.add('correct');
166+
}
167+
});
168+
169+
result.textContent = 'Time\'s up!';
170+
171+
setTimeout(() => {
172+
handleNextQuestion();
173+
}, 1500);
174+
}
175+
95176
/** Move to next question */
96177
function handleNextQuestion() {
97178
i++;
98-
render();
179+
if (i < currentQuestions.length) {
180+
render();
181+
} else {
182+
endQuiz();
183+
}
184+
}
185+
186+
/** End quiz and show results */
187+
function endQuiz() {
188+
clearInterval(timerInterval);
189+
timerElement.parentElement.style.display = 'none';
190+
q.textContent = '🎉 Quiz Complete!';
191+
answers.innerHTML = '';
192+
result.textContent = `Final Score: ${score}/${currentQuestions.length}`;
193+
194+
// Add restart button
195+
const restartBtn = document.createElement('button');
196+
restartBtn.textContent = 'Try Again';
197+
restartBtn.className = 'start-btn';
198+
restartBtn.style.marginTop = '1rem';
199+
restartBtn.onclick = resetQuiz;
200+
result.appendChild(restartBtn);
201+
}
202+
203+
/** Reset quiz to settings screen */
204+
function resetQuiz() {
205+
i = 0;
206+
score = 0;
207+
currentQuestions = [];
208+
settingsPanel.classList.remove('hidden');
209+
quizContent.classList.add('hidden');
210+
result.textContent = '';
99211
}
100212

101213
/** Render current question */
102214
function render() {
103-
if (!questions.length) return;
104-
105-
if (i >= questions.length) {
106-
clearInterval(timerInterval);
107-
timerElement.parentElement.style.display = 'none';
108-
q.textContent = '🎉 Quiz Complete!';
109-
answers.innerHTML = '';
110-
result.textContent = `Score: ${score}/${questions.length}`;
111-
return;
112-
}
215+
if (!currentQuestions.length) return;
113216

217+
updateProgress();
114218
startTimer();
115219

116-
const cur = questions[i];
220+
const cur = currentQuestions[i];
117221
q.textContent = cur.q;
118222
answers.innerHTML = '';
119223
result.textContent = '';
@@ -122,36 +226,49 @@ function render() {
122226
const b = document.createElement('button');
123227
b.textContent = ans;
124228
b.className = 'answer-btn';
229+
b.dataset.index = idx;
125230
b.addEventListener('click', () => {
126-
// prevent double clicks
127231
if (b.disabled) return;
128232
clearInterval(timerInterval);
129-
// mark selected
130-
Array.from(answers.children).forEach(x=>x.classList.remove('selected'));
233+
234+
Array.from(answers.children).forEach(x => x.classList.remove('selected'));
131235
b.classList.add('selected');
132-
// mark correct/incorrect
236+
133237
if (idx === cur.c){
134238
b.classList.add('correct');
135239
score++;
240+
result.textContent = 'Correct! 🎉';
136241
} else {
137242
b.classList.add('incorrect');
138-
// reveal the correct one
139243
const correctBtn = answers.children[cur.c];
140244
if (correctBtn) correctBtn.classList.add('correct');
245+
result.textContent = 'Incorrect 😞';
141246
}
142-
// disable all to avoid extra clicks
143-
Array.from(answers.children).forEach(x=>x.disabled=true);
144-
// short delay to show feedback
145-
setTimeout(()=>{
247+
248+
Array.from(answers.children).forEach(x => x.disabled = true);
249+
250+
setTimeout(() => {
146251
handleNextQuestion();
147-
}, 700);
252+
}, 1500);
148253
});
149254
answers.appendChild(b);
150255
});
151256
}
152257

153-
(async function init() {
154-
result.textContent = 'Loading questions...';
155-
await loadQuestions();
156-
render();
157-
})();
258+
/** Start quiz */
259+
async function startQuiz() {
260+
const success = await loadQuestions();
261+
if (success && currentQuestions.length > 0) {
262+
i = 0;
263+
score = 0;
264+
settingsPanel.classList.add('hidden');
265+
quizContent.classList.remove('hidden');
266+
render();
267+
}
268+
}
269+
270+
// Event Listeners
271+
startButton.addEventListener('click', startQuiz);
272+
273+
// Initialize
274+
result.textContent = 'Configure your quiz settings and click "Start Quiz"';

0 commit comments

Comments
 (0)