Full step by step tutorial to make a Flash game like “a blocky Christmas”

Read all posts about "" game

As you should know, tomorrow it’s Christmas, and I think you are expecting some Christmas gifts, so let me show you the Christmas gift I am giving to you all.

Did you play Bart Bonte‘s smash hit a blocky Christmas?

It’s one of the best yet simple games I played in the last months, and I am going to show you how to make your own blocky puzzle game following eight easy steps.

But before all, spend some (if not several) minutes to solve the game, the levels are really well designed and I am sure you will enjoy it.

Now, let’s start with the tutorial. Basically, a blocky Christmas is a tile based game, so all we need is an array and some coordinates.

1) Creating and showing of the game field

We are about to build level 5, because it includes everything we need to test all game features, and it’s easy to solve, so you will be able to test it without burning your brain.

The game field is a 19×19 array with walkable areas, walls, the player itself (a 1×1 green block) and some other green blocks you have to move to the goal.

So we are going to define some variables:

tileSize: the size, in pixels, of a tile.

gameField: a two-dimensional array with level walls (1) and walkable tiles (0).

player: a Point variable holding player’s x and y starting position.

playerGoal: a Point variable holding player’s x and y goal position.

blocks: an array of objects representing a block. Every block object contains an array of points defining its shape and the color we will use to render it.

goals: an array of objects containing an array of points defining the goal position of each block. I used an object although it contains only one item (the array) to let you expand the game easily.

The entire game is then rendered by displayGame function which lazily clears and redraws the entire level every time we need it, but it’s ok anyway as rendering the level in a fancy way is beyond the scope of this tutorial.

This is the script:

package {
	import flash.display.Sprite;
	import flash.geom.Point;
	public class Main extends Sprite {
		private var tileSize:Number=25;
		private var gameField:Array=[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]];
		private var player:Point=new Point(5,9);
		private var playerGoal:Point=new Point(9,13);
		private var blocks:Array=[{shape:[new Point(7,3),new Point(8,3),new Point(9,3),new Point(10,3),new Point(11,3)],color:0x339900},{shape:[new Point(8,4),new Point(9,4),new Point(10,4)],color:0x00cc00}];
		private var goals:Array=[{shape:[new Point(7,15),new Point(8,15),new Point(9,15),new Point(10,15),new Point(11,15)]},{shape:[new Point(8,14),new Point(9,14),new Point(10,14)]}];
		public function Main() {
			displayGame();
		}
		private function displayGame():void {
			graphics.clear();
			graphics.lineStyle(1,0x000000,0.2);
			for (var i:Number=0; i

And this is the result:

We have our level rendered, now it's time to allow some interaction.

2) Moving the player with arrow keys

From now on, the new blocks of code added, or rewritten, are included between start of new code and end of new code comments, so you can easily see what I am going to add/change to previous scripts.

At this time we need to add a KeyboardEvent listener to check for pressed keys, assign a possible move among up, down, left and right and check if the player can move in such direction. In this case, we update player position and render the level once more.

The function which decides if the player can move is possibleMove and at the moment it only returns true, meaning the player can always move.

I am storing the possible move in a Point variable, using it to save the x and y offsets of the desired player movement, so it will be (0,-1) if the player goes up, (1,0) if the player goes right, and so on.

This is the code:

package {
	import flash.display.Sprite;
	import flash.geom.Point;
	// ******************* start of new code ******************* \\
	import flash.events.KeyboardEvent;
	// ******************** end of new code ******************** \\
	public class Main extends Sprite {
		private var tileSize:Number=25;
		private var gameField:Array=[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]];
		private var player:Point=new Point(5,9);
		private var playerGoal:Point=new Point(9,13);
		private var blocks:Array=[{shape:[new Point(7,3),new Point(8,3),new Point(9,3),new Point(10,3),new Point(11,3)],color:0x339900},{shape:[new Point(8,4),new Point(9,4),new Point(10,4)],color:0x00cc00}];
		private var goals:Array=[{shape:[new Point(7,15),new Point(8,15),new Point(9,15),new Point(10,15),new Point(11,15)]},{shape:[new Point(8,14),new Point(9,14),new Point(10,14)]}];
		// ******************* start of new code ******************* \\
		private var possibleMove:Point;
		// ******************** end of new code ******************** \\
		public function Main() {
			displayGame();
			// ******************* start of new code ******************* \\
			stage.addEventListener(KeyboardEvent.KEY_DOWN,movePlayer);
			// ******************** end of new code ******************** \\
		}
		// ******************* start of new code ******************* \\
		private function movePlayer(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 37 :
					possibleMove=new Point(-1,0);
					break;
				case 38 :
					possibleMove=new Point(0,-1);
					break;
				case 39 :
					possibleMove=new Point(1,0);
					break;
				case 40 :
					possibleMove=new Point(0,1);
					break;
			}
			if (checkPlayerMove()) {
				player.x+=possibleMove.x;
				player.y+=possibleMove.y;
				displayGame();
			}
		}
		private function checkPlayerMove():Boolean {
			return true;
		}
		// ******************** end of new code ******************** \\
		private function displayGame():void {
			graphics.clear();
			graphics.lineStyle(1,0x000000,0.2);
			for (var i:Number=0; i

And this is the result:

Move the player with arrow keys around the screen.

Unfortunately, the player does not move alone, as it's also a magnet

3) Introducing magnetism

If close to a moveable block, the player also acts as a magnet, so we expect it to pull and push attached blocks as it moves around the screen.

In this step, I am going to see when a block is directly attached to the player and make it follow player movement.

To see if a block is directly attached to the player, the Manhattan distance between the player and one of the block tiles must be one. If you need more information about Manhattan distance, check the post the fastest way to find the distance between two points.

So if we have a block directly attached to the player, we can move it in the same player direction.

This is the code:

package {
	import flash.display.Sprite;
	import flash.geom.Point;
	import flash.events.KeyboardEvent;
	public class Main extends Sprite {
		private var tileSize:Number=25;
		private var gameField:Array=[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]];
		private var player:Point=new Point(5,9);
		private var playerGoal:Point=new Point(9,13);
		private var blocks:Array=[{shape:[new Point(7,3),new Point(8,3),new Point(9,3),new Point(10,3),new Point(11,3)],color:0x339900},{shape:[new Point(8,4),new Point(9,4),new Point(10,4)],color:0x00cc00}];
		private var goals:Array=[{shape:[new Point(7,15),new Point(8,15),new Point(9,15),new Point(10,15),new Point(11,15)]},{shape:[new Point(8,14),new Point(9,14),new Point(10,14)]}];
		private var possibleMove:Point;
		public function Main() {
			displayGame();
			stage.addEventListener(KeyboardEvent.KEY_DOWN,movePlayer);
		}
		private function movePlayer(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 37 :
					possibleMove=new Point(-1,0);
					break;
				case 38 :
					possibleMove=new Point(0,-1);
					break;
				case 39 :
					possibleMove=new Point(1,0);
					break;
				case 40 :
					possibleMove=new Point(0,1);
					break;
			}
			if (checkPlayerMove()) {
				// ******************* start of new code ******************* \\
				checkMagnet();
				// ******************** end of new code ******************** \\
				player.x+=possibleMove.x;
				player.y+=possibleMove.y;
				displayGame();
			}
		}
		// ******************* start of new code ******************* \\
		private function checkMagnet():void {
			for (var i:Number=0; i

and this is the result:

You will be able to push and pull blocks around the screen, but only if you are directly adjacent to them, while in the original game a block attached to the player also becomes magnetic and is able to attach other blocks.

4) Extending magnetism

One of the ways to check for blocks attached to blocks which are attached to the player would be some kind of recursive flood fill algorithm. Check flood fill implementation post for more information about flood fill.

This time I won't use recursion, so I need to check for all tiles adjacent to each block for another block, which in this case will inherit the magnetism, and to avoid infinite loops (e.g. I am checking A and find B is adjacent, so I am checking B and find A is adjacent so I am checking A...) I need to store visited blocks into an array, this way if a block is not in the array, I can be sure it's the first time I meet it.

This is the code:

package {
	import flash.display.Sprite;
	import flash.geom.Point;
	import flash.events.KeyboardEvent;
	public class Main extends Sprite {
		private var tileSize:Number=25;
		private var gameField:Array=[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]];
		private var player:Point=new Point(5,9);
		private var playerGoal:Point=new Point(9,13);
		private var blocks:Array=[{shape:[new Point(7,3),new Point(8,3),new Point(9,3),new Point(10,3),new Point(11,3)],color:0x339900},{shape:[new Point(8,4),new Point(9,4),new Point(10,4)],color:0x00cc00}];
		private var goals:Array=[{shape:[new Point(7,15),new Point(8,15),new Point(9,15),new Point(10,15),new Point(11,15)]},{shape:[new Point(8,14),new Point(9,14),new Point(10,14)]}];
		private var possibleMove:Point;
		public function Main() {
			displayGame();
			stage.addEventListener(KeyboardEvent.KEY_DOWN,movePlayer);
		}
		private function movePlayer(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 37 :
					possibleMove=new Point(-1,0);
					break;
				case 38 :
					possibleMove=new Point(0,-1);
					break;
				case 39 :
					possibleMove=new Point(1,0);
					break;
				case 40 :
					possibleMove=new Point(0,1);
					break;
			}
			if (checkPlayerMove()) {
				checkMagnet();
				player.x+=possibleMove.x;
				player.y+=possibleMove.y;
				displayGame();
			}
		}
		// ******************* start of new code ******************* \\
		private function checkMagnet():void {
			var magneticBlocks:Array=new Array();
			var visited:Array=[player];
			for (var n:Number=0; n

And this is the result:

Now blocks inherit magnetism and the whole game moves all actors correctly, so now we must start to limit player movements.

5) Preventing player to go through walls

The first way we are going to limit player movement, is preventing it to go through walls, that is the player cannot stand over tiles marked with 1.

It's time to work a bit on checkPlayerMove function:

package {
	import flash.display.Sprite;
	import flash.geom.Point;
	import flash.events.KeyboardEvent;
	public class Main extends Sprite {
		private var tileSize:Number=25;
		private var gameField:Array=[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]];
		private var player:Point=new Point(5,9);
		private var playerGoal:Point=new Point(9,13);
		private var blocks:Array=[{shape:[new Point(7,3),new Point(8,3),new Point(9,3),new Point(10,3),new Point(11,3)],color:0x339900},{shape:[new Point(8,4),new Point(9,4),new Point(10,4)],color:0x00cc00}];
		private var goals:Array=[{shape:[new Point(7,15),new Point(8,15),new Point(9,15),new Point(10,15),new Point(11,15)]},{shape:[new Point(8,14),new Point(9,14),new Point(10,14)]}];
		private var possibleMove:Point;
		public function Main() {
			displayGame();
			stage.addEventListener(KeyboardEvent.KEY_DOWN,movePlayer);
		}
		private function movePlayer(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 37 :
					possibleMove=new Point(-1,0);
					break;
				case 38 :
					possibleMove=new Point(0,-1);
					break;
				case 39 :
					possibleMove=new Point(1,0);
					break;
				case 40 :
					possibleMove=new Point(0,1);
					break;
			}
			if (checkPlayerMove()) {
				checkMagnet();
				player.x+=possibleMove.x;
				player.y+=possibleMove.y;
				displayGame();
			}
		}
		private function checkMagnet():void {
			var magneticBlocks:Array=new Array();
			var visited:Array=[player];
			for (var n:Number=0; n

And this is the result:

Now walls stop the player, but they don't stop attached blocks.

6) Preventing blocks to go through walls

Preventing blocks to go through walls is not different than preventing the player to go through walls, you just need to check blocks not to overlap walls tile by tile.

This is the code:

package {
	import flash.display.Sprite;
	import flash.geom.Point;
	import flash.events.KeyboardEvent;
	public class Main extends Sprite {
		private var tileSize:Number=25;
		private var gameField:Array=[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]];
		private var player:Point=new Point(5,9);
		private var playerGoal:Point=new Point(9,13);
		private var blocks:Array=[{shape:[new Point(7,3),new Point(8,3),new Point(9,3),new Point(10,3),new Point(11,3)],color:0x339900},{shape:[new Point(8,4),new Point(9,4),new Point(10,4)],color:0x00cc00}];
		private var goals:Array=[{shape:[new Point(7,15),new Point(8,15),new Point(9,15),new Point(10,15),new Point(11,15)]},{shape:[new Point(8,14),new Point(9,14),new Point(10,14)]}];
		private var possibleMove:Point;
		public function Main() {
			displayGame();
			stage.addEventListener(KeyboardEvent.KEY_DOWN,movePlayer);
		}
		private function movePlayer(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 37 :
					possibleMove=new Point(-1,0);
					break;
				case 38 :
					possibleMove=new Point(0,-1);
					break;
				case 39 :
					possibleMove=new Point(1,0);
					break;
				case 40 :
					possibleMove=new Point(0,1);
					break;
			}
			if (checkPlayerMove()) {
				checkMagnet();
				player.x+=possibleMove.x;
				player.y+=possibleMove.y;
				displayGame();
			}
		}
		private function checkMagnet():void {
			var magneticBlocks:Array=new Array();
			var visited:Array=[player];
			for (var n:Number=0; n

And this is the result:

Now blocks cannot overlap walls, but they still can overlap each others

7) Preventing blocks to overlap each others

This is the most difficult thing to do without using recursive functions. Basically if a block can move (because it won't overlap any wall) but it would push another block which cannot move, the unmovable block should by threated like a solid wall, preventing the first block too to move, which becomes a wall preventing the player to push it.

It sounds complicate but you just need to scan through all blocks, transforming them to walls if they cannot move, and then run the previous routines with the remaining, free blocks.

Don't forget, at the end of the process, to clear the fake walls you set when dealing with unmoveable blocks.

This is the code:

package {
	import flash.display.Sprite;
	import flash.geom.Point;
	import flash.events.KeyboardEvent;
	public class Main extends Sprite {
		private var tileSize:Number=25;
		private var gameField:Array=[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]];
		private var player:Point=new Point(5,9);
		private var playerGoal:Point=new Point(9,13);
		private var blocks:Array=[{shape:[new Point(7,3),new Point(8,3),new Point(9,3),new Point(10,3),new Point(11,3)],color:0x339900},{shape:[new Point(8,4),new Point(9,4),new Point(10,4)],color:0x00cc00}];
		private var goals:Array=[{shape:[new Point(7,15),new Point(8,15),new Point(9,15),new Point(10,15),new Point(11,15)]},{shape:[new Point(8,14),new Point(9,14),new Point(10,14)]}];
		private var possibleMove:Point;
		public function Main() {
			displayGame();
			stage.addEventListener(KeyboardEvent.KEY_DOWN,movePlayer);
		}
		private function movePlayer(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 37 :
					possibleMove=new Point(-1,0);
					break;
				case 38 :
					possibleMove=new Point(0,-1);
					break;
				case 39 :
					possibleMove=new Point(1,0);
					break;
				case 40 :
					possibleMove=new Point(0,1);
					break;
			}
			if (checkPlayerMove()) {
				checkMagnet();
				// ******************* start of new code ******************* \\
				if (checkPlayerMove()) {
					player.x+=possibleMove.x;
					player.y+=possibleMove.y;
				}
				for (var i:Number=0; i

And this is the result:

Now everything acts just like the original game, we only need to check if the player solved the level

8) Checking for level solution

Checking for level solution is really easy, you just need to see if every block tile overlaps its goal tile, and if player position overlaps goal position, in this case the game ends:

package {
	import flash.display.Sprite;
	import flash.geom.Point;
	import flash.events.KeyboardEvent;
	public class Main extends Sprite {
		private var tileSize:Number=25;
		private var gameField:Array=[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]];
		private var player:Point=new Point(5,9);
		private var playerGoal:Point=new Point(9,13);
		private var blocks:Array=[{shape:[new Point(7,3),new Point(8,3),new Point(9,3),new Point(10,3),new Point(11,3)],color:0x339900},{shape:[new Point(8,4),new Point(9,4),new Point(10,4)],color:0x00cc00}];
		private var goals:Array=[{shape:[new Point(7,15),new Point(8,15),new Point(9,15),new Point(10,15),new Point(11,15)]},{shape:[new Point(8,14),new Point(9,14),new Point(10,14)]}];
		private var possibleMove:Point;
		public function Main() {
			displayGame();
			stage.addEventListener(KeyboardEvent.KEY_DOWN,movePlayer);
		}
		private function movePlayer(e:KeyboardEvent):void {
			switch (e.keyCode) {
				case 37 :
					possibleMove=new Point(-1,0);
					break;
				case 38 :
					possibleMove=new Point(0,-1);
					break;
				case 39 :
					possibleMove=new Point(1,0);
					break;
				case 40 :
					possibleMove=new Point(0,1);
					break;
			}
			if (checkPlayerMove()) {
				checkMagnet();
				if (checkPlayerMove()) {
					player.x+=possibleMove.x;
					player.y+=possibleMove.y;
				}
				for (var i:Number=0; i

And this is the result:

Now you can play the complete level.

Download the source code of all examples.

I really wish you a blocky Christmas, and a special thanks goes to Bart Bonte for giving me the permission to dissect his game that hard.

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

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