1- const TIME_LIMIT = 15 ; // seconds per question
1+ const TIME_LIMIT = 20 ; // seconds per question
22let timeLeft = TIME_LIMIT ;
33let timerInterval = null ;
44
55const 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
712let questions = [ ] ; // API-loaded questions
13+ let currentQuestions = [ ] ; // Questions for current session
814let i = 0 , score = 0 ;
15+ let totalQuestions = 5 ;
916
1017const q = document . getElementById ( 'q' ) ,
1118 answers = document . getElementById ( 'answers' ) ,
@@ -35,38 +42,95 @@ const stored = safeGet('quiz-theme');
3542const prefersLight = window . matchMedia && window . matchMedia ( '(prefers-color-scheme: light)' ) . matches ;
3643applyTheme ( 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 */
3956function 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 */
5174async 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 */
96177function 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 */
102214function 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