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

Read all posts about "" game

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.

// 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.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers