HTML5 “2048” game made with Phaser updated to 2.4.6

Read all posts about "" game
About two years ago I published my first complete prototype of a game made with Phaser, in the post How to create a complete HTML5 “2048” game with Phaser. I was using Phaser 2 at the time, while now we have 2.4.6 so I thought it was time to update the prototype. You know the game, the famous 2048
Play with WASD keys and try to get to 2048. Here is the updated and commented source code:
var game;

// tile width, in pixels
var tileSize = 100;

// colors to tint tiles according to their value
var colors = {
	2:0xFFFFFF,
	4:0xFFEEEE,
	8:0xFFDDDD,
	16:0xFFCCCC,
	32:0xFFBBBB,
	64:0xFFAAAA,
	128:0xFF9999,
	256:0xFF8888,
	512:0xFF7777,
	1024:0xFF6666,
	2048:0xFF5555,
	4096:0xFF4444,
	8192:0xFF3333,
	16384:0xFF2222,
	32768:0xFF1111,
	65536:0xFF0000
}


window.onload = function() {	
	game = new Phaser.Game(tileSize * 4, tileSize * 4, Phaser.AUTO, "");
     game.state.add("PlayGame",playGame);
     game.state.start("PlayGame");
}

var playGame = function(game){};

playGame.prototype = {
     preload: function(){
          // preload the only image we are using in the game
	    game.load.image("tile", "tile.png");
     },
     create: function(){
          // SCALING AND ALIGNING THE GAME
          game.scale.pageAlignHorizontally = true;
		game.scale.pageAlignVertically = true;
          game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
          // game array, starts with all cells to zero
          this.fieldArray = new Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
          // variables to handle keyboard input
          this.upKey = game.input.keyboard.addKey(Phaser.Keyboard.W);
		this.upKey.onDown.add(this.handleKey, this);
    		this.downKey = game.input.keyboard.addKey(Phaser.Keyboard.S);
    		this.downKey.onDown.add(this.handleKey, this);
    		this.leftKey = game.input.keyboard.addKey(Phaser.Keyboard.A);
    		this.leftKey.onDown.add(this.handleKey, this);
    		this.rightKey = game.input.keyboard.addKey(Phaser.Keyboard.D);
    		this.rightKey.onDown.add(this.handleKey, this);   
          // this is the group which will contain all tile sprites
		this.tileSprites = game.add.group();
          // at the beginning of the game, the player cannot move
          this.canMove=false;
          // at the beginning of the game we add two "2"
          this.addTwo();
		this.addTwo();
     },
     // A NEW "2" IS ADDED TO THE GAME
	addTwo: function(){
		// choosing an empty tile in the field
		do{
			var randomValue = Math.floor(Math.random() * 16);
		} while (this.fieldArray[randomValue] != 0)
		// such empty tile now takes "2" value
		this.fieldArray[randomValue] = 2;
		// creation of a new sprite with "tile" instance, that is "tile.png" we loaded before
		var tile = game.add.sprite(toCol(randomValue) * tileSize, toRow(randomValue) * tileSize, "tile");
		// creation of a custom property "pos" and assigning it the index of the newly added "2"
		tile.pos = randomValue;
		// at the beginning the tile is completely transparent
		tile.alpha = 0;
		// creation of a text which will represent the value of the tile
		var text = game.add.text(tileSize / 2, tileSize / 2, "2", {
               font: "bold 16px Arial",
               align: "center"
          });
          // setting text anchor in the horizontal and vertical cenater
		text.anchor.set(0.5);
		// adding the text as a child of tile sprite
		tile.addChild(text);
		// adding tile sprites to the group
		this.tileSprites.add(tile);
		// creation of a new tween for the tile sprite
		var fadeIn = game.add.tween(tile);
		// the tween will make the sprite completely opaque in 250 milliseconds
		fadeIn.to({
               alpha: 1
          }, 250, Phaser.Easing.Linear.None, true);
		// tween callback
		fadeIn.onComplete.add(function(){
			// updating tile numbers. This is not necessary the 1st time, anyway
			this.updateNumbers();
			// now I can move
			this.canMove=true;
		}, this);
	},
     handleKey: function(e){
          // Is the player allowed to move?
          if(this.canMove){
          	// the player can move, let's set "canMove" to false to prevent moving again until the move process is done
               this.canMove=false;
               // keeping track if the player moved, i.e. if it's a legal move
               var moved = false;
               // look how I can sort a group ordering it by a property
               switch(e.keyCode){
                    case Phaser.Keyboard.A:
                         this.tileSprites.sort("x", Phaser.Group.SORT_ASCENDING);
                    break;
                    case Phaser.Keyboard.D:
                         this.tileSprites.sort("x", Phaser.Group.SORT_DESCENDING);
                    break; 
                    case Phaser.Keyboard.W:
                         this.tileSprites.sort("y", Phaser.Group.SORT_ASCENDING);
                    break;
                    case Phaser.Keyboard.S:
                         this.tileSprites.sort("y", Phaser.Group.SORT_DESCENDING);
                    break;
               }    
			// looping through each element in the group
			this.tileSprites.forEach(function(item){
				// getting row and column starting from a one-dimensional array
				var row = toRow(item.pos);
				var col = toCol(item.pos);
                    switch(e.keyCode){
                         // moving LEFT
     				case Phaser.Keyboard.A:
                              // checking if we aren't already on the leftmost column (the tile can't move)
          				if(col > 0){
          					// setting a "remove" flag to false. Sometimes you have to remove tiles, when two merge into one 
          					var remove = false;
          					// looping from column position back to the leftmost column
          					for(var i = col - 1; i >= 0; i--){
          						// if we find a tile which is not empty, our search is about to end...
          						if(this.fieldArray[row * 4 + i] != 0){
          							// ...we just have to see if the tile we are landing on has the same value of the tile we are moving
          							if(this.fieldArray[row * 4 + i] == this.fieldArray[row * 4 + col]){
          								// in this case the current tile will be removed
          								remove = true;
          								i--;                                             
          							}
          							break;
          						}
          					}
          					// if we can actually move...
          					if(col != i + 1){
          						// set moved to true
                                        moved=true;
                                        // moving the tile "item" from row*4+col to row*4+i+1 and (if allowed) remove it
                                        this.moveTile(item, row * 4 + col, row * 4 + i + 1, remove);
          					}
          				}
                         break;
                         // moving RIGHT
                         case Phaser.Keyboard.D:
                              if(col < 3){
                                   var remove = false;
          					for(i = col + 1; i <= 3; i++){
          						if(this.fieldArray[row * 4 + i] !=0){
                                             if(this.fieldArray[row * 4 + i] == this.fieldArray[row * 4 + col]){
          								remove = true;
          								i++;                                             
          							}
          							break
          						}
          					}
          					if(col != i - 1){
                                        moved = true;
          						this.moveTile(item, row * 4 + col, row * 4 + i - 1, remove);
          					}
          				}
                         break;
                         // moving UP
                         case Phaser.Keyboard.W:
                              if(row > 0){  
                                   var remove=false;
     						for(i = row - 1; i >= 0; i--){
     						    if(this.fieldArray[i * 4 + col] != 0){
     							   if(this.fieldArray[i * 4 + col] == this.fieldArray[row * 4 + col]){
     							        remove = true;
     							        i--;                                             
                                             }
                                             break
     							}
     						}
     						if(row != i + 1){
                                        moved=true;
                                        this.moveTile(item, row * 4 + col, (i + 1) * 4 + col, remove);
     						}
     					}
                         break;
                         // moving DOWN
                         case Phaser.Keyboard.S:
                              if(row<3){
                                   var remove = false;
                                   for(i = row + 1; i <= 3; i++){
                                        if(this.fieldArray[i * 4 + col] !=0 ){
                                             if(this.fieldArray[i * 4 + col] == this.fieldArray[row * 4 + col]){
                                                  remove = true;
                                                  i++;                                             
                                             }
                                             break
                                        }
                                   }
                                   if(row!=i-1){
                                        moved=true;
                                        this.moveTile(item,row*4+col,(i-1)*4+col,remove);
                                   }
                              }
                         break;
                         
                    }
			}, this);
			// completing the move
			this.endMove(moved);
          }
     },
     // THIS FUNCTION UPDATES NUMBER AND COLOR IN EACH TILE
     updateNumbers: function(){
	    // look how I loop through all tiles
		this.tileSprites.forEach(function(item){
			// retrieving the proper value to show
			var value = this.fieldArray[item.pos];
			// showing the value
			item.getChildAt(0).text = value;
			// tinting the tile
			item.tint = colors[value];
		}, this);	
	},
     // FUNCTION TO COMPLETE THE MOVE AND PLACE ANOTHER "2" IF WE CAN
	endMove: function(m){
	    // if we move the tile...
		if(m){
			// add another "2"
			this.addTwo();
          }
          else{
          	// otherwise just let the player be able to move again
			this.canMove=true;
		}
	},
     // FUNCTION TO MOVE A TILE
	moveTile: function(tile, from, to, remove){
		// first, we update the array with new values
          this.fieldArray[to] = this.fieldArray[from];
          this.fieldArray[from] = 0;
          tile.pos = to;
          // then we create a tween
          var movement = game.add.tween(tile).to({
               x: tileSize * (toCol(to)),
               y: tileSize * (toRow(to))
          }, 150, Phaser.Easing.Linear.None, true);
          if(remove){
          	// if the tile has to be removed, it means the destination tile must be multiplied by 2
               this.fieldArray[to] *= 2;
               // at the end of the tween we must destroy the tile
               movement.onComplete.add(function(){
                    tile.destroy();
               });
          }
     }     
}

// GIVEN A NUMBER IN A 1-DIMENSION ARRAY, RETURNS THE ROW AS IF IT WERE IN A 4*4 TWO DIMENSIONAL ARRAY
function toRow(n){
	return Math.floor(n / 4);
}
				
// GIVEN A NUMBER IN A 1-DIMENSION ARRAY, RETURNS THE COLUMN AS IF IT WERE IN A 4*4 TWO DIMENSIONAL ARRAY
function toCol(n){
	return n % 4;	
}
It would also be interesting to publish a step by step guide and turn it into a complete game, meanwhile 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