JavaScript Editor JavaScript Validator     JavaScript Editor 



Team LiB
Previous Section Next Section

The Trivia Quiz

The goal for the trivia quiz in this chapter is to enable it to set questions with answers that have to be typed in by the user, in addition to the multiple-choice questions we already have. To do this we'll be making use of our newfound knowledge of regular expressions to search the reply that the user types in for a match with the correct answer.

The problem we face with text answers is that a number of possible answers may be correct and we don't want to annoy the user by insisting that only one version is correct. For example, the answer to the question "Which President was involved in the Watergate Scandal?" is Richard Milhous Nixon. However, most people will type Nixon, or maybe Richard Nixon or even R Nixon. Each of these variations is valid, and using regular expressions we can easily check for all of them (or at least many plausible alternatives) in just a few lines of code.

What will we need to change to add this extra functionality? In fact changes are needed in only two pages: the GlobalFunctions.htm page and the AskQuestion.htm page.

In the GlobalFunctions.htm page, we need to define our new questions and answers, change the getQuestion() function, which builds up the HTML to display the question to the user, and change the answerCorrect() function, which checks whether the user's answer is correct.

In the AskQuestion.htm page, we need to change the function getAnswer(), which retrieves the user's answer from the page's form.

We'll start by making the changes to GlobalFunctions.htm that we created in the last chapter, so open this up in your HTML editor.

All the existing multiple-choice questions that we define near the top of the page can remain in exactly the same format, so there's no need for any changes there. How can this be if we're using regular expressions?

Previously we checked to see that the answer the user selected, such as A, B, C, and so on, was equal to the character in the answers array. Well, we can do the same thing, but using a very simple regular expression that matches the character supplied by the user with the character in the answers array. If they match, we know the answer is correct.

Now we'll add the first new text-based question and answer directly underneath the last multiple-choice question in the GlobalFunctions.htm file.

// define question 4
questions[3] = "In the Simpsons, Bleeding Gums Murphy played which instrument?"
// assign answer for question 4
answers[3] = "\\bsax(ophone)?\\b";

The question definition is much simpler for text-based questions than for the multiple-choice questions: it's just the question text itself.

The answer definition is a regular expression. Note that we use \\b rather than \b, since we'll be creating our regular expressions using new RegExp() rather than using the / and / method. The valid answers to this question are sax and saxophone, so we need to define our regular expression to match either of those. We'll see later that the case flag will be set so that even "SaxoPhone" is valid, though dubious, English! Let's break it down stage by stage as shown in the following table.

Expression

Description

\\b

The \\b indicates that the answer must start with a word boundary; in other words the answer must be a whole word and not contained inside another word. We do this just in case the user for some reason puts characters before his answer, such as "my answer is saxophone."

sax

The user's answer must start with the characters sax.

(ophone)?

We've grouped the pattern ophone by putting it in parentheses. Then by putting the ? just after it, we are saying that that pattern can appear zero or one times.

If the user types sax, it appears zero times, and if the user types saxophone, it appears once—either way we make a match.

\\b

Finally we want the word to end at a word boundary

The second question we'll create is

"Which American President was involved in the Watergate Scandal?"

The possible correct answers for this are quite numerous and include the following:

Richard Milhous Nixon
Richard Nixon
Richard M. Nixon
Richard M Nixon
R Milhous Nixon
R. Milhous Nixon
R. M. Nixon
R M Nixon
R.M. Nixon
RM Nixon
R Nixon
R. Nixon
Nixon

This is a fairly exhaustive list of possible correct answers. We could perhaps just have accepted "Nixon" and "Richard Nixon," but the longer list makes for a more challenging regular expression.

// define question 5
questions[4] = "Which American President was involved in the Watergate Scandal?"

// assign answer for question 5
answers[4] = "\\b((Richard |R\\.? ?)(Milhous |M\\.? )?)?Nixon\\b";

Add the question and answer code under the other questions and answers in the GlobalFunctions.htm file.

Let's analyze this regular expression now.

Expression

Description

\\b

The \\b indicates that the answer must start with a word boundary, so the answer must be a whole word and not contained inside another word. We do this just in case the user for some reason puts characters before his answer, such as "my answer is President Nixon".

((Richard |R\\.? ?)

This part of the expression in fact is grouped together with the next part, (Milhous |M\\.? )?). The first parenthesis creates the outer group. Inside this is an inner group, which can be one of two patterns. Before the | is the pattern Richard, and after it is the pattern R followed by an optional dot (.) followed by an optional space. So either Richard or R will match. Since the . is a special character in regular expressions, we have to tell JavaScript we mean a literal dot and not a special character dot. We do this by placing the \ in front. However, because we are defining this regular expression using the RegExp() constructor, we need to place an additional \ in front.

(Milhous |M\\.? )?)?

This is the second subgroup within the outer group. It works in a similar way to the first subgroup except it's Milhous rather than Richard and M rather than R that we are matching. Also, the space after the initial is not optional, since we don't want RMNixon. The second ? outside this inner group indicates that the middle name/initial is optional. The final parenthesis indicates the end of the outer group. The final ? indicates that the outer group pattern is optional—this is to allow the answer Nixon alone to be valid.

Nixon\\b

Finally the pattern Nixon must be matched followed by a word boundary.

That completes the two additional text-based questions. Now we need to alter the question creation function, getQuestion(), again inside the file GlobalFunctions.htm as follows:

function getQuestion()
{
   if (questions.length != numberOfQuestionsAsked)
   {
      var questionNumber = Math.floor(Math.random() * questions.length);
      while (questionsAsked[questionNumber] == true)
      {
         questionNumber = Math.floor(Math.random() * questions.length);
      }
      var questionLength = questions[questionNumber].length;
      var questionChoice;
      numberOfQuestionsAsked++;
      var questionHTML = "<h4>Question " + numberOfQuestionsAsked +  "</h4>";
      // Check if array or string
      if (typeof questions[questionNumber] == "string")
      {
         questionHTML = questionHTML + "<p>" + questions[questionNumber] + "</p>";
         questionHTML = questionHTML + "<p><input type=text name=txtAnswer ";
         questionHTML = questionHTML + " maxlength=100 size=35></p>"
         questionHTML = questionHTML + '<script type="text/javascript">'
                         + 'document.QuestionForm.txtAnswer.value = "";<\/script>';
      }
      else
      {
      questionHTML = questionHTML + "<p>" + questions[questionNumber][0];
      questionHTML = questionHTML + "</p>";
      for (questionChoice = 1;questionChoice < questionLength;questionChoice++)
      {
         questionHTML = questionHTML + "<input type=radio ";
         questionHTML = questionHTML + "name=radQuestionChoice";
         if (questionChoice == 1)
         {
            questionHTML = questionHTML + " checked";
         }
         questionHTML = questionHTML + ">" +
            questions[questionNumber][questionChoice];
         questionHTML = questionHTML + "<br>"
      }
      }
      questionHTML = questionHTML + "<br><input type='button' "
      questionHTML = questionHTML + "value='Answer Question'";
      questionHTML = questionHTML + "name=buttonNextQ ";
      questionHTML = questionHTML + "onclick='return buttonCheckQ_onclick()'>";
      currentQNumber = questionNumber;
      questionsAsked[questionNumber] = true;
   }
   else
   {
      questionHTML = "<h3>Quiz Complete</h3>";
      questionHTML = questionHTML + "You got " + numberOfQuestionsCorrect;
      questionHTML = questionHTML + " questions correct out of "
      questionHTML = questionHTML + numberOfQuestionsAsked;
      questionHTML = questionHTML + "<br><br>Your trivia rating is "
      switch(Math.round(((numberOfQuestionsCorrect / numberOfQuestionsAsked) *
10)))
      {
         case 0:
         case 1:
         case 2:
         case 3:
            questionHTML = questionHTML + "Beyond embarrasing";
            break;
         case 4:
         case 5:
         case 6:
         case 7:
            questionHTML = questionHTML + "Average";
            break;
         default:
            questionHTML = questionHTML + "Excellent"
      }
      questionHTML = questionHTML + "<br><br><A href='quizpage.htm'><strong>"
      questionHTML = questionHTML + "Start again</strong></A>"
   }
   return questionHTML;
}

You can see that the getQuestion() function is mostly unchanged by our need to ask text-based questions. The only real code lines that have changed are the following:

      if (typeof questions[questionNumber] == "string")
      {
         questionHTML = questionHTML + "<p>" + questions[questionNumber] + "</p>";
         questionHTML = questionHTML + "<p><input type=text name=txtAnswer ";
         questionHTML = questionHTML + " maxlength=100 size=35></P>"
         // Next line necessary due to bugs in Netscape 7.x
         questionHTML = questionHTML + '<script type="text/javascript">'
                         + 'document.QuestionForm.txtAnswer.value = "";<\/script>';
      }
      else
      {

The reason for this change is that the questions for multiple-choice and text-based questions are displayed differently. Having obtained our question number, we then need to check to see if this is a text question or a multiple-choice question. In text-based questions, we store the string containing the text inside the questions[] array; with the multiple-choice questions, we store an array inside the questions[] array, which contains the question and options. We can check to see whether the type of data stored in the questions[] array at the index for that particular question is a string type. If it's a string type, we know we have a text-based question; otherwise we assume it's a multiple-choice question. Note that Netscape 7.x has a habit of keeping previously entered data in text fields. This means when the second text-based question is asked, the answer given for the previous text question is automatically pre-entered.

We use the typeof operator as part of the condition in our if statement in the following line:

if (typeof questions[questionNumber] == "string")

If the condition is true, we then create the HTML for the text-based question; otherwise the HTML for a multiple-choice question is created.

The second function inside GlobalFunctions.htm that needs to be changed is the answerCorrect() function, which actually checks the answer given by the user.

function answerCorrect(questionNumber, answer)
{
   // declare a variable to hold return value
   var correct = false;
   // if answer provided is same as answer then correct answer is true
   var answerRegExp = new RegExp(answers[questionNumber],"i");
   if (answer.search(answerRegExp) != -1)
   {
      numberOfQuestionsCorrect++;
      correct = true;
   }
   // return whether the answer was correct (true or false)
   return correct;
}

Instead of doing a simple comparison of the user's answer to the value in the answers[] array, we're now using regular expressions.

First we create a new RegExp object called answerRegExp and initialize it to the regular expression stored as a string inside our answers[] array. We want to do a case-insensitive match, so we pass the string i as the second parameter.

In our if statement, we search for the regular expression answer pattern in the answer given by the user. This answer will be a string for a text-based question or a single character for a multiple-choice question. If a match is found, we'll get the character match position. If no match is found, -1 is returned. Therefore, if the match value is not -1, we know that the user's answer is correct, and the if statement's code executes. This increments the value of the variable numberOfQuestionsCorrect, and sets the correct variable to the value true.

That completes the changes to GlobalFunctions.htm. Remember to save the file before you close it.

Finally we have just one more function we need to alter before our changes are complete. This time the function is in the file AskQuestion.htm. The function is getAnswer(), which is used to retrieve the user's answer from the form on the page. The changes are listed below.

function getAnswer()
{
   var answer = 0;
   if (document.QuestionForm.elements[0].type == "radio")
   {
      while (document.QuestionForm.radQuestionChoice[answer].checked != true)
         answer++;
      answer =  String.fromCharCode(65 + answer);
   }
   else
   {
      answer = document.QuestionForm.txtAnswer.value;
   }
   return answer;
}

The user's answer can now be given via one of two means: choosing an option in an option group, or through text entered in a text box. We determine which way was used for this question by using the type property of the first control in the form. If the first control is a radio button, we know this is a multiple-choice question; otherwise we assume it's a text-based question.

If it is a multiple-choice question, we obtain the answer, a character, as we did before we added text questions. If it's a text-based question, it's simply a matter of getting the text value from the text control written into the form dynamically by the getQuestion() function in the GlobalFunctions.htm page.

Save the changes to the page. We're now ready to give our updated trivia quiz a test run. Load in TriviaQuiz.htm to start the quiz. You should now see the text questions displayed in Figure 8-13.

Click To expand
Figure 8-13

Although we've learned a bit more about regular expressions while altering the trivia quiz, perhaps the most important lesson has been that using general functions, and where possible placing them inside common code modules, makes later changes quite simple. In less than 20 lines, mostly in one file, we have made a significant addition to the quiz.


Team LiB
Previous Section Next Section


JavaScript Editor JavaScript Validator     JavaScript Editor


©