Table of Contents

9.14 The Multiple-Choice Quiz Revisited

In Example 1-1, we introduced new programmers to a simple scripted movie—a multiple-choice quiz. Let's revisit that quiz now to see how we can centralize its code.

Our new quiz's layer structure is set up exactly as before. We'll be changing the code on only the first frame, the quizEnd frame, and the buttons.

You can obtain the .fla file for both the original and revised versions of the quiz from the online Code Depot.

9.14.1 Organizing the Quiz Code into Functions

In our first attempt at creating a multiple-choice quiz, we scattered our code around our movie. We placed the logic of our quiz in three places: on the first frame, where we initialized our quiz; on the q2 frame, where we assigned Question 2's button event handlers; and on the quizEnd frame, where we tallied the user's score. We're now going to centralize the quiz logic by performing all of those tasks in the first frame. To initialize the quiz, we'll still use a simple series of statements as we did in the first version, but to track the user's answers and tally the user's score, we'll use two functions, answer( ) and gradeUser( ). We'll also use function literals to define the behavior of the quiz answer buttons.

Example 9-13 shows the code on our quiz's first frame. This is where we'll store all the logic needed to run our quiz. Take a look at the code in its entirety—then we'll dissect it. This code does not yet reflect best practices, such as using arrays to store a series of related data items. Much of the code is still only intended for illustration purposes—in this case, to learn how to centralize code using functions. We'll refine the example further later in the book.

Example 9-13. A multiple-choice quiz, version 2
//  Stop the movie at the first question
stop( );
   
//  Init quiz variables
var quizTimeline = this;     // Store a reference to the timeline that
                             // contains the quiz user interface
var numQuestions = 2;        // Number of questions in the quiz
var q1answer;                // User's answer for question 1
var q2answer;                // User's answer for question 2
var correctAnswer1 = 3;      // The correct choice for question 1
var correctAnswer2 = 2;      // The correct choice for question 2
answer.currentQuestion = 0;  // Function property: the question being answered
   
//   Function: answer( )
//       Desc: Registers the user's answers to quiz questions and
//             advances the quiz through its questions
//     Params: choice           The user's answer for the current question
// Properties: currentQuestion  The question being answered
function answer (choice) {
  // The answer( ) function is called once per question. We add one to 
  // currentQuestion each time answer( ) is called so we can track the 
  // question number we're on. Because currentQuestion is a function property,
  // it maintains its value between function calls.
  answer.currentQuestion++;
   
  // Display the question and answer in the Output window for debugging.
  trace("Question " + answer.currentQuestion 
         + ". The user answered: " + choice + ".");
   
  // Record the user's answer to this question. The set( )
  // function dynamically constructs a variable name in the
  // series q1answer, q2answer,...qnanswer.
  set("q" + answer.currentQuestion + "answer", choice);
   
  // If we're on the last question...
  if (answer.currentQuestion =  = numQuestions) {
    // ...go to the quizEnd frame.
    quizTimeline.gotoAndStop("quizEnd");
    // And grade the user.
    gradeUser( );
  } else {
    // ...otherwise, go to the next question frame.
    quizTimeline.gotoAndStop("q"+ (answer.currentQuestion + 1));
  }
}
   
//   Function: gradeUser( )
//       Desc: Tallies the user's score at the end of the quiz.
function gradeUser ( ) {
  // Report that we're about to grade the quiz in the Output window.
  trace("Quiz complete. Now grading...");
   
  // Create a local variable to track the 
  // number of questions user answered correctly.
  var totalCorrect = 0;      
   
  // Count how many questions the user answered correctly.
  // For each question...
  for (var i = 1; i <= numQuestions; i++) {
    // If the user's answer matches the correct answer.
    if (eval("q" + i + "answer") =  = eval("correctAnswer" + i)) {
      // Give the user a point.
      totalCorrect++;
    }
    // Display the correct answer and the user's answer
    // in the Output window for debugging.
    trace("Question " + i 
         + ". Correct answer: " + eval("correctAnswer" + i)
         + ". User's answer: "  + eval("q" + i + "answer"));
  }
   
  // Display the final score in the Output window for debugging.
  trace("User's score: " + totalCorrect + "/" + numQuestions);
   
  // Create an onscreen text field to display the user's score.
  quizTimeline.createTextField("totalOutput_txt", 1, 150, 200, 200, 20);
   
  // Show the user's score in an onscreen text field.
  quizTimeline.totalOutput_txt.text = "Your final score is: " 
                                    + totalCorrect + "/" + numQuestions;
   
  // Customize the font face, size, and color for the text field.
  var formatObj = new TextFormat( );
  formatObj.size = 16;
  formatObj.color = 0xC7FF9C;
  formatObj.font = "_sans";
  formatObj.bold = true;
  quizTimeline.totalOutput_txt.setTextFormat(formatObj);
}
   
// Code executed when button 1 is pressed.
choice1_btn.onRelease = function ( ) {
  // Call answer( ), which records the user's choice (supplied as an argument)
  // and advances the quiz to the next question.
  this._parent.answer(1);
};
   
// Code executed when button 2 is pressed.
choice2_btn.onRelease = function ( ) {
  this._parent.answer(2);
};
   
// Code executed when button 3 is pressed.
choice3_btn.onRelease = function ( ) {
  this._parent.answer(3);
};

Our first chore is to stop the movie, using the stop( ) function, so that it doesn't play through all the questions. Next, we initialize our quiz's timeline variables. You'll recognize q1answer and q2answer from the first version of the quiz. We've also added:

With our variables initialized, we can create the answer( ) function, which records the user's answers and advances the playhead to the next question. The answer( ) function expects one parameter—choice, which is the number of the user's answer for each question—so its function declaration begins like this:

function answer (choice) {

Each time an answer is given, the function increments currentQuestion, a function property that tracks the question being answered:

answer.currentQuestion++;

Next, we set the user's choice in a dynamically named timeline variable that corresponds to the question being answered. We use the value of the currentQuestion property to determine the name of our timeline variable—q1answer, q2answer, etc. (in a later version of the quiz, we'll use a more elegant array):

set ("q" + answer.currentQuestion + "answer", choice);

With the user's choice stored in the appropriate variable, we need to decide what to show the user next:

That takes care of our question-answering logic. The answer( ) function is ready to handle answers from any question in the quiz. Now let's build the function that evaluates those answers: gradeUser( ).

The gradeUser( ) function takes no parameters. It has to compare each user answer with each correct answer and display the user's score. To tally the user's score, we use the variable totalCorrect, which was a timeline variable in the first version of the quiz but is more appropriately a local variable in this version (totalCorrect is used only by the gradeUser( ) function, so it is best kept private to that function).

We compare the user's answers with the correct answers in a for loop—we cycle through the loop body once for each question in the quiz:

for (i = 1; i <= numQuestions; i++) {

Inside the loop, a comparison expression tests the user's answers against the correct answers. Using eval( ), we dynamically retrieve the value of each user-answer variable and each correct-answer variable. (In a later version of the quiz, we'll get rid of the eval( ) statement by using an array.) If the two variables are equal, totalCorrect is incremented:

if (eval("q" + i + "answer") =  = eval("correctAnswer" + i)) {
  totalCorrect++;
}

When the loop finishes, totalCorrect will contain the number of questions that the user answered correctly. We display that number in a dynamically created text field:

quizTimeline.createTextField("totalOutput_txt", 1, 150, 200, 200, 20);

where totalOutput_txt is the name of the text field, 1 is the field's depth in quizTimeline's content stack, 150 and 200 are the field's (x, y) position, and 200 and 20 are its height and width. To display the value of totalCorrect in the text field, we assign it to the text property:

quizTimeline.totalOutput_txt.text = "Your final score is: " 
                                  + totalCorrect + "/" + numQuestions;

Finally, we apply custom formatting to the text field. See the TextFormat class in the Language Reference for details.

var formatObj = new TextFormat( );
formatObj.size = 16;
formatObj.color = 0xC7FF9C;
formatObj.font = "_sans";
formatObj.bold = true;
quizTimeline.totalOutput_txt.setTextFormat(formatObj);

Voilà! With both answer( ) and gradeUser( ) created and our quiz variables initialized, the logic of our quiz system is complete. Notice how much easier it is to follow the quiz's operation when most of the code is contained in a single frame. All that's left is to call the answer( ) function from the appropriate buttons in the quiz.

9.14.1.1 Calling the answer( ) function

We'll call the answer( ) function from the buttons that the user clicks to indicate answers. Here's the code for the "Answer 1" button:

choice1_btn.onRelease = function ( ) {
  this._parent.answer(1);
};

This code assigns a function literal to the choice1_btn's onRelease( ) handler, which is invoked automatically when the button is pressed. The function literal contains a single statement that says, "On the movie clip timeline that contains this button, this._parent, invoke answer( ), passing the user's choice for this question (1) as an argument: this._parent.answer(1);"

Notice that the choice1_btn handler applies to any question, so we don't have to assign new button handlers for each question (as we did in the first quiz). The button's job is simply to tell the answer( ) function which choice the user made. The answer( ) function keeps track of which question the user is answering, so it records the choice in the appropriate variable.

With the code needed to handle each button neatly packed into the answer( ) function, our button code is kept to a bare minimum. Furthermore, creating new buttons is a simple matter of passing the correct argument to answer( ). To add a fourth choice button, for example, we'd use:

choice4_btn.onRelease = function ( ) {
  this._parent.answer(4);
};

While that's pretty flexible, we're still duplicating the single statement of the function literal. In the object-oriented version of the quiz, we'll see how to rid our code of this redundancy.

9.14.2 Quiz Summary

By placing all our code on the first frame of our movie, we've separated the display of the system from the logic of the system. Hence, we don't lose the logic in the maze of frames and buttons that Flash movies can often become.

Test your quiz to make sure that it works. Experiment to see what happens when you add quiz questions, remove quiz questions, and change anything else that comes to mind. As we continue, think about how the techniques you learn might be applied to the quiz. What visible features can we add to the user experience? What invisible changes will make the code more flexible or elegant, even if the user experience is unchanged? Can you alter the quiz to allow more than one correct answer for a question?


Table of Contents