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.
// 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.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.