Reputation: 17
I'm trying to dynamically generate questions and answers in a quiz game. The questions/answers are held in an array of objects and are supposed to be generated one after another inside of a div. When the user clicks on an answer, then the next question is generated. For now there are 5 questions and the problem is that after the first two questions are generated, the 3rd one gets skipped over, the 4th one is generated, and then the 5th gets skipped. When I submit an answer for the second question, it appears in the console that the 2nd and 3rd question were answered, even though the 3rd question never appeared on the page.
here is the javascript-
const questions = [
{
question: "Where was the first attempted allied invasion of France?",
choices: ['Normandy', 'Nice', 'Dieppe', "Bourdeaux"],
answer: "Dieppe"
},
{
question: "Which American general was in charge of the pacific campaign?",
choices: ["George Patton", "Omar Bradley", "George Marshall", "Douglas MacArthur"],
answer: "Douglas MacArthur"
},
{
question: "When was VE day?",
choices: ["April", "May", "June", "July"],
answer: "May"
},
{
question: "Which of these was considered the largest tank battle in history?",
choices: ["Battle of the Bulge", "D-Day", "Kursk", "Stalingrad", "Market Garden"],
answer: "Kursk"
},
{
question: "When did the war start?",
choices: ["1939", "1938", "1941", "1944"],
answer: "1939"
}
]
let questionIndex = 0;
//function to start the game//
const startGame = () => {
$('.start-btn').hide()
$('.game-header').hide()
$('.container').show();
renderQuestion()
}
const renderQuestion = () => {
$('#question').text(questions[questionIndex].question);
$('#answer1').text(questions[questionIndex].choices[0])
$('#answer2').text(questions[questionIndex].choices[1])
$('#answer3').text(questions[questionIndex].choices[2])
$('#answer4').text(questions[questionIndex].choices[3])
$('.btn').click(function () {
let response = $(this).text()
console.log(response)
checkAnswer(response)
})
}
//function to set the timer
//function to end the quiz
//function to save high score
const checkAnswer = (response) => {
if (response === questions[questionIndex].answer) {
window.alert('CORRECT!')
} else {
window.alert('INCORRECT!')
}
questionIndex++
console.log(questionIndex)
renderQuestion()
}
$('.start-btn').click(startGame)
I believe the problem has to do with the "questionIndex" variable that I declared after the question array. At the bottom of the checkAnswer function, I have questionIndex++ which is supposed to cycle through all the questions, but I'm not sure what I'm getting wrong.
and here is the html-
<body>
<div class="timer">
Time left
</div>
<div class="game-header">
<h1>Welcome to the WWII trivia game!</h1>
<h2>Answer all the questions before the time runs out and check your score in the end!</h2>
</div>
<button class="start-btn">Start!</button>
<div class="container">
<div id="question-container" class="hide">
<div id="question">""</div>
<div class="answers">
<button id='answer1' class="btn">Answer 1</button>
<button id='answer2' class="btn">Answer 2</button>
<button id='answer3' class="btn">Answer 3</button>
<button id='answer4' class="btn">Answer 4</button>
</div>
</div>
<div class="controls">
<button id="start-btn" class="start-btn btn">Start</button>
<button id="start-btn" class="start-btn btn hide">Next</button>
</div>
</div>
<script src="script.js"></script>
</body>
So to conclude, I'm trying to cycle through all the questions and corresponding answers in the array so that they are generated one after another in the quiz game. as of now, the 3rd and 5th questions are being omitted. Appreciate any help :)
Upvotes: 1
Views: 524
Reputation: 11
I think one the problems was that you didn't check if your questionIndex was out of range (0-4). Also when you call your checkAnswer() function from your arrow function, you assign that arrow function to every element that has a btn class. So if you click the answer button, then click the next button, you increase the value of questionIndex two times. Maybe that was the issue.
Here is my solution for your question:
const start = document.getElementById('start');
const a1 = document.getElementById('answer1');
const a2 = document.getElementById('answer2');
const a3 = document.getElementById('answer3');
const a4 = document.getElementById('answer4');
const q = document.getElementById('question');
a1.onclick = handleRes;
a2.onclick = handleRes;
a3.onclick = handleRes;
a4.onclick = handleRes;
const questions = [{
question: "Where was the first attempted allied invasion of France?",
choices: ['Normandy', 'Nice', 'Dieppe', "Bourdeaux"],
answer: "Dieppe"
},
{
question: "Which American general was in charge of the pacific campaign?",
choices: ["George Patton", "Omar Bradley", "George Marshall", "Douglas MacArthur"],
answer: "Douglas MacArthur"
},
{
question: "When was VE day?",
choices: ["April", "May", "June", "July"],
answer: "May"
},
{
question: "Which of these was considered the largest tank battle in history?",
choices: ["Battle of the Bulge", "D-Day", "Kursk", "Stalingrad", "Market Garden"],
answer: "Kursk"
},
{
question: "When did the war start?",
choices: ["1939", "1938", "1941", "1944"],
answer: "1939"
}
]
let questionIndex = 0;
function handleRes(res) {
console.log(res.path[0].innerHTML);
checkAnswer(res.path[0].innerHTML);
}
//function to start the game//
const startGame = () => {
/*$('.start-btn').hide()
$('.game-header').hide()
$('.container').show();*/
renderQuestion()
}
const renderQuestion = () => {
/*$('#question').text(questions[questionIndex].question);
$('#answer1').text(questions[questionIndex].choices[0])
$('#answer2').text(questions[questionIndex].choices[1])
$('#answer3').text(questions[questionIndex].choices[2])
$('#answer4').text(questions[questionIndex].choices[3])*/
q.innerHTML = questions[questionIndex].question;
a1.innerHTML = questions[questionIndex].choices[0];
a2.innerHTML = questions[questionIndex].choices[1];
a3.innerHTML = questions[questionIndex].choices[2];
a4.innerHTML = questions[questionIndex].choices[3];
}
//function to set the timer
//function to end the quiz
//function to save high score
const checkAnswer = (response) => {
if (response === questions[questionIndex].answer) {
window.alert('CORRECT!')
} else {
window.alert('INCORRECT!')
}
if (questionIndex == questions.length - 1) {
return;
}
questionIndex++
console.log(questionIndex)
renderQuestion()
}
start.addEventListener('click', startGame);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>Document</title>
</head>
<body>
<div class="timer">
Time left
</div>
<div class="game-header">
<h1>Welcome to the WWII trivia game!</h1>
<h2>Answer all the questions before the time runs out and check your score in the end!</h2>
</div>
<button id="start" class="start-btn">Start!</button>
<div class="container">
<div id="question-container" class="hide">
<div id="question">""</div>
<div class="answers">
<button id='answer1' onclick="handleRes(this.innerHTML)" class="btn">Answer 1</button>
<button id='answer2' onclick="handleRes(this.innerHTML)" class="btn">Answer 2</button>
<button id='answer3' onclick="handleRes(this.innerHTML)" class="btn">Answer 3</button>
<button id='answer4' onclick="handleRes(this.innerHTML)" class="btn">Answer 4</button>
</div>
</div>
<div class="controls">
<button id="start-btn" class="start-btn btn">Start</button>
<button id="start-btn" class="start-btn btn hide">Next</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
Upvotes: 1
Reputation: 43950
At first glance the OP looked OK, I didn't bother to actually take the time to test it since there's no minimal reproduceable example. I reformatted it because there were a huge amount of empty lines and that hinders debugging -- you need to see as much of the code you can neatly fit within sight. I also set the only top level event handler to the top, it doesn't affect functionality. Here's a breakdown of what I see in OP:
/*
There are 7 buttons.
button#start.start-btn
button#answer1.btn
button#answer2.btn
button#answer3.btn
button#answer4.btn
button#start-btn.start-btn.btn <= Invalid id must be unique
button#start-btn.start-btn.btn.hide <= Invalid as above
- Although invalid they actual cause no problems
*/
/*
A. The first time button is clicked (button#start.start-btn):
1. startGame() is called
2. renderGame() is called
a. ALL .btn's have an event handler that calls checkAnswer() 🪲
B. The second time a button is clicked (button#answerX.btn):
1. checkAnswer() is called 💣
2. questionIndex = 1 💣
3. renderGame() is called 💣
a. ALL .btn's have an event handler that calls checkAnswer() 🪲
C. User clicks button#answerX.btn again:
1. checkAnswer() is called twice 💥
2. questionIndex = 3 💥
3. renderGame() is called twice 💥
a. ALL .btn's have an event handler that calls checkAnswer() 🪲
b. ALL .btn's have an event handler that calls checkAnswer() 🪲
Event handlers don't get overwritten they will accumulate and each one will
fire when a click event happens on a bound tag.
*/
$('.start-btn').click(startGame);
const startGame = () => {
$('.start-btn').hide();
$('.game-header').hide();
$('.container').show();
renderQuestion();
};
const renderQuestion = () => {
$('#question').text(questions[questionIndex].question);
$('#answer1').text(questions[questionIndex].choices[0]);
$('#answer2').text(questions[questionIndex].choices[1]);
$('#answer3').text(questions[questionIndex].choices[2]);
$('#answer4').text(questions[questionIndex].choices[3]);
$('.btn').click(function () { // 🪲
let response = $(this).text();
console.log(response);
checkAnswer(response);
});
};
const checkAnswer = (response) => {
if (response === questions[questionIndex].answer) {
window.alert('CORRECT!'); /* 💩 Even for testing alert() IMO is a code
smell */
} else {
window.alert('INCORRECT!'); /* 💩 That goes for prompt() and confirm()
as well */
}
questionIndex++;
console.log(questionIndex);
renderQuestion();
};
I'm not going to show you how to fix that code because it has other problems that reveal themselves when something else has changed. So here's a working example with comments. BTW, the 4th QA had 5 choices -- so I removed "Market Garden". I left out the timer() and the details portion at the end, I have already went beyond the scope of question.
Details are commented in example
const qa = [{
prompt: "Where was the first attempted allied invasion of France?",
choices: ["Normandy", "Nice", "Dieppe", "Bourdeaux"],
correct: "Dieppe"
}, {
prompt: "Which American general was in charge of the pacific campaign?",
choices: ["George Patton", "Omar Bradley", "George Marshall", "Douglas MacArthur"],
correct: "Douglas MacArthur"
}, {
prompt: "When was V-Day?",
choices: ["April", "May", "June", "July"],
correct: "May"
}, {
prompt: "Which of these was considered the largest tank battle in history?",
choices: ["Battle of the Bulge", "D-Day", "Kursk", "Stalingrad"],
correct: "Kursk"
}, {
prompt: "When did WWII start?",
choices: ["1939", "1940", "1941", "1944"],
correct: "1939"
}];
let q = 0;
let userPicks = [];
/* button.start clicked */
/*
Rearrange interface start first QA with nextQA()
button.next starts disabled
*/
$('.start').on('click', function() {
//timer(duration);
$('main').show();
$('header').hide();
$(this).hide();
$('.next').show().attr('disabled', true);
nextQA();
});
/* Any input[type='radio'] is changed */
/*
button.next is enabled
This behavior forces user to answer the current QA to
move on to the next QA
*/
$(':radio').on('change', function() {
$('.next').prop('disabled', false);
});
/* button.next is clicked */
/*
Get the checked :radio and put it's value and the text of the label
that sits next to it.
ex. userPicks = [['0', 'Nice'], ['1', 'Douglas MacArthur']]
That's the first two QA first is wrong, second is right
*/
/*
Increment q. After the last QA this function gets "short circuited
to invoke endQA(). Otherwise the :radio:checked gets unchecked
and button.next gets disabled, then nextQA() is called
*/
$('.next').on('click', function() {
userPicks.push([$(':radio:checked').val(), $(':radio:checked').next().text()]);
++q;
if (q === qa.length) {
return endQA();
}
$(':radio:checked').prop('checked', false);
$(this).prop('disabled', true);
nextQA();
});
/*
Populate output.question and each .pick p with current data from qa
Change the value of the :radio with qa['correct'] data from '0' to '1'
*/
function nextQA() {
$(".question").val(qa[q].prompt).attr('data-count', q + 1);
$('.pick p').each(function(idx) {
$(this).text(qa[q].choices[idx]);
if ($(this).text() === qa[q].correct) {
$(this).prev().val('1');
} else {
$(this).prev().val('0');
}
});
}
/*
Rearrange interface and sort out wrong answers and right answers.
Display the score on output.score
*/
function endQA() {
// timer(false);
$('.QA').hide();
$('.next').hide();
$('.results').show();
$('.repeat').show();
let wrong = [], right = [];
userPicks.forEach((ans, idx) => {
if (ans[0] === '1') {
right.push([idx, ans[1]]);
} else {
wrong.push([idx, ans[1]]);
}
});
$('.score').val(`${right.length} out of ${qa.length}`);
}
*,
*::before,
*::after {
margin: 0;
padding: 0
}
html {
font: 300 2ch/1.2 'Segoe UI'
}
body {
padding: 8px;
}
header {
margin-bottom: 10px;
}
main {
display: none;
}
fieldset {
padding: 0 1rem 1rem;
}
legend {
margin-bottom: 8px;
font-size: 1.5rem;
}
time {
font-family: consolas;
font-size: 1.75rem;
}
time::before {
content: attr(datetime)
}
output {
display: block;
margin-bottom: 8px;
}
.question::before {
content: attr(data-count)'. '
}
ol {
margin-left: 30px;
}
.qaList {
list-style: lower-latin;
}
li {
display: list-item;
margin-bottom: 8px;
}
input,
button {
display: inline-block;
font: inherit
}
.pick {
display: inline-block;
vertical-align: middle
}
.pick input {
vertical-align: middle;
}
.pick p {
display: inline-block;
margin-left: 5px;
}
.pick:last-of-type p {
vertical-align: text-top
}
.pick:last-of-type input {
margin-top: -4.5px
}
.start,
.next,
.repeat {
width: 100%;
padding: 0.25rem 0;
font-size: 1.5rem;
font-weight: 500;
cursor: pointer
}
.next,
.repeat {
display: none
}
summary {
cursor: pointer
}
<header>
<h1>WWII Trivia</h1>
<p>Answer all the questions before the time runs out and check your score in the end</p>
</header>
<main>
<form id='quiz'>
<fieldset class='QA'>
<legend>
<time datetime="00:00"></time>
</legend>
<output class="question"></output>
<ol class='qaList'>
<li><label class='pick'>
<input name='pick' type='radio' value='0'><p></p>
</label></li>
<li><label class='pick'>
<input name='pick' type='radio' value='0'><p></p>
</label></li>
<li><label class='pick'>
<input name='pick' type='radio' value='0'><p></p>
</label></li>
<li><label class='pick'>
<input name='pick' type='radio' value='0'><p></p>
</label></li>
</ol>
</fieldset>
<fieldset class='results' hidden>
<legend>Quiz Results</legend>
<output class='score'></output>
<details>
<summary>Review Answers</summary>
<ol class='endList'></ol>
</details>
</fieldset>
</form>
</main>
<menu>
<button class='start' type='button'>Start</button>
<button class='next' type='button'>Next</button>
<button class='repeat' type='button'>Start Again</button>
</menu>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="script.js"></script>
Upvotes: 0
Reputation: 13099
As mentioned in a comment, you should separate out the different tasks that you need to perform into their own functions. You catch mistakes in logic much, much faster amongst other benefits. If you increment the questionIndex each time someone tries an answer, you've got problems when they get the answer wrong. They'll need to hit the button more than once, which will change the questionIndex more than once.
Here's a version that works here on the page without jQuery.
"use strict";
function byId(id){return document.getElementById(id)}
function qsa(sel,par=document){return par.querySelectorAll(sel)}
function qs(sel,par=document){return par.querySelector(sel)}
var questionIndex;
window.addEventListener('load', onLoaded, false);
function onLoaded(evt)
{
let ansBtns = qsa('.answers > .btn');
ansBtns.forEach( btn => btn.addEventListener('click', onAnswerBtnClicked, false) );
startGame();
}
function startGame()
{
qs('.start-btn').style.display = 'none';
qs('.game-header').style.display = 'none';
qs('.container').style.display = '';
questionIndex = 0;
renderQuestion(questionIndex);
}
function renderQuestion(index)
{
let curQ = questions[index];
qs('#question').textContent = curQ.question;
qs('#answer1').textContent = curQ.choices[0];
qs('#answer2').textContent = curQ.choices[1];
qs('#answer3').textContent = curQ.choices[2];
qs('#answer4').textContent = curQ.choices[3];
}
function isAnswerCorrect(answer, questionIndex)
{
let curQ = questions[questionIndex];
if (answer == curQ.answer)
return true;
else
return false;
}
function onAnswerBtnClicked(evt)
{
let answer = this.textContent;
if (isAnswerCorrect(answer, questionIndex))
{
alert('correct');
if (questionIndex < 3)
{
questionIndex++;
renderQuestion(questionIndex);
}
// all questions dealt with.
// quiz finished.
else
{
}
}
else
alert('incorrect');
}
const questions = [
{
question: "Where was the first attempted allied invasion of France?",
choices: ['Normandy', 'Nice', 'Dieppe', "Bourdeaux"],
answer: "Dieppe"
},
{
question: "Which American general was in charge of the pacific campaign?",
choices: ["George Patton", "Omar Bradley", "George Marshall", "Douglas MacArthur"],
answer: "Douglas MacArthur"
},
{
question: "When was VE day?",
choices: ["April", "May", "June", "July"],
answer: "May"
},
{
question: "Which of these was considered the largest tank battle in history?",
choices: ["Battle of the Bulge", "D-Day", "Kursk", "Stalingrad", "Market Garden"],
answer: "Kursk"
},
{
question: "When did the war start?",
choices: ["1939", "1938", "1941", "1944"],
answer: "1939"
}
];
<body>
<div class="timer">
Time left
</div>
<div class="game-header">
<h1>Welcome to the WWII trivia game!</h1>
<h2>Answer all the questions before the time runs out and check your score in the end!</h2>
</div>
<button class="start-btn">Start!</button>
<div class="container">
<div id="question-container" class="hide">
<div id="question">""</div>
<div class="answers">
<button id='answer1' class="btn">Answer 1</button>
<button id='answer2' class="btn">Answer 2</button>
<button id='answer3' class="btn">Answer 3</button>
<button id='answer4' class="btn">Answer 4</button>
</div>
</div>
<div class="controls">
<button id="start-btn" class="start-btn btn">Start</button>
<button id="start-btn" class="start-btn btn hide">Next</button>
</div>
</div>
</body>
Upvotes: 0