Get the full commented source code of

HTML5 Suika Watermelon Game

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

If you enjoyed the HTML5 SameGame example I showed you last week, here is the second part with animations and a deeply commented source code. Three different animations have been featured: * The fade-out animation when tiles are removed, using a tween on alpha property. * The animation of tiles falling down, using a tween on y property * The animation of tiles scrolling to the left, using a tween on x property with a bounce easing. Adding animations made the game more polished, as you can see.
And here is the complete source code, as promised:
// the game itself
var game;

// this object contains all customizable game options
// changing them will affect gameplay
var gameOptions = {
	gameWidth: 800,    // game width, in pixels 
	gameHeight: 800,   // game height, in pixels
	tileSize: 100,     // tile size, in pixels 
	fieldSize: {       // field size, an object
          rows: 8,      // rows in the field, in units
          cols: 8       // columns in the field, in units
     },
     colors: [0xff0000, 0x00ff00, 0x0000ff, 0xffff00] // tile colors
}

// 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 = 0x222222;
          
          // 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(){
     
          // canPick tells if we can pick a tile, we start with "true" has at the moment a tile can be picked
          this.canPick = true;   
          
          // tiles are saved in an array called tilesArray
          this.tilesArray = [];
          
          // this group will contain all tiles
		this.tileGroup = game.add.group();
          
          // we are centering the group, both horizontally and vertically, in the canvas
          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);
			}
		}
	},
     
     // 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 a button which will call "pickTile" function if triggered
          var theTile = game.add.button(tileXPos, tileYPos, "tiles", this.pickTile, this);
		
          // 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
          theTile.value = game.rnd.integerInRange(0, gameOptions.colors.length - 1);
          
          // tinting the tile
          theTile.tint = gameOptions.colors[theTile.value];
          
          // saving tile coordinate as a Point, to quickly have access to this information
          theTile.coordinate = new Phaser.Point(col, row);
          
          // adding the button to "tilesArray" array
          this.tilesArray[row][col] = theTile;
          
          // also adding it to "tileGroup" group
	     this.tileGroup.add(theTile);	
	},
     
     // this function is executed each time a tile is selected
     pickTile: function(e){
     
          // can the player pick a tile?
          if(this.canPick){
          
               // the most secure way to have a clean and empty array
               this.filled = [];
               this.filled.length = 0;
               
               // performing a flood fill on the selected tile
               // this will populate "filled" array
               this.floodFill(e.coordinate, e.value);
               
               // do we have more than one tile in the array?
               if(this.filled.length > 1){
               
                    // ok, this is a valid move and player won't be able to pick another tile until all animations have been played
                    this.canPick = false;
                    
                    // function to destroy selected tiles
                    this.destroyTiles();
               }
          }
     },
     
     // this function will destroy all tiles we can find in "filled" array
     destroyTiles: function(){      
     
          // looping through the array
          for(var i = 0; i < this.filled.length; i++){
          
               // fading tile out with a tween
               var tween = game.add.tween(this.tilesArray[this.filled[i].y][this.filled[i].x]).to({
                    alpha: 0
               }, 300, Phaser.Easing.Linear.None, true);
               
               // once the tween has been completed...
               tween.onComplete.add(function(e){ 
               
                    // remove the tile 
                    e.destroy();  
                    
                    // we don't know how many tiles we have already removed, so counting the tweens
                    // currently in use is a good way, at the moment
                    // if this was the last tween (we only have one tween running, this one) 
                    if(tween.manager.getAll().length == 1){
                    
                         // call fillVerticalHoles function to make tiles fall down
                         this.fillVerticalHoles();     
                    }                                
               }, this);
               
               // tilesArray item is set to null, this means it's empty now
               this.tilesArray[this.filled[i].y][this.filled[i].x] = null;
          }
     },
     
     // this function will make tiles fall down
     fillVerticalHoles: function(){
     
          // filled is a variable which tells us if we filled a hole
          var filled = false;
          
          // looping through the entire gamefield
          for(var i = gameOptions.fieldSize.rows - 2; i >= 0; i--){
               for(var j = 0; j < gameOptions.fieldSize.cols; j++){
               
                    // if we have a tile...
                    if(this.tilesArray[i][j] != null){
                    
                         // let's count how many holes we can find below this tile
                         var holesBelow = 0;
                         for(var z = i + 1; z < gameOptions.fieldSize.rows; z++){
                              if(this.tilesArray[z][j] == null){
                                   holesBelow ++;
                              }    
                         }
                         
                         // if holesBelow is greater than zero...
                         if(holesBelow){  
                         
                              // we filled a hole, or at least we are about to do it
                              filled = true;
                              
                              // function to move down a tile at column "j" from "i" to "i + holesBelow" row
                              this.moveDownTile(i, j, i + holesBelow);                                                                   
                         }
                    }     
               }
          }
          
          // if we looped trough all tiles but did not fill anything...
          if(!filled){
          
               // let's see if there are horizontal holes to fill
               this.fillHorizontalHoles();      
          }
     },
     
     // function to move down a tile
     moveDownTile: function(fromRow, fromCol, toRow){
     
          // adjusting tilesArray items actually copying the tile to move in the new position...
          this.tilesArray[toRow][fromCol] = this.tilesArray[fromRow][fromCol];
          
          // ... and updating the coordinate
          this.tilesArray[toRow][fromCol].coordinate = new Phaser.Point(fromCol, toRow);
          
          // a tween manages the movement
          var tween = game.add.tween(this.tilesArray[toRow][fromCol]).to({
               y: toRow * gameOptions.tileSize + gameOptions.tileSize / 2         
          }, 250, Phaser.Easing.Linear.None, true);
          
          // same thing as before to see how many tweens remain alive, and if this is the last
          // active tween, call "fillHorizontalHoles" function
          tween.onComplete.add(function(){
               if(tween.manager.getAll().length == 1){
                    this.fillHorizontalHoles();
               }
          }, this)
          
          // the old place now is set to null
          this.tilesArray[fromRow][fromCol] = null;
     },
     
     // this function will make tiles slide to the left
     fillHorizontalHoles: function(){
     
          // we did not fill anything at the moment
          var filled = false
          
          // looping though all columns
          for(i = 0; i < gameOptions.fieldSize.cols - 1; i++){
               
               // if this is an empty column (we can see it thanks to tilesInColumn function)
               if(this.tilesInColumn(i) == 0){
               
                    // looping through the columns at its right...
                    for(j = i + 1; j < gameOptions.fieldSize.cols; j++){
                    
                         // ...until we find a non-empty column
                         if(this.tilesInColumn(j) != 0){
                         
                              // at this time all tiles in this columns must be moved to the left
                              for(z = 0; z < gameOptions.fieldSize.rows; z++){
                                   if(this.tilesArray[z][j] != null){
                                   
                                        // we filled!!
                                        filled = true;
                                        
                                        // function to move left a tile at row "z" from "j" to "i" column
                                        this.moveLeftTile(z, j, i)
                                   }    
                              }
                              break;
                         }     
                    }
               }
          }
          
          // if we did not fill anything...
          if(!filled){
          
               // the move is over and the player can pick another tile
               this.canPick = true;     
          }
     },
     
     // this is basically the same concept seen at "moveDownTile", we just move tiles left with a bounce effect
     moveLeftTile: function(fromRow, fromCol, toCol){
          this.tilesArray[fromRow][toCol] = this.tilesArray[fromRow][fromCol];
          this.tilesArray[fromRow][toCol].coordinate = new Phaser.Point(toCol, fromRow);
          var tween = game.add.tween(this.tilesArray[fromRow][toCol]).to({
               x: toCol * gameOptions.tileSize + gameOptions.tileSize / 2  
          }, 500, Phaser.Easing.Bounce.Out, true);
          tween.onComplete.add(function(){
               if(tween.manager.getAll().length == 1){
                    this.canPick = true;     
               }
          }, this)
          this.tilesArray[fromRow][fromCol] = null;
     },
     
     // function which counts tiles in a column
     tilesInColumn: function(col){
          var result = 0;
          for(var i = 0; i < gameOptions.fieldSize.rows; i++){
               if(this.tilesArray[i][col] != null){ 
                    result ++;
               }
          }
          return result;
     },
     
     // flood fill function, for more information
     // http://emanueleferonato.com/2008/06/06/flash-flood-fill-implementation/
     floodFill: function(p, n){
          if(p.x < 0 || p.y < 0 || p.x >= gameOptions.fieldSize.cols || p.y >= gameOptions.fieldSize.rows){
               return;
          }
          if(this.tilesArray[p.y][p.x] != null && this.tilesArray[p.y][p.x].value == n && !this.pointInArray(p)){
               this.filled.push(p);
               this.floodFill(new Phaser.Point(p.x + 1, p.y), n);
               this.floodFill(new Phaser.Point(p.x - 1, p.y), n); 
               this.floodFill(new Phaser.Point(p.x, p.y + 1), n);
               this.floodFill(new Phaser.Point(p.x, p.y - 1), n);       
          }
     },
     
     // there isn't a built-in javascript method to see if an array contains a point, so here it is.
     pointInArray: function(p){
          for(var i = 0; i < this.filled.length; i++){
               if(this.filled[i].x == p.x && this.filled[i].y == p.y){
                    return true;
               }
          }
          return false;
     }
}
You can also download the full source code of the example, let me know if you can create a full SameGame clone out of it.

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