Case Study... Multiple Choice Quiz Game

The aim of this game is to answer the multiple-choice questions.

This game shows:

 

Play game

Download .zip file

 

The game data is held in a json file, as shown below. Each answer can contain text and/or an image.

multiple_choice_quiz.json.js

{
    "questions":
            [
                {
                    "question": "How many countries are there in the EU?",
                    "correct": 4,
                    "answers":
                            [
                                {
                                    "text": "17",
                                    "image": ""
                                },
                                {
                                    "text": "18",
                                    "image": ""
                                },
                                {
                                    "text": "27",
                                    "image": ""
                                },
                                {
                                    "text": "28",
                                    "image": ""
                                }
                            ]
                },
                {
                    "question": "Click on the map that highlights Poland",
                    "correct": 2,
                    "answers":
                            [
                                {
                                    "text": "",
                                    "image": "images/map_czech_republic.png"
                                },
                                {
                                    "text": "",
                                    "image": "images/map_poland.png"
                                },
                                {
                                    "text": "",
                                    "image": "images/map_belarus.png"
                                },
                                {
                                    "text": "",
                                    "image": "images/map_ukraine.png"
                                }

                            ]
                },
                {
                    "question": "Click on the Finnish flag",
                    "correct": 3,
                    "answers":
                            [
                                {
                                    "text": "",
                                    "image": "images/flag_denmark.png"
                                },
                                {
                                    "text": "",
                                    "image": "images/flag_sweden.png"
                                },
                                {
                                    "text": "",
                                    "image": "images/flag_finland.png"
                                },
                                {
                                    "text": "",
                                    "image": "images/flag_norway.png"
                                }
                            ]
                }               
            ]
}

multiple_choice_quiz_game.js

/* Author: Derek O Reilly, Dundalk Institute of Technology, Ireland.             */
/* There should always be a javaScript file with the same name as the html file. */
/* This file always holds the playGame function().                               */
/* It also holds game specific code, which will be different for each game       */





/******************** Declare game specific global data and functions *****************/
/* images must be declared as global, so that they will load before the game starts  */


/* The various gameObjects */
/* These are the positions that each gameObject is held in the gameObjects[] array */
const QUESTION = 0;
const WIN_MESSAGE = 1;
/******************* END OF Declare game specific data and functions *****************/







/* Always have a playGame() function                                     */
/* However, the content of this function will be different for each game */
function playGame()
{
    /* We need to initialise the game objects outside of the Game class */
    /* This function does this initialisation.                          */
    /* This function will:                                              */
    /* 1. create the various game game gameObjects                   */
    /* 2. store the game gameObjects in an array                     */
    /* 3. create a new Game to display the game gameObjects          */
    /* 4. start the Game                                                */


    /* Create the various gameObjects for this game. */
    /* This is game specific code. It will be different for each game, as each game will have it own gameObjects */
    // make the canvas wider for this example

    /* END OF game specific code. */

    /* Always create a game that uses the gameObject array */
    let game = null;
    
    /* The game must be loaded from the json file before it is played */
    fetch('json/multiple_choice_quiz.json').then(function (response)
    {
        return response.json();
    }).then(function (jsonData)
    {
        game = new MultipleChoiceQuizCanvasGame(jsonData);

        /* Always play the game */
        game.start();
    });




    /* If they are needed, then include any game-specific mouse and keyboard listners */
    document.getElementById("gameCanvas").addEventListener("mousedown", function (e)
    {
        if (e.which === 1)  // left mouse button
        {
            let canvasBoundingRectangle = document.getElementById("gameCanvas").getBoundingClientRect();
            let mouseX = e.clientX - canvasBoundingRectangle.left;
            let mouseY = e.clientY - canvasBoundingRectangle.top;

            game.mousedown(mouseX, mouseY);
        }
    });

    document.addEventListener("mousemove", function (e)
    {
        if (e.which === 0) // no button selected
        {
            let canvasBoundingRectangle = document.getElementById("gameCanvas").getBoundingClientRect();
            let mouseX = e.clientX - canvasBoundingRectangle.left;
            let mouseY = e.clientY - canvasBoundingRectangle.top;

            game.mousemove(mouseX, mouseY);
        }
    });
}

An AJAX fetch is done on the json file. The MultipleChoiceQuizCanvasGame game object cannot be created and the game cannot start until the json data has been fetched, as the jsonData is needed to create the MultipleChoiceQuizCanvasGame (as shown in red).

    /* The game must be loaded from the json file before it is played */
    fetch('json/multiple_choice_quiz.json').then(function (response)
    {
        return response.json();
    }).then(function (jsonData)
    {
        game = new MultipleChoiceQuizCanvasGame(jsonData);

        /* Always play the game */
        game.start();
    });

The mousedown and mousemove events are passed to the game for handling, as shown in red below.

/* If they are needed, then include any game-specific mouse and keyboard listners */
    document.getElementById("gameCanvas").addEventListener("mousedown", function (e)
    {
        if (e.which === 1)  // left mouse button
        {
            let canvasBoundingRectangle = document.getElementById("gameCanvas").getBoundingClientRect();
            let mouseX = e.clientX - canvasBoundingRectangle.left;
            let mouseY = e.clientY - canvasBoundingRectangle.top;

            game.mousedown(mouseX, mouseY);
        }
    });

    document.addEventListener("mousemove", function (e)
    {
        if (e.which === 0) // no button selected
        {
            let canvasBoundingRectangle = document.getElementById("gameCanvas").getBoundingClientRect();
            let mouseX = e.clientX - canvasBoundingRectangle.left;
            let mouseY = e.clientY - canvasBoundingRectangle.top;

            game.mousemove(mouseX, mouseY);
        }
    });

MultipleChoiceQuizCanvasGame.js

/* Author: Derek O Reilly, Dundalk Institute of Technology, Ireland.       */
/* A CanvasGame that implements collision detection.                       */

let buttons = [];

class MultipleChoiceQuizCanvasGame extends CanvasGame
{
    constructor(jsonData)
    {
        super();
        this.jsonData = jsonData;

        /* If the current question has images on the buttons, they will be stored here. */
        this.currentImage = [new Image(), new Image(), new Image(), new Image()];

        this.score = 0;
        this.currentQuestion = 0;

        /* Load the first question */
        this.loadQuestion();
    }

    render()
    {
        super.render();
        for (let i = 0; i < buttons.length; i++)
        {
            if (buttons[i].isDisplayed())
            {
                buttons[i].render();
            }
        }
    }

    mousedown(x, y)
    {
        for (let i = 0; i < buttons.length; i++)
        {
            if (buttons[i].isDisplayed())
            {
                if (buttons[i].pointIsInsideBoundingRectangle(x, y))
                {
                    if (buttons[i].isThisTheCorrectAnswer())
                    {
                        this.score++;
                    }

                    this.currentQuestion++;

                    /* Check to see if all of the questions have been completed */
                    if (this.gameIsOver())
                    {
                        for (let i = 0; i < gameObjects.length; i++) /* stop all gameObjects from animating */
                        {
                            gameObjects[i].stopAndHide();
                        }
                        for (let i = 0; i < buttons.length; i++) /* stop all buttons from animating */
                        {
                            buttons[i].stopAndHide();
                        }

                        gameObjects[WIN_MESSAGE] = new StaticText("Your score is: " + this.score, 15, 260, "Times Roman", 75, "black");
                        gameObjects[WIN_MESSAGE].start(); /* render win message */
                    }
                    else /* Load the next question */
                    {
                        this.loadQuestion();
                    }

                    return; /* There is no need to test any other items in the for-loop */
                }
            }
        }
    }

    mousemove(x, y)
    {
        for (let i = 0; i < buttons.length; i++)
        {
            if (buttons[i].isDisplayed())
            {
                buttons[i].pointIsInsideBoundingRectangle(x, y); /* Used to highlight the button that the mouse is currently hovering over */
            }
        }
    }

    gameIsOver()
    {
        /* Test for end-of-game */
        return (this.currentQuestion === this.jsonData.questions.length);
    }

    loadQuestion()
    {
        /* Load the question from the json */
        gameObjects[QUESTION] = new StaticText(this.jsonData.questions[this.currentQuestion].question, STATIC_TEXT_CENTRE, 30, "Times Roman", 25, "red");
        gameObjects[QUESTION].start();

        /* Load the buttons from the json */
        for (let i = 0; i < this.jsonData.questions[this.currentQuestion].answers.length; i++)
        {
            /* Identify the button with the correct answer */
            let isCorrect = false;
            if (this.jsonData.questions[this.currentQuestion].correct === i + 1)
            {
                isCorrect = true;
            }

            /* Buttons might or might not have an image. We need to deal with both cases */
            if (this.jsonData.questions[this.currentQuestion].answers[i].image !== "")
            {
                this.currentImage[i].src = this.jsonData.questions[this.currentQuestion].answers[i].image;

                buttons[i] = new MultipleChoiceQuizButton(isCorrect, (canvas.width * 0.5) * (i % 2), (canvas.height * 0.1) + (canvas.height * 0.45) * (Math.floor(i / 2)), canvas.width * 0.5, canvas.height * 0.45, this.jsonData.questions[this.currentQuestion].answers[i].text, this.currentImage[i]);
            }
            else
            {
                buttons[i] = new MultipleChoiceQuizButton(isCorrect, (canvas.width * 0.5) * (i % 2), (canvas.height * 0.1) + (canvas.height * 0.45) * (Math.floor(i / 2)), canvas.width * 0.5, canvas.height * 0.45, this.jsonData.questions[this.currentQuestion].answers[i].text, null);
            }

            /* Display the button */
            buttons[i].start();
        }
    }
}

the buttons[] array will hold the four buttons for the current question.

let buttons = [];

The json file that hold the game data can only contain strings. However, the Button class takes an image object rather than a string filename. Therefore, we need to store the images that are contained in the json strings into Image objects prior to creating Button objects. The array 'this.currentImage' will be used to hold the four buttons' images.

constructor(jsonData)
    {
        super();
        this.jsonData = jsonData;

        /* If the current question has images on the buttons, they will be stored here. */
        this.currentImage = [new Image(), new Image(), new Image(), new Image()];

        this.score = 0;
        this.currentQuestion = 0;

        /* Load the first question */
        this.loadQuestion();
    }

This game extend from the class CanvasGame. The render() method will call super.render() to render the gameObjects[] that are contained in the game.
The game-specific button objects are then rendered.

    render()
    {
        super.render();
        
        for (let i = 0; i < buttons.length; i++)
        {
            if (buttons[i].isDisplayed())
            {
                buttons[i].render();
            }
        }
    }

The mousedown() method steps through each button and checks if it has been clicked.
If the button contains the correct answer, then the score is incremented (shown in green).
The current question is incremented (shown in blue).
A test is then done to see if the game is over. If the game is over, then the score is displayed. Otherwise, the next question is loaded (shown in red).

mousedown(x, y)
    {
        for (let i = 0; i < buttons.length; i++)
        {
            if (buttons[i].isDisplayed())
            {
                if (buttons[i].pointIsInsideBoundingRectangle(x, y))
                {
                    if (buttons[i].isThisTheCorrectAnswer())
                    {
                        this.score++;
                    }

                    this.currentQuestion++;

                    /* Check to see if all of the questions have been completed */
                    if (this.gameIsOver())
                    {
                        for (let i = 0; i < gameObjects.length; i++) /* stop all gameObjects from animating */
                        {
                            gameObjects[i].stopAndHide();
                        }
                        for (let i = 0; i < buttons.length; i++) /* stop all buttons from animating */
                        {
                            buttons[i].stopAndHide();
                        }

                        gameObjects[WIN_MESSAGE] = new StaticText("Your score is: " + this.score, 15, 260, "Times Roman", 75, "black");
                        gameObjects[WIN_MESSAGE].start(); /* render win message */
                    }
                    else /* Load the next question */
                    {
                        this.loadQuestion();
                    }

                    return; /* There is no need to test any other items in the for-loop */
                }
            }
        }
    }

The mousemove highlights the button that the mouse is currently hovering over

    mousemove(x, y)
    {
        for (let i = 0; i < buttons.length; i++)
        {
            if (buttons[i].isDisplayed())
            {
                buttons[i].pointIsInsideBoundingRectangle(x, y); /* Used to highlight the button that the mouse is currently hovering over */
            }
        }
    }



MultipleChoiceQuizButton.js

/* Author: Derek O Reilly, Dundalk Institute of Technology, Ireland. */



class MultipleChoiceQuizButton extends Button
{
    /* Each gameObject MUST have a constructor() and a render() method.        */
    /* If the object animates, then it must also have an updateState() method. */

    constructor(isCorrectAnswer, x, y, width, height, text, backgroundImage)
    {
        super(x, y, width, height, text, true, backgroundImage, 20, "Times Roman", "Black", "#ee6", "#999", 5);

        /* These variables depend on the object */
        this.isCorrectAnswer = isCorrectAnswer;
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.text = text;
        this.backgroundImage = backgroundImage;
    }

    isThisTheCorrectAnswer()
    {
        return this.isCorrectAnswer;
    }
}

The game is over if all of the questions from the json file have been completed.

    gameIsOver()
    {
        /* Test for end-of-game */
        return (this.currentQuestion === this.jsonData.questions.length);
    }

The loadQuestion() method displays the 'this.currentQuestion' from jsonData. Each question consists of a question and four answer buttons.
The question is placed in gameObjects[QUESTION] (shown in blue)
The four answers are placed in four buttons. When creating the buttons, we need to account for the fact that an answer might or might not contain an image (as shown in green). If a button does contain an image, then the json file will hold the image's name. The MultipleChoiceQuizButton class requires an image rather than an image filename. Therefore, we need to create an Image() object to hold the image, so that we can create a new MultipleChoiceQuizButton object. This is shown in red.

    loadQuestion()
    {
        /* Load the question from the json */
        gameObjects[QUESTION] = new StaticText(this.jsonData.questions[this.currentQuestion].question, STATIC_TEXT_CENTRE, 30, "Times Roman", 25, "red");
        gameObjects[QUESTION].start();

        /* Load the buttons from the json */
        for (let i = 0; i < this.jsonData.questions[this.currentQuestion].answers.length; i++)
        {
            /* Identify the button with the correct answer */
            let isCorrect = false;
            if (this.jsonData.questions[this.currentQuestion].correct === i + 1)
            {
                isCorrect = true;
            }

            /* Buttons might or might not have an image. We need to deal with both cases */
            if (this.jsonData.questions[this.currentQuestion].answers[i].image !== "")
            {
                this.currentImage[i].src = this.jsonData.questions[this.currentQuestion].answers[i].image;

                buttons[i] = new MultipleChoiceQuizButton(isCorrect, (canvas.width * 0.5) * (i % 2), (canvas.height * 0.1) + (canvas.height * 0.45) * (Math.floor(i / 2)), canvas.width * 0.5, canvas.height * 0.45, this.jsonData.questions[this.currentQuestion].answers[i].text, this.currentImage[i]);
            }
            else
            {
                buttons[i] = new MultipleChoiceQuizButton(isCorrect, (canvas.width * 0.5) * (i % 2), (canvas.height * 0.1) + (canvas.height * 0.45) * (Math.floor(i / 2)), canvas.width * 0.5, canvas.height * 0.45, this.jsonData.questions[this.currentQuestion].answers[i].text, null);
            }

            /* Display the button */
            buttons[i].start();
        }
    }