Get the full commented source code of

HTML5 Suika Watermelon Game

Talking about zNumbers game, Game development, HTML5, Javascript and Phaser.

I am sure you don’t know the clever puzzle game zNumbers by Karl Bartel. It was originally published for the Sharp Zaurus and no, don’t ask me what it is. You play in a 6×6 grid, filled with numbers from 1 to 4. Every tile with a number can be moved horizontally, vertically or diagonally. The number on the block indicates how far the block must be moved. Other blocks between the start and destination don’t prevent a movement, but the destination field must be free. All possible destinations are highlighted when you select a block. Way easier to play than to explain, so here it is the first level:
Tap/touch a number then tap/touch a destination. Will you be able to move all numbers once? The game has also been published by Lightforce games several years ago. If you have a mobile device, you can play directly at this link. The original game features 10 levels and I will publish an update with all levels, but above all I would like to create randomly generated levels. Here it is the source code, completely commented:
// the game itself
var game;

// levels. Actually, just one.
var levels = [
    [
        [0, 0, 0, 0, 0, 0],
        [3, 2, 3, 2, 2, 2],
        [0, 0, 2, 3, 2, 2],
        [2, 0, 2, 2, 0, 0],
        [0, 2, 3, 0, 2, 2],
        [2, 3, 0, 2, 0, 4]
    ]
];

// this object contains all customizable game options
// changing them will affect gameplay
var gameOptions = {

    // game width, in pixels
	gameWidth: 700,

    // game height, in pixels
	gameHeight: 700,

     // tile size, in pixels
    tileSize: 100,

    // field size, an object
	fieldSize: {

        // rows in the field, in units
        rows: 6,

        // columns in the field, in units
        cols: 6
    },

    // tile colors
    colors: [0x999999, 0xffcb97, 0xffaeae, 0xa8ffa8, 0x9fcfff],

    // array with various directions, you can make the game harder if you only use the first 4 (up, down, left, right)
    directions: [
        new Phaser.Point(0, 1),
        new Phaser.Point(0, -1),
        new Phaser.Point(1, 0),
        new Phaser.Point(-1, 0),
        new Phaser.Point(1, 1),
        new Phaser.Point(-1, -1),
        new Phaser.Point(1, -1),
        new Phaser.Point(-1, 1)
    ]
}

// function to be execute once the page loads
window.onload = function() {

    // creation of a new Phaser Game
	game = new Phaser.Game(gameOptions.gameWidth, gameOptions.gameHeight);

    // adding "TheGame" state
    game.state.add("TheGame", TheGame);

    // launching "TheGame" state
    game.state.start("TheGame");
}

/* ****************** TheGame state ****************** */

var TheGame = function(){};

TheGame.prototype = {

    // function to be executed when the game preloads
    preload: function(){

        // setting background color to dark grey
        game.stage.backgroundColor = 0xf5f5f5;

        // load the only graphic asset in the game, a white tile which will be tinted on the fly
        game.load.image("tiles", "assets/sprites/tile.png");
    },

    // function to be executed as soon as the game has completely loaded
  	create: function(){

        // scaling the game to cover the entire screen, while keeping its ratio
        game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;

        // horizontally centering the game
		game.scale.pageAlignHorizontally = true;

        // vertically centering the game
		game.scale.pageAlignVertically = true;

        // this function will create the level
  		this.createLevel();
  	},

	createLevel: function(){

        //  current level
        this.level = 0;

        // tiles are saved in an array called tilesArray, so we can update tile layout without losing the initial configuration
        this.tilesArray = [];

        // this group will contain all tiles
		this.tileGroup = game.add.group();

        // we are centering the group horizontally and keeping the same margin from the bottom
        this.tileGroup.x = (game.width - gameOptions.tileSize * gameOptions.fieldSize.cols) / 2;
        this.tileGroup.y = (game.height - gameOptions.tileSize * gameOptions.fieldSize.rows) / 2;

        // two loops to create a grid made by "gameOptions.fieldSize.rows" x "gameOptions.fieldSize.cols" columns
  		for(var i = 0; i < gameOptions.fieldSize.rows; i++){
            this.tilesArray[i] = [];
			for(var j = 0; j < gameOptions.fieldSize.cols; j++){

                // this function adds a tile at row "i" and column "j"
				this.addTile(i, j);
			}
		}

        // waiting for user input
        game.input.onDown.add(this.pickTile, this);
	},

    // function to add a tile at "row" row and "col" column
	addTile: function(row, col){

        // determining x and y tile position according to tile size
		var tileXPos = col * gameOptions.tileSize + gameOptions.tileSize / 2;
		var tileYPos = row * gameOptions.tileSize + gameOptions.tileSize / 2;

        // tile is added as an image
        var theTile = game.add.sprite(tileXPos, tileYPos, "tiles");

        // setting tile registration point to its center
        theTile.anchor.set(0.5);

        // adjusting tile width and height according to tile size
        theTile.width = gameOptions.tileSize;
        theTile.height = gameOptions.tileSize;

        // time to assign the tile a random value, which is also a random color
        var tileValue = levels[this.level][row][col];

        // tinting the tile
        theTile.tint = gameOptions.colors[tileValue];

        // adding tile number
        var tileText = game.add.text(0, 0, tileValue.toString(), {
            font: (gameOptions.tileSize / 2).toString() + "px Arial",
            fontWeight: "bold"
        });

        // setting tile number registration point to the center
        tileText.anchor.set(0.5);

        tileText.alpha = (tileValue > 0) ? 0.5 : 0

        // now tile number is a child of tile itself
        theTile.addChild(tileText);

        // adding the image to "tilesArray" array, creating an object
        this.tilesArray[row][col] = {

             // tile image
            tileSprite: theTile,

            // the value of the tile
            value: tileValue,

            // the text of the tile
            text: tileText
        };

        // also adding it to "tileGroup" group
	    this.tileGroup.add(theTile);

	},

    // this function is executed at each user input (click or touch)
    pickTile: function(e){

        // method to reset all tile tweens
        this.resetTileTweens();

        // determining x and y position of the input inside tileGroup
        var posX = e.x - this.tileGroup.x;
        var posY = e.y - this.tileGroup.y;

        // transforming coordinates into actual rows and columns
        var pickedRow = Math.floor(posY / gameOptions.tileSize);
        var pickedCol = Math.floor(posX / gameOptions.tileSize);

        // checking if row and column are inside the actual game field
        if(pickedRow >= 0 && pickedCol >= 0 && pickedRow < gameOptions.fieldSize.rows && pickedCol < gameOptions.fieldSize.cols){

            // this is the tile we picked
            var pickedTile = this.tilesArray[pickedRow][pickedCol];

            // getting tile value
            var pickedValue = pickedTile.value;

            // if it's a legal tile...
            if(pickedValue > 0){

                // saving picked tile coordinate
                this.saveTile = new Phaser.Point(pickedRow, pickedCol);

                // here we will place the possible landing tiles, if ones
                this.possibleLanding = [];
                this.possibleLanding.length = 0;

                // tween the tile
                this.setTileTweens(pickedTile.tileSprite);

                // looping through all directions
                for(var i = 0; i < gameOptions.directions.length; i++){

                    // determining new coordinates
                    var newRow = pickedRow + pickedValue * gameOptions.directions[i].x;
                    var newCol = pickedCol + pickedValue * gameOptions.directions[i].y;

                    // are we on a legal tile?
                    if(newRow < gameOptions.fieldSize.rows && newRow >= 0 && newCol < gameOptions.fieldSize.cols && newCol >=0 && this.tilesArray[newRow][newCol].value == 0){

                        // we tween the tile
                        this.setTileTweens(this.tilesArray[newRow][newCol].tileSprite);

                        // this tile is a possible landing tile
                        this.possibleLanding.push(new Phaser.Point(newRow, newCol));
                    }
                }
            }
            // it's not a legal tile. Maybe a possible landing?
            else{

                // check if the picked tile is in the array of possible landings
                if(this.pointInArray(new Phaser.Point(pickedRow, pickedCol))){

                    // this tile can't be picked anymore
                    this.tilesArray[pickedRow][pickedCol].value = -1;

                    // showing tile text
                    this.tilesArray[pickedRow][pickedCol].text.alpha = 0.5;

                    // setting destination tile text as source tile value
                    this.tilesArray[pickedRow][pickedCol].text.text = this.tilesArray[this.saveTile.x][this.saveTile.y].value.toString();

                    // empty source tile
                    this.tilesArray[this.saveTile.x][this.saveTile.y].value = 0;

                    // change source tile color
                    this.tilesArray[this.saveTile.x][this.saveTile.y].tileSprite.tint = gameOptions.colors[0];

                    // hiding tile text
                    this.tilesArray[this.saveTile.x][this.saveTile.y].text.alpha = 0;
                }

                // empty possibleLanding array
                this.possibleLanding = [];
                this.possibleLanding.length = 0;
            }
        }
    },

    // defines the tile tween
    setTileTweens: function(tile){

        // defining a pulse tween which changes width and height in 200 milliseconds, with a continuous loop and with a yoyo effect
        this.pulseTween = game.add.tween(tile).to({
            width: gameOptions.tileSize * 0.8,
            height: gameOptions.tileSize * 0.8
        }, 200, Phaser.Easing.Cubic.InOut, true, 0, -1, true);
    },

    // this function reset all tile tweens
    resetTileTweens: function(){

        // get all running tweens
        var activeTweens = game.tweens.getAll();

        // looping through all running tweens
        for(var i = 0; i < activeTweens.length; i++){

            // set tile width and height back to original size
            activeTweens[i].target.width = gameOptions.tileSize;
            activeTweens[i].target.height = gameOptions.tileSize;
        }

        // remove all tweens
        game.tweens.removeAll();
    },

    // checks if a point is in possibleLanding array
    pointInArray: function(p){

        // looping through possibleLanding
        for(var i = 0; i < this.possibleLanding.length; i++){

            // do we find the point at i-th element?
            if(this.possibleLanding[i].x == p.x && this.possibleLanding[i].y == p.y){

                // yes we found it
                return true;
            }
        }

        // p is not inside possibleLanding
        return false;
    }
}
You can add more levels, animations and an undo feature on your own, downloading the source code and playing with it.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.