“1+2=3” HTML5 game built with Phaser update to Phaser 3 and commented

Talking about 1+2=3 game, Game development, HTML5, Javascript and Phaser.

If you think you are quick and clever with simple math operations, 1+2=3 will prove your skills.

Starting from this the prototype, I added some kind of story and 132 levels (yes, 132) then released Matt Vs Math, which was sponsored by Softgames.

The concept remains interesting, so I decided to update it to Phaser 3, but first have a go with the game:

Although the game is very simple, looking at the source code you will learn some key concepts like tweens, timer and sprite masking.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// the game itself
var game;
 
var gameOptions = {
 
    // maximum length of the sum
    maxSumLen: 5,
 
    // local storage name used to save high score
    localStorageName: "oneplustwo",
 
    // time allowed to answer a question, in milliseconds
    timeToAnswer: 3000,
 
    // score needed to increase difficulty
    nextLevel: 400
}
 
// once the window has been loaded...
window.onload = function() {
 
    // game configuration object
    var gameConfig = {
        width: 500,
        height: 500,
        scene: [playGame],
        backgroundColor: 0x444444
    }
 
    // creation of the game itself
    game = new Phaser.Game(gameConfig);
}
 
// "PlayGame" scene
class playGame extends Phaser.Scene{
    constructor(){
        super("PlayGame");
    }
 
    // when the scene preloads...
    preload(){
 
        // preloading images
        this.load.image("timebar", "timebar.png");
 
        // preloading a spritesheet where each sprite is 400x50 pixels
        this.load.spritesheet("buttons", "buttons.png", {
            frameWidth: 400,
            frameHeight: 50
        });
    }
 
    // when the sceen has been created...
    create(){
 
        // it's not game over yet...
        this.isGameOver = false;
 
        // current score is set to zero
        this.score = 0;
 
        // we'll also keep track of correct answers
        this.correctAnswers = 0;
 
        // topScore gets the previously saved value in local storage if any, zero otherwise
        this.topScore = localStorage.getItem(gameOptions.localStorageName) == null ? 0 : localStorage.getItem(gameOptions.localStorageName);
 
        // sumsArray is the array with all possible questions
        this.sumsArray = [];
 
        // rather than tossing a random question each time, I found easier
        // to store all possible questions in an array then draw a random question
        // each time. I just need an algorithm to generate all possible questions.
 
        // let's start building all possible questions with this loop
        // ranging from 1 (only one operator, like 1+1) to maxSumLen
        // (in this case 5, like 1+1+1+1-1-1)
        for(var i = 1; i < gameOptions.maxSumLen; i++){
 
            // defining sumsArray[i] as an array of three empty arrays
            this.sumsArray[i]=[[], [], []];
 
            // looping from 1 to 3, which are the possible results of each sum
            for(var j = 1; j <= 3; j++){
 
                // buildTrees is the core of the script, see it explained
                // some lines below
                this.buildThrees(j, 1, i, j);
            }
        }
 
        // try this! You will see all possible combinations
        console.log(this.sumsArray);
 
        // questionText is the text object which will display the question
        this.questionText = this.add.text(250 , 160, "-", {
            font: "bold 72px Arial"
        });
 
        // setting questionText registration point to its center
        this.questionText.setOrigin(0.5);
 
        // scoreText will keep the current score
        this.scoreText = this.add.text(10, 10, "-", {
            font: "bold 24px Arial"
        });
 
        // loop to create the three answer buttons
        for(i = 0; i < 3; i++){
 
            // creation of the answer button
            var numberButton = this.add.sprite(game.config.width / 2, 250 + i * 75, "buttons");
 
            // showing the i-th frame
            numberButton.setFrame(i);
 
            // setting numberButton interactive
            numberButton.setInteractive();
 
            // calling checkAnswer method if clicked/tapped
            numberButton.on("pointerdown", this.checkAnswer);
        }
 
        // adding the time bar
        var numberTimer =  this.add.sprite(game.config.width / 2, 325, "timebar");
 
        // the same image will also be used as a mask
        this.buttonMask = this.add.sprite(game.config.width / 2, numberTimer.y, "timebar");
 
        // we do not need to show the mask image
        this.buttonMask.setVisible(false);
 
        // creation of the mask itself
        var mask = this.buttonMask.createBitmapMask();
 
        // applying mask to number timer
        numberTimer.setMask(mask);
 
        // method to ask next question
        this.nextNumber();
    }
 
    // buildThrees method, it will find all possible sums
    // arguments:
    // initialNumber: the first number. Each question always start with a positive number
    // currentIndex: it's the amount of operands already placed in the sum
    // limit: the max amount of operands allowed in the question
    // currentString: the string generated so far
    buildThrees(initialNummber, currentIndex, limit, currentString){
 
        // the possible operands, from -3 to 3, excluding the zero
        var numbersArray = [-3, -2, -1, 1, 2, 3];
 
        // looping from 0 to numbersArray's length
        for(var i = 0; i < numbersArray.length; i++){
 
            // "sum" is the sum between the first number and current numberArray item
            var sum = initialNummber + numbersArray[i];
 
            // output string is generated by the concatenation of current string with
            // current numbersArray item. I am adding a "+" if the item is greater than zero,
            // otherwise it already has its "-"
            var outputString = currentString + (numbersArray[i] < 0 ? "" : "+") + numbersArray[i];
 
            // if sum is between 1 and 3 and we reached the limit of operands we want...
            if(sum > 0 && sum < 4 && currentIndex == limit){
 
                // then push the output string into sumsArray[amount of operands][result]
                this.sumsArray[limit][sum - 1].push(outputString);
            }
 
            // if the amount of operands is still below the amount we want...
            if(currentIndex < limit){
 
                // recursively calling buildThrees, passing as arguments:
                // the current sum
                // the new amount of operands
                // the amount of operands we want
                // the current output string
                this.buildThrees(sum, currentIndex + 1, limit, outputString);
            }
        }
    }
 
    // this method asks next question
    nextNumber(){
 
        // updating score text
        this.scoreText.setText("Score: " + this.score.toString() + "\nBest Score: " + this.topScore.toString());
 
        // if we already answered more than one question...
        if(this.correctAnswers > 1){
 
            // stopping time tween
            this.timeTween.stop();
 
            // resetting mask horizontal position
            this.buttonMask.x = game.config.width / 2;
        }
 
        // if we already answered at least one question...
        if(this.correctAnswers > 0){
 
            // tween to slide out the mask, unvealing what's behind it
            this.timeTween = this.tweens.add({
                targets: this.buttonMask,
                x: -150,
                duration: gameOptions.timeToAnswer,
                callbackScope: this,
                onComplete: function(){
 
                    // calling "gameOver" method. "?" is the string to display
                    this.gameOver("?");
                }
            });
        }
 
        // drawing a random result between 0 and 2 (it will be from 1 to 3)
        this.randomSum = Phaser.Math.Between(0, 2);
 
        // choosing question length according to current score
        var questionLength = Math.min(Math.floor(this.score / gameOptions.nextLevel) + 1, 4)
 
        // updating question text
        this.questionText.setText(this.sumsArray[questionLength][this.randomSum][Phaser.Math.Between(0, this.sumsArray[questionLength][this.randomSum].length - 1)]);
    }
 
    // method to check the answer, the argument is the button pressed
    checkAnswer(){
 
        // we check the answer only if it's not game over yet
        if(!this.scene.isGameOver){
 
            // button frame is equal to randomSum means the answer is correct
            if(this.frame.name == this.scene.randomSum){
 
                // score is increased according to the time spent to answer
                this.scene.score += Math.floor((this.scene.buttonMask.x + 350) / 4);
 
                // one more correct answer
                this.scene.correctAnswers++;
 
                // moving on to next question
                this.scene.nextNumber();
            }
 
            // wrong answer
            else{
 
                // if it's not the first question...
                if(this.scene.correctAnswers > 1) {
 
                    // stop the tween
                    this.scene.timeTween.stop();
                }
 
                // calling "gameOver" method. "this.frame.name + 1" is the string to display
                this.scene.gameOver(this.frame.name + 1);
            }
        }
    }
 
    // method to end the game. The argument is the string to write
    gameOver(gameOverString){
 
        // changing background color
        this.cameras.main.setBackgroundColor("#ff0000");
 
        // displaying game over text
        this.questionText.setText(this.questionText.text + " = " + gameOverString);
 
        // now it's game over
        this.isGameOver = true;
 
        // updating top score in local storage
        localStorage.setItem(gameOptions.localStorageName, Math.max(this.score, this.topScore));
 
        // restart the game after two seconds
        this.time.addEvent({
            delay: 2000,
            callback: function(){
                this.scene.start("PlayGame");
            },
            callbackScope: this
        });
    }
}

What is your best score? Have fun with the game and download the source code.