Advertisement

Create Your Own Match 3 Game With Flixel: Core Game Mechanics

by

This tutorial will guide you through all the steps required to create a Match 3 game (such as Bejeweled) using Flixel. We'll structure the project using solid OOP techniques.


Also available in this series:

  1. Create Your Own Match 3 Game With Flixel: Core Game Mechanics
  2. Create Your Own Match 3 Game With Flixel: Polish

Step 1: Download the Project Files

After downloading project files, place them in your "projects" folder or wherever you feel comfortable with them.

Remember that if you change names or paths of the files, you'll also need to change them in the code!

If you look inside you'll notice I have also attached the Flixel engine, so you don't have to download it yourself. However, if you'd like to, it's available here.


Step 2: Create the Project

Once our files are in the place we selected them to be, it's time to create the project. In this tutorial I will be using FlashDevelop, which is completely free (albeit Windows-only) and can be found here. It's a good idea to download the newest stable version.

Now open FlashDevelop (or the IDE of your choice) and create the project; to do this in FlashDevelop you need to go to Project->New Project.


After doing so, you should see a similar window to the one above, depending on your IDE.

Now that the project is created, let's double check some settings. Firstly go to the Tools->Program Settings or simply press F10.


Make sure that Flex SDK Location under AS3Context is a correctly pointing to the Flex SDK directory. If you haven't downloaded the SDK yet, go here, to do so.

After checking that, switch from AS3Context to FlashViewer, and check the path of a debug player.


The External Player Path should point to the debug player. If you didn't download this one yet either, go here and do so, the version you should use with your IDE is projector content debugger.

Now that we have everything set up, we only need to test it out.

 
package  
{ 
	import flash.display.Sprite; 
	import flash.events.Event; 
	 
	/** 
	 * ... 
	 * @author You 
	 */ 
	public class Main extends Sprite  
	{ 
		 
		public function Main():void  
		{ 
			trace("It's working!"); 
			if (stage) init(); 
			else addEventListener(Event.ADDED_TO_STAGE, init); 
			 
		} 
		 
		private function init(e:Event = null):void  
		{ 
			removeEventListener(Event.ADDED_TO_STAGE, init); 
			// entry point 
		} 
		 
	} 
	 
}

Paste this code into your Main.as file and run it in the Debug mode. There should be no errors, the debug player should spawn with nothing but a blank window and in the output you should see It's working! - a string that we traced. If it does work, we can go to the next step, if it doesn't, I recommend checking the paths and making sure all is where it's supposed to be. Remember that if you're using windows XP, you need to have .NET framework updated.


Step 3: Attach Flixel to the Project

If everything works, we can attach Flixel to our project and compile a simple application.

Start by going to "Project->Properties" and then enter the tab named Classpaths.


Now press Add Classpath button, find directory named flixel, select it and then press OK button. You can see that now the folder you selected has been added to your classpaths.


Press OK again, and we're finishing with preparing our project. Now we can actually start coding!

Delete all the code in Main.as leaving only:

 
package  
{ 
 
}

So we have a clean document. Now, first thing we need to do is to import flixel to our package, which we do by:

 
import org.flixel.*;

Notice that we don't specify what are we going to use. By using * we're including all classes from flixel, even those we are not going to use! It may make the SWF file bigger, but for now we don't have to worry about that, we are very far from finishing our project. After importing the library, we should specify size and background color for our SWF:

 
[SWF(width="600", height="450", backgroundColor="#000000")]

I set width to 600 and height to 450, so it would nicely fit on the site, you can change it to whatever you like it to be, the same applies to the backgroundColor. Once we're done with that, we should define our Main object:

 
public class Main extends FlxGame 
{ 
 
}

Note that our class extends FlxGame. That basically means that our class, besides having its own exclusive methods and properties, will also contain all methods and properties decalred in FlxGame.as. After that, we need to create a constructor:

 
public function Main() 
{ 
 
}

Constructor is just a method with the same name as our object, in this case named Main, that is called at object's creation. Since it's our Main class, it will be called at the beginning of our game. Your current code should be similar to this:

 
package   
{ 
	import org.flixel.*; 
	[SWF(width="600", height="450", backgroundColor="#000000")] 
  
	public class Main extends FlxGame 
	{	 
		public function Main() 
		{ 
 
		} 
	} 
}

We can try to run our application now, press F5 or go to Project->Test Movie.

 
col: 19 Error: No default constructor found in base class org.flixel:FlxGame.

Unfortunately, the program couldn't compile. The error tells us, that base class -- FlxGame, which is inherited by our Main class -- doesn't have a default constructor. Default constructor is such a constructor that requires no arguments. That means we are supposed to feed our base class's constructor some arguments, so we need to call its constructor from our Main object. To do that, we use the super() function from Main object's constructor. All we need to know is what kind of arguments we should pass; for that we can either jump to Flixel documentation or let our IDE to tell us that. The documentation can be found here.

public function FlxGame(GameSizeX:uint, GameSizeY:uint, InitialState:Class, Zoom:uint = 2)

From flixel docs.

The arguments that need to be filled are: GameSizeX, gameSizeY - which set width and height of our game in pixels, the next one is InitialState, which I will talk about in a second and Zoom, which is self-explanatory. The InitialState must be a class that inherits from FlxState. That means we need to create it, before we can move on.


Step 4: Create a GameState

To create a new class, simply right-click the folder you want the new class to be in and then go to Add->New Class in the project view.



New AS3 class creation window will pop up. Instead of filling only a name gap, I also told IDE to inherit from FlxState class and to create its constructor. Click OK and you will see a new class document named GameState.as, if you named the new class the same way I did. You should replace import org.flixel.FlxState; with import org.flixel.*; for now. The code inside the document should look like this:

 
package   
{ 
	import org.flixel.*; 
	 
	public class GameState extends FlxState 
	{ 
		 
		public function GameState()  
		{ 
			super(); 
			 
		} 
		 
	} 
 
}

As you can see, this time we have default constructor of the base class available. Now let's go back to our Main.as and let's fill the super() function with arguments.

 
package   
{ 
	import org.flixel.*; 
	[SWF(width="600", height="450", backgroundColor="#000000")] 
  
	public class Main extends FlxGame 
	{	 
		public function Main() 
		{ 
			super(600, 450, GameState, 1); 
		} 
	} 
}

Without further ado, let's compile our game!

Nothing interesting on the screen, but it compiled successfully!


Step 5: Create a Gem Class

Let's start this step from creating a class in a similar way we created GameState, but this time we will inherit from FlxSprite.


Gem class will represent a gem in our game, for now let's embed graphics for it.

 
package   
{ 
	import org.flixel.FlxSprite; 
	 
	public class Gem extends FlxSprite 
	{ 
		[Embed(source = '../assets/gems.png')] 
		private const ImgGems:Class; 
		 
		public function Gem()  
		{ 
			 
		} 
		 
	} 
 
}

ImgGems (gems.png) from now on will represent our image. Let's also create an update() function, this function is called once a frame so we'll be putting stuff that needs our constant attention here, as well as things that happen over time.

 
override public function update():void 
{ 
	super.update(); 
}

As you may have noticed, in the definition of this function appeared override. We needed to state that, because FlxSprite also has its own update() function, and because we can't have two functions with the same name in one object, we have overridden the FlxSprite's update() function. Because FlxSprite's update() needs to be called anyway (to update the animation and so on), we just use super.update(), which simply calls the overridden function. Since we can now write our own code in this function, it's as if we're extending the base update(). This is how FlxSprite's update() function looks:

 
override public function update():void 
{ 
	updateMotion(); 
	updateAnimation(); 
	updateFlickering(); 
}

As you can see, it also overrides its base class constructor, but it doesn't call super.update() though, that's because since it inherited all its base class's (FlxObject) functions, it calls them by itself. That means that we could replace our super.update() with:

 
updateMotion(); 
updateAnimation(); 
updateFlickering();

And the result would be the very same. In fact, it would be a tiny bit more efficient, but since it's such a tiny bit, I'm going to spare two lines of the code. :)


Step 6: Create Animations for Gem Class

It's time to put something colorful on the screen! Let's start by loading our embedded image and creating animations in Gem.as.

 
public function Gem()  
{ 
	loadGraphic(ImgGems, true, false, 32, 32, false); 
	addAnimation("blue", [0]); 
	addAnimation("green", [4]); 
	addAnimation("orange", [8]); 
	addAnimation("aqua", [12]); 
	addAnimation("red", [16]); 
	addAnimation("yellow", [20]); 
}

As you can see, we do all this at the time when the object is created, in constructor. First we loadGraphic(), this function takes for its arguments: an image, boolean value whether the sprite will be animated, another boolean to know whether or not we will use horizontal flipping (we will not), width of a frame, height of a frame and finally a boolean that's telling the function whether we want to share the graphic with all other sprites using it or whether we want an unique instance of it.

Now we can add animations. I named them after colors they will represent. The first argument is, as you can see, the name of the animation, and the second is an array containing the index of the first frame of the corresponding animation.


Our gems are 32px by 32px, that's why we set frameWidth, frameHeight to 32px each. The first frame's ID is actually a 0. We count frames from left to right. As you can see, the first frame is blue, the fifth is green, ninth orange and so on. The frames we skipped are part of an other animation, and for now we can simply ignore them. Our animations are one frame each, and if you look at the rest of arguments we did not have to fill, you'll notice that we're not really animating anything. We're only changing sprites. :)


Step 7: Assign Animations

Since we have quite a few animations, we need to tell the gem which animation it should use. We don't want all the gems to use the same animation, so we need to add a new variable:

 
private var type:uint;

We want to tell the gem which animation it should play when we create it, so we simply add an argument to our gem's constructor:

 
public function Gem(_type:uint)  
{ 
	loadGraphic(ImgGems, true, false, 32, 32, false); 
	addAnimation("blue", [0]); 
	addAnimation("green", [4]); 
	addAnimation("orange", [8]); 
	addAnimation("aqua", [12]); 
	addAnimation("red", [16]); 
	addAnimation("yellow", [20]); 
			 
	type = _type; 
}

We save a given argument to the variable we created earlier (type). Now let's use type to tell the gem which animation it should play:

 
switch (type)  
{ 
	case 0: 
		play("blue"); 
		break; 
	case 1: 
		play("green"); 
		break; 
	case 2: 
		play("orange"); 
		break; 
	case 3: 
		play("aqua"); 
		break; 
	case 4: 
		play("red"); 
		break; 
	case 5: 
		play("yellow"); 
		break; 
}

The function play() takes an animation name as an argument, and makes the sprite start using the animation with this name. As you can see in the code above, depending the type's value, we use switch statement to play an animation attached to that value.


Step 8: Render a Gem

Go back to the GameState.as and under import org.flixel.*;, import our feshly made class, Gem:

 
import org.flixel.*; 
import Gem;

Now we'll add a new object (a gem) to our game loop. We do this by using the following function:

 
add(new Gem(0));

Our constructor should look like this now:

 
public function GameState()  
{ 
	super(); 
	add(new Gem(0)); 
			 
}

add() takes an object as an argument and adds it to the game loop. We just created new object that we have no reference to, but it's not like we need any now. Note that we created a gem giving it 0 as an argument, so it will play animation bound to that number. Let's press F5 and see if we can see the gem rendering properly.

As expected, we can clearly see the blue gem in the top left corner! You can replace Gem(0) to Gem(3) or any other number, as long as it's lower or equal to 5.


Step 9: Create an Array

We're still not leaving GameState.as; this time let's create an array that can contain all of our game's gems. We'll use this to generate the grid of gems used in the game.

 
private var gems:Array = new Array(); //this array will contain all of our game's gems

Now let's fill our array with gems! We'll use a for loop for that... but exactly how many will we need to add? We'll need to use two new variables. Let's call them rows and columns. Their names are pretty self explanatory, so let's not waste any time and define them.

 
private var columns:uint = 8; 
private var rows:uint = 8;

We set columns and rows to 8, that means we'll have 64 gems total. Without further ado, let's push as many gems as there should be on rows by columns board.


Step 10: Fill gems with Gems

Let's start from creating the loop in which we will push all the gems.

 
for (var i:int = 0; i < columns * rows; ++i) 
{ 
 
}

Let's use push to add all the gems to our array:

 
for (var i:int = 0; i < columns * rows; ++i) 
{ 
	gems.push(new Gem(0)); 
}

We're doing it alright but now there appears a problem, they are all of the same type! Let's use a Math.random() function to get a random integer between 0 and 5. For that of course, we need our game to know how many types we have, so we need to add a constant, which will represent a number of available types for our gems:

 
private const types = 6; //the number of available gems' types (red, green, blue, etc.)
 
for (var i:int = 0; i < columns * rows; ++i) 
{ 
	gems.push(new Gem(Math.floor(Math.random() * types))); 
}

Math.random() returns a real number that is greater or equal to 0 and less than 1. We multiply it by types, so now the random number will be greater or equal to 0 and less than 6. After that we use Math.floor(), so in case we'll get a number like 5.57648 it will return the nearest lower integer than that number (5, in this case).

Let's finally use add() function, to add all the pushed gems to the game loop, since add() returns the object we added, we simply make it return into push() function:

 
for (var i:int = 0; i < columns * rows; ++i) 
{ 
	gems.push(add(new Gem(Math.floor(Math.random() * types)))); 
}

Now let's press F5 and check out how our 64 gems look in the game!


I'm sure you can see they are all there! Unfortunately, every one of them is in the very same position. Let's change that.


Step 11: Position the Gems

To position all the gems we need to know how much space do we need between them. Let's create two additional constants for this purpose:

 
private const spaceX:uint = 32; //the space between columns 
private const spaceY:uint = 32; //the space between rows

Now let's add these spaces between the gems:

 
for (var i:int = 0; i < columns * rows; ++i) 
{ 
	gems.push(add(new Gem(Math.floor(Math.random() * types)))); 
	gems[i].y = Math.floor(i / columns) * spaceY; 
	gems[i].x = (i - (columns * Math.floor(i / columns))) * spaceX; 
}

For gems[i].y, we calculate the row ID (Math.floor(i / columns)) and then multiply it by spaceY so every row is spaceY px lower than the previous one. Note than in flixel the bigger the y value, the lower on screen the object is.

Secondly we calculate the column ID ((i - (columns * Math.floor(i / columns)))) and then multiply it by spaceX, which will place every column spaceX px further than the previous one.

Now you can compile the project and see results!


Doesn't it look lovely? Well, it could be even better, for example it isn't a good choice to start the board in the top left corner. The spaces between gems also could be a bit bigger. Let's fix both of those problems by making some small changes:

 
private const spaceX:uint = 42; //the space between gems on X axis 
private const spaceY:uint = 42; //the space between gems on Y axis 
private const begX:uint = 140;   //the X position component of our board 
private const begY:uint = 64;   //the Y position component of our board 
 
public function GameState()  
{ 
	super(); 
	for (var i:int = 0; i < columns * rows; ++i) 
	{ 
		gems.push(add(new Gem(Math.floor(Math.random() * types)))); 
		gems[i].y = begY + Math.floor(i / columns) * spaceY; 
		gems[i].x = begX + (i - (columns * Math.floor(i / columns))) * spaceX; 
	} 
}

We simply added a constant value to the position of each gem, which should result in shifting the whole board. Let's run our game and see how it looks:

Much better!


Step 12: Show the Cursor

Before we start adding new features, we should show a mouse cursor to the user. Let's go back to the Main.as and embed the PNG file with our custom mouse cursor:

 
[Embed(source = "../assets/mouseCursor.png")]  
private var ImgCursor:Class;

Nothing unexpected here, so let's just go straght to displaying the cursor, which is also quite straightforward:

 
public function Main() 
{ 
	super(600, 450, GameState, 1); 
	FlxG.mouse.show(ImgCursor); 
}

We used the FlxG.mouse.show() method to make the cursor visible. If we wanted to hide it, we can always use FlxG.mouse.hide(), but it's doubtful that it will be necessary in this game. Notice that we submitted ImgCursor to FlxG.mouse.hide() function, so it will display the cursor with our graphics. If we just left a null there, a simple built-in Flash cursor would appear. There are also two other arguments, which are offsetX and offsetY: these simply shift the cursor's image so its actual position isn't in the top left corner.

Let's build and run our project to see if the cursor is displaying properly:


It's there, and that's all it's supposed to do now!


Step 13: Create a Frame

Now that our mouse cursor is visible, we can start to use the mouse itself. We'll start off with selecting a gem, it's logical that before we actually do any action on gems the player is supposed to select one gem from the whole board. The first thing we need to consider is how will we show the player that the gem is selected. One of the easiest solutions to this problem is just placing a border around the selected gem. Notice that the frame image is in the gems.png, so now we need to embed it to the GameState. Let's switch the document to the GameState.as, then embed needed graphics and create an FlxSprite:

 
[Embed(source = '../assets/gems.png')] 
private const ImgGems:Class;
 
private var frame:FlxSprite = new FlxSprite();

Now let's load graphics and create animations for it, we can do it all in the constructor:

 
frame.loadGraphic(ImgGems, false, false, 32, 32, false);

Actually, we need only one animation for our frame. Let's look at the gems.png, and see what's the ID for our animation frame:


It's not very well visible on the image because it's so thin and light, but it's at the beginning of 7th row. Remember we need to count IDs from 0:

 
frame.addAnimation("frame", [24]);

There's nothing much to do but check out if it works properly, but to do that first we need to play freshly created animation and add the frame to the game loop:

 
frame.play("frame"); 
frame.addAnimation("frame", [24]);

Let's see if it works as it's supposed to:


Since we neither changed x nor y coordinates, the sprite displays at the top left corner of the screen. For now we don't want it to display at all, so let's just set the visible property to false:

 
frame.visible = false;

You can test the project once more to check if the frame is still rendering or not, it would be really weird if it did!


Step 14: Prepare for Selection

Now that we have our frame ready it's time to put it to use, but just before that we need to add a new variable that will keep an ID of the selected gem:

 
private const types:uint = 6;   //the number of available gems' types (red, green, blue, etc.) 
private const spaceX:uint = 42; //the space between gems on X axis 
private const spaceY:uint = 42; //the space between gems on Y axis 
private const begX:uint = 140;   //the X position component of our board 
private const begY:uint = 64;   //the Y position component of our board 
 
private var gems:Array = new Array(); //this array will contain all of our game's gems 
private var columns:uint = 8; 
private var rows:uint = 8; 
		 
private var selID:int = -1; //selID holds an ID of a selected gem, -1 = no gem selected

We set the selID to -1, and this will indicate, that no gem is selected (we're counting from 0, so if selID is equal to 0, then that means the first gem is the selected one). So, when should we select a gem? The right time for that is when the mouse button is pressed or released. For now let's create our update() function and a few new variables while we are at that:

 
override function update():void 
{ 
	var pressed:Boolean = FlxG.mouse.justPressed(); 
	var released:Boolean = FlxG.mouse.justReleased(); 
	var mouseX:Number = FlxG.mouse.x; 
	var mouseY:Number = FlxG.mouse.y; 
}

Those variables we created are just easier to use than some long named functions (Flx.mouse.justPressed();), and that's their sole purpose. Instead of typing those methods, we'll be able to just use the variables we just declared.


Step 15: Select a Gem

Now we need to place a few conditions before we actually select a gem, and these are:

 
if (released && mouseX > begX && mouseX < begX + columns * spaceX 
	&& mouseY > begY && mouseY < begY + rows * spaceY) 
{ 
				 
}

The first condition makes sure that the mouse button (remember that in Flash we actually can use only left mouse button) was released in this game frame. The second, third, forth and fifth make sure that the mouse is in the board area both on X and Y axes, if it's not then it shouldn't be possible to make any selections. If the player clicks outside those bounds, that must mean he wanted to do something else.

Now that we have our condition, it's time to add even more variables that will help us in navigating our board of gems!

 
if (pressed && mouseX > begX && mouseX < begX + columns * spaceX 
	&& mouseY > begY && mouseY < begY + rows * spaceY) 
{ 
	var rowID:uint = Math.floor((mouseY - begY) / spaceY); 
	var columnID:uint = Math.floor((mouseX - begX) / spaceX); 
	var id:uint = rowID * columns + columnID;		 
}

As you can guess, rowID and columnID does a similar thing it did during the positioning of our gems, but now it holds a row and a column of the gem underneath the cursor. We calculate this by Math.floor((mouseY - begY) / spaceY) for the gem's row, and by Math.floor((mouseX - begX) / spaceX) for the gem's column. Then we simply use those two variables to tell the id precisely what ID is under the cursor.

Now to select our gem we just need to tell the selID that some kind of id got selected, then make the frame visible and move it to that gem's position:

 
selID = id; 
frame.visible = true; 
frame.x = gems[selID].x; 
frame.y = gems[selID].y;

Setting the position is easy, we know the exact position of every gem, as long as we have its ID. We also told selID that now the selected gem's ID is equal to id.

Let's try it out and see if it works well:

By testing it, you can see that there's nothing odd about selecting, it works the way it should. Thanks to that, we can move on to the next step.


Step 16: Conceptualize the Swapping of Two Gems

Let's plan out how to implement the swapping of two gems. The first idea that would probably pop in your head is to make a deep copy of a first gem, the replace the first gem with the second one, and finally replacing a deep copy we made earlier with the second gem, this way we would swap the gems pretty easly. Of course that's not the most efficient way of doing it, after all by swapping alone we are making four copies. To do this more efficiently, we should just swap the gems' types and change their currently played animations. This way we'll save ourselves the copying trouble. Another thing is, that we shouldn't just swap the gems' positions right away, because that won't look very pretty. We need to make a smooth transition from the first gem's position, to the second one, for both gems of course. I've drawn a simple sketch to help you visualize what will be happening both, on the screen and in the game:


As you can see, we just move a gem to another gem's position, and at the end we just shift them back to their initial positions, with changed types. The second way of doing that would be just a reverse of that - we would swap gems' positions and types at the beginning, and then make a transistion to their initial position, but since I don't want to make you even more confused, we'll stick to the version on the sketch.

The first thing we should do is to make a function that will move the gem from one position to another, then after reaching that position to shift it back, and finally change its type. That's pretty a lot, but we can simply do these tasks one by one. First let's go to the Gem.as and let's create a new function in our Gem class. Let's call it simply move():

 
public function move(_x:Number, _y:Number, _type:int):void 
{ 
 
}

We needed to add quite a few arguments, but the function itself won't be very complicated, most of the moving will actually happen in the update(). In this function we'll just save the variables, so the gem will remember where is it supposed to move (_x, _y), where is it supposed to shift after achieving its destination, and what kind of gem should it change into, after moving back to it's original position (_type). We also need to know whether the gem's moving or not. Let's declare those variables:

 
private var startX:int;			//the x value that the gem will shift to after finishing moving 
private var startY:int;			//the y value that the gem will shift to after finishing moving 
private var destX:int; 			//the x value that the gem moves to 
private var destY:int; 			//the y value that the gem moves to 
private var destType:uint;		//the type that the gem will change into after finishing moving 
private var moving:Boolean = false;	//is the gem moving?

Let's fill our move() function now:

 
public function move(_x:Number, _y:Number, _type:int):void 
{ 
	startX = x; 
	startY = y; 
	destX = _x; 
	destY = _y; 
	destType = _type; 
	moving = true; 
}

That's it, next thing we need to do is to handle this data in the update().


Step 17: Handle the Gem's Movement

It's time to look at our update() function in the Gem class. Let's simply start by stating that something should happen when the moving is set to true:

 
override public function update():void 
{ 
	super.update(); 
			 
	if (moving) 
	{ 
				 
	} 
}

Now let's take care of the actual position changing; for that we, obviously, need to know how fast the gem should move. Let's declare a new constant:

 
private const speed:int = 450;		//speed of the moving gem, pixels/second

Alright now that's done, let's go back to our update(). We know that the gem can move in four directions (we're not supporting diagonal movement, but it could be easly added later to a finished game), it also needs to move until it reaches its final destination (destX, destY):

 
if (moving) 
{ 
	if (Math.abs(destX - x) < speed*FlxG.elapsed) 
		x = destX; 
						 
	if (Math.abs(destY - y) < speed*FlxG.elapsed) 
		y = destY; 
					 
	if (destX > x) 
		x += speed*FlxG.elapsed; 
	else if (destX < x) 
		x -= speed*FlxG.elapsed; 
						 
	if (destY > y) 
		y += speed*FlxG.elapsed; 
	else if (destY < y) 
		y -= speed*FlxG.elapsed; 
}

Since we know the destX and destY, it's straightforward to change the gem's position. We just move it in the direction of its destination until x is equal to destX, and we reapeat that for y property. Of course it can't be strictly equal to but the change in position needs to be less than the single step. Notice that we're using FlxG.elapsed, and we're multyplying it by our speed. elapsed keeps the value of how many seconds passed since the last frame. If we're multyplying our speed by it, it means that our speed now isn't 450px per frame, but 450px per second, making it frame-independent. Since we didn't import all the flixel classes to Gem, now we need to import FlxG, which we are using:

 
import org.flixel.FlxSprite; 
import org.flixel.FlxG;

So now we only need to stop, shift the gem to the initial position, and change its type.


Step 18: Finish the Gem's Movement

Firstly, let's actually move the gem only if the x and y aren't equal to destX and destY, there's no point in moving if we are already at our destination:

 
if (destX != x || destY != y) 
{ 
	if (moving) 
	{ 
		if (Math.abs(destX - x) < speed) 
			x = destX; 
						 
		if (Math.abs(destY - y) < speed) 
			y = destY; 
					 
		if (destX > x) 
			x += speed; 
		else if (destX < x) 
			x -= speed; 
						 
		if (destY > y) 
			y += speed; 
		else if (destY < y) 
			y -= speed; 
	} 
} 
else if (moving) 
{ 
}

If we are at our destination, but the moving is still set to true that means we just got there, and now is the time to do all the shifting and changing the type. We also need to change moving to false:

 
else if (moving) 
{ 
	x = destX = startX; 
	y = destY = startY; 
	moving = false; 
	type = destType; 
}

Alright, now we would need to change the animation the same way we did in the contructor. To not make our code bigger than it needs to be, let's just make a function that does that for us:

 
public function InitType():void 
{ 
	switch (type)  
	{ 
		case 0: 
			play("blue"); 
			break; 
		case 1: 
			play("green"); 
			break; 
		case 2: 
			play("orange"); 
			break; 
		case 3: 
			play("aqua"); 
			break; 
		case 4: 
			play("red"); 
			break; 
		case 5: 
			play("yellow"); 
			break; 
	} 
}

Now we can just replace the same piece of code from our constructor with simple call to our new function:

 
type = _type; 
InitType();

And finally let's add it where it was needed in the first place:

 
else if (moving) 
{ 
	x = destX = startX; 
	y = destY = startY; 
	moving = false; 
	type = destType; 
	InitType(); 
}

Step 19: Swap the Gems

Since we seem to be done with moving, we just need to try it out and see if it works. Let's go to GameState.as and change our update function a little bit:

 
for (var i:int = 0; i < gems.length; ++i) 
	gems[i].update();

Firstly, we need to be updating our gems if we want to see any of their movement. Now that we have taken care of that, let's go deeper into our update() function. If any gem is selected, let's make that a click will swap the clicked gem with the selected one, and automatically erase the selection:

 
if (selID == -1) 
{ 
	selID = id; 
	frame.visible = true; 
	frame.x = gems[selID].x; 
	frame.y = gems[selID].y; 
} 
else 
{ 
	gems[selID].move(gems[id].x, gems[id].y, gems[id].type); 
	gems[id].move(gems[selID].x, gems[selID].y, gems[selID].type); 
	selID = -1; 
	frame.visible = false; 
}

It's very easy to check whether any gem is selected, since then the selID is different than -1. If selID is equal to -1, then we're selecting a clicked gem. If we have any gem selected already, we're using our freshly made functionality to swap the gems' positions. You can see that we're swapping gem that's ID is selID - that's the selected gem, and we're swapping it with gem that has an ID equal to id - that's the gem that was clicked in this frame. Since we moved the selected gem, we need to move the other one too, so it moves to selected gem's position and gets it's type. Finally we set selID to -1, noting that there's no selection and make the selection frame invisible (since there is no gem it could point to). Let's test our project and see if it works:

 
gems[selID].move(gems[id].x, gems[id].y, gems[id].type);

When we try to swap any gems, the output window will show us an error that basically explains why did our code break. This happend, because the GameState can't access the property type in the Gem class. That is so because we declared type as a private variable, and that means we can't really access it outside of the class we declared it in. We can do two things about this, either change it from private to public or to provide a public function that would simply return the gem's type. Generally the second option is wiser to use, but since we are the only ones that work on this project, it doesn't really matter much, we can simply change private to public in our Gem.as:

 
public var type:uint;

Now let's try again and check if it's working:

Yes it does, and it does it pretty well. If you think the speed should be different, you can always set the constant we declared in Gem.as to whatever you like. Notice that for now we're not making any restrictions and you can swap any gems you want.


Step 20: Conceptualize the Clearing of Gems

Now it's time to erase a few gems from our board. For now we'll leave finding any matches for later, and will concentrate on clearing any gems we want, it'll be easy to clear up the matches later if we do this now. Of course we won't be doing anything like removing gems from the array, no, this wouldn't be too efficient and would mess up our array quite a lot. Again I've got a few sketches prepared, they'll help you understand the idea of implementing the clearing function, though this time it's not really anything complicated:



We simply scale down the gems we want to clear. We're leaving the invisible gems on the board for now. After clearing they'll need to be moved before the gems above them will fall, but we'll do that when we'll be creating our fall() function, now let's concentrate on our current task.


Step 21: Scale Down the Gems

Let's open Gem.as and declare two new variables there. We'll need to know when to scale down and how long scaling down should take place:

 
private const speed:int = 450;			//speed of the moving gem, pixels/second 
private const dieTime:Number = 0.5; 		//time taking to scale down from 1.0 to 0.0 in seconds 
		 
public var type:uint; 
		 
private var startX:int;				//the x value that the gem will shift to after finishing moving 
private var startY:int;				//the y value that the gem will shift to after finishing moving 
private var destX:int; 				//the x value that the gem moves to 
private var destY:int; 				//the y value that the gem moves to 
private var destType:uint;			//the type that the gem will change into after finishing moving 
private var moving:Boolean = false;		//is the gem moving? 
		 
private var dying:Boolean = false;  		//is the gem scaling down?

I called those two new variables dieTime and dying. dieTime holds a time of scaling the gem down and dying will show us whether we're scaling down or not. Now let's create a new method, that will simply turn the scaling down on:

 
public function die():void 
{ 
	dying = true; 
}

Now we should handle scaling the gem down.


Step 22: Handle the Scaling Down

Go to our update() function in Gem.as, and let's add new conditional statement:

 
else if (moving) 
{ 
	x = destX = startX; 
	y = destX = startY; 
	moving = false; 
	type = destType; 
	InitType(); 
} 
else if (dying) 
{ 
				 
}

Now let's simply scale the gem down, both vertically and horizontally. If its scale is lower than or equal to 0, let's stop scaling it:

 
else if (dying) 
{ 
	if (scale.x > 0) 
	{ 
		scale.x -= FlxG.elapsed/dieTime; 
		scale.y -= FlxG.elapsed/dieTime; 
	} 
	else 
	{ 
		scale.x = 0; 
		scale.y = 0; 
		dying = false; 
	} 
}

And that would be it for the gem's actions. Now the rest of work lies in game's logic.


Step 23: Clear the Gems

All right, it's time to test the clear() function. Firstly, let's comment out our selecting and let's make it clear() the gem the cursor is pointing to instead:

 
if (selID == -1) 
{ 
	gems[id].die(); 
	/* 
	selID = id; 
	frame.visible = true; 
	frame.x = gems[selID].x; 
	frame.y = gems[selID].y;*/ 
}

If you run the game now you'll notice that nothing happens upon a click. After placing a few breakpoints, they point us to the cause of this, and the root of that is here:

 
if (destX != x || destY != y) 
{ 
	if (moving) 
	{ 
		if (Math.abs(destX - x) < speed*FlxG.elapsed) 
			x = destX; 
						 
		if (Math.abs(destY - y) < speed*FlxG.elapsed) 
			y = destY; 
					 
		if (destX > x) 
			x += speed*FlxG.elapsed; 
		else if (destX < x) 
			x -= speed*FlxG.elapsed; 
						 
		if (destY > y) 
			y += speed*FlxG.elapsed; 
		else if (destY < y) 
			y -= speed*FlxG.elapsed; 
	} 
}

Because we intially didn't set destX and destY to x and y, we're getting past through first condition, but we don't make it past the second one. Let's add some code to repair that, so destX and destY will be equal to x and y, when the gem is not moving:

 
if (destX != x || destY != y) 
{ 
	if (moving) 
	{ 
		if (Math.abs(destX - x) < speed*FlxG.elapsed) 
			x = destX; 
						 
		if (Math.abs(destY - y) < speed*FlxG.elapsed) 
			y = destY; 
					 
		if (destX > x) 
			x += speed*FlxG.elapsed; 
		else if (destX < x) 
			x -= speed*FlxG.elapsed; 
						 
		if (destY > y) 
			y += speed*FlxG.elapsed; 
		else if (destY < y) 
			y -= speed*FlxG.elapsed; 
	} 
	else 
	{ 
		destX = x; 
		destY = y; 
	} 
}

Now since we won't go through the first condition, our else before if (dying) won't screw anything up. Let's try to run the project once again and see if it works:

The clearing works nicely, remember that you can change the time of scaling by changing the dieTime in Gem.as.


Step 24: Conceptualize the Spawning of New Gems

It's time to think how are we going to implement the spawning of new gems. The first thing we'll need to do is to shift the cleared gems above the board, randomize their types, and let all the gems fall down the distance that was cleared. Of course, after all the falling down we'll move them to their initial positions with changed types, the same way we did while we were swapping the gems! Here are a few sketches that show this concept:





I hope this makes it clear, make sure you understand it all because now we're going to write a method that will do this all for us.


Step 25: Prepare the Gems for Falling

Before we start writing our fall() function, let's go to the Gem.as and let's first create a function here. We'll call it recreate(), it will just change the gem's type to a random one and play the animation attached to that type:

 
public function recreate(types:int):void 
{ 
	moving = dying = false; 
	type = Math.floor(Math.random() * types); 
	InitType(); 
}

We also set moving and dying to their initial values. You should also have noticed that on the sketches not all the gem's could return to their previous position, for example cleared gems shouldn't go back above the board, but to their initial position. We'll need to make another function that will let us decide where the gem should shift to, after it succefully moved to the destination. Let's declare our function for that purpose:

 
public function moveToShift(_x:Number, _y:Number, shiftX:Number, shiftY:Number, _type:int):void 
{ 
	startX = shiftX; 
	startY = shiftY; 
	destX = _x; 
	destY = _y; 
	destType = _type; 
	moving = true; 
}

As you can see, we simply added two arguments: shiftX, shiftY, and we set startX, startY to their values instead to x, y. Since while handling the movement later on we shift our gem to the startX, startY, if we use this function, the gem will be shifted to shiftX, shiftY isntead. I hope this doesn't confuse you, it's actually pretty straightforward if you still remember the movement handling code.


Step 26: Shift the Cleared Gems

Let's go back to our GameState.as and here too, create a new function:

 
/*	 
 * fall function will let blocks fall if there is "no block" underneath  
 * them 
 *  
 *  
 * columnID - in which column does the falling occur 
 *  
 * rowID - in which row is the LAST (the lowest one) block 
 *  
 * blockCount - how many blocks (vertically) were destroyed 
 *  
 * */ 
public function fall(columnID:uint, rowID:uint, gemCount:uint):void 
{ 
 
}

We'll be taking columnID, rowID of the last block in a match (the lowest one) as an arguments, and of course we need to know how many gems are there in a match, that's what gemCount is for. Let's start from setting the cleared gems anew:

 
var last:int = columns * rowID + columnID; 
			 
//setting all cleared blocks and making them into new ones 
for (var i:int = 0; i < gemCount; ++i) 
{ 
	gems[last - i * columns].y = begY - (i+1) * offsetY; 
	gems[last - i * columns].scale = new FlxPoint(1.0, 1.0); 
	gems[last - i * columns].recreate(types); 
}

Nothing much to explain here, as you can see we calculate the ID of the last gem (the one in the lowest row) and store it in the last variable. After that, we shift cleared gems above the board, scale them back to the full size and use our freshly created recreate() function to change cleared gems' types. Now let's actually move the gems to their destined positions:


Step 27: Let the Gems Fall

Let's start from a loop. The loop we're using counts from the last cleared gem up to the one in row 0. If we clear a gem in one column, the gems will fall only in that column:

 
for (var n:int = last; n >= 0 ; n -= columns) 
{ 
}

Now we can go on and move the gems. We simply move the current gem we're processing (n) down the distance equal to the number of cleared gems (gemCount), multiplied by the spaceY, which makes them basically replace a gem gemCount rows below. If you still can't imagine it then use an example, if we're clearing a match of three gems, every gem above those matched gems will have to move 3 rows below their own row. Remember that we shift the gem back later on, so the gem has to change the type to the type of the gem that would replace it. The gem should change the type to the type of the gem gemCount rows above it. In example, if we matched three gems, the gem would need to change its type to that of the gem three rows above it. Here's all that explanation put into code:

 
for (var n:int = last; n >= 0 ; n -= columns) 
{ 
	gems[n].move(gems[n].x, gems[n].y + gemCount * offsetY, 
			gems[n - gemCount * columns].type); 
}

Step 28: Handle Special Cases

But that's just the tip of the iceberg. We need to cover more cases, because in some this solution won't work. For example the cleared gems, in this case they'll need to be shifted not above the board, which is their current position now, but to their former position. We created the function to help us do that earlier, now we simply need to use it. So, firstly, we need to differentiate the cleared gems from the others. That's pretty simple, we know the ID of the last cleared gem, so we also know that every gem up to gemCount rows - 1 above it, is also cleared:

 
for (var n:int = last; n >= 0 ; n -= columns) 
{ 
	if (n >= last - (gemCount - 1) * columns) 
	{ 
		gems[n].moveShift(gems[n].x, gems[n].y + gemCount * spaceY, 
						gems[n].x,begY + Math.floor(n / columns) * spaceY, 
						gems[n - gemCount * columns].type); 
	{ 
	else 
	{ 
		gems[n].move(gems[n].x, gems[n].y + gemCount * spaceY, 
				gems[n - gemCount * columns].type); 
	} 
}

Now notice, that we can't just change types of all of our gems that way. That is simply because some gems... have no gems above them! And I'm not speaking about the gems that are cleared and their positions are the highest ones. I'm speaking about the gems that reside in the top rows of the board. For example, a gem from row 0 can't switch type to that of the gem in row -1, because such a gem doesn't exist! The highest gems need to get their new type from the cleared gems, and IDs of the cleared gems are actually quite far away from them. So now we need to differentiate the gems that can get their new IDs the straightforward way between those that can't. It's pretty simple because those that can't are always the lowest IDs. Their IDs must be lower than gemCount*columns:

 
for (var n:int = last; n >= 0 ; n -= columns) 
{ 
	if (n >= gemCount * columns) 
	{ 
		if (n >= last - (gemCount - 1) * columns) 
		{ 
			gems[n].moveShift(gems[n].x, gems[n].y + gemCount * spaceY, 
						gems[n].x,begY + Math.floor(n / columns) * spaceY, 
						gems[n - gemCount * columns].type); 
		{ 
		else 
		{ 
			gems[n].move(gems[n].x, gems[n].y + gemCount * spaceY, 
					gems[n - gemCount * columns].type); 
		} 
	else 
	{ 
	} 
}

Step 29: Handle More Special Cases

We are almost there! Note that the gems in highest rows can also be cleared, that means we'll need a similar structure as to the one above:

 
for (var n:int = last; n >= 0 ; n -= columns) 
{ 
	if (n >= gemCount * columns) 
	{ 
		if (n >= last - (gemCount - 1) * columns) 
		{ 
			gems[n].moveShift(gems[n].x, gems[n].y + gemCount * spaceY, 
						gems[n].x,begY + Math.floor(n / columns) * spaceY, 
						gems[n - gemCount * columns].type); 
		{ 
		else 
		{ 
			gems[n].move(gems[n].x, gems[n].y + gemCount * spaceY, 
					gems[n - gemCount * columns].type); 
		} 
	else 
	{ 
		if (n >= last - (gemCount - 1) * columns) 
		{ 
 
		} 
		else 
		{ 
 
		} 
	} 
}

Now you can see that we need another helper variable that will help us count the gems from the last one upwards. Let's declare it; since it will help us iterate it can be a one letter name, for example h. So, we'll use it to help us calculate which gem (that can't get its new type from any gem above) should get its type from which cleared gem. It's pretty easy to imagine, that the highest gem should get its type from the highest cleared gem, and the last gem (from those that can't get type from above!) should ultimately get its type from the last cleared gem. Small sketch here:


Enough explanations, let's code it:

 
var h:int = 0; 
for (var n:int = last; n >= 0 ; n -= columns) 
{ 
	if (n >= gemCount * columns) 
	{ 
		if (n >= last - (gemCount - 1) * columns) 
		{ 
			gems[n].moveShift(gems[n].x, gems[n].y + gemCount * spaceY, 
						gems[n].x,begY + Math.floor(n / columns) * spaceY, 
						gems[n - gemCount * columns].type); 
		{ 
		else 
		{ 
			gems[n].move(gems[n].x, gems[n].y + gemCount * spaceY, 
					gems[n - gemCount * columns].type); 
		} 
	else 
	{ 
		if (n >= last - (gemCount - 1) * columns) 
		{ 
			gems[n].moveToShift(gems[n].x, gems[n].y + gemCount * spaceY, 
						gems[n].x, begY + Math.floor(n / columns) * spaceY, 
						gems[last - h * columns].type);		 
		} 
		else 
		{ 
			gems[n].move(gems[n].x, gems[n].y + gemCount * spaceY, 
					gems[last - h * columns].type); 
		} 
		++h; 
	} 
}

We, of course, had to increase the value of h so it would point to the gem that's higher and higher above (which has lower and lower ID!) with every loop step. We're done!


Step 30: Test the Falling

Let's use similar trick we used for checking out the clearing. Let's go to the update() function in our GameState.as, and let's kill, say, three gems and immediately after that call our fall() function. Note that since we don't really give any time for the scaling down animation, the gems will be immediately respawned. It won't look very good, but for testing purposes, it's all right to do so!

 
if (selID == -1) 
{ 
	gems[id].die(); 
	gems[id - columns].die(); 
	gems[id - columns * 2].die(); 
	fall(columnID, rowID, 3); 
	/* 
	selID = id; 
	frame.visible = true; 
	frame.x = gems[selID].x; 
	frame.y = gems[selID].y;*/ 
}

We're clearing the gem we clicked on and also two gems above it. That means we can't really click on any gem from first or second row, because then we'll try to kill a gem that doesn't exist. Let's quickly add a condition to prevent an error in case we click too high anyway:

 
if (selID == -1) 
{ 
	if (rowID >= 2) 
	{ 
		gems[id].die(); 
		gems[id - columns].die(); 
		gems[id - columns * 2].die(); 
		fall(columnID, rowID, 3); 
	} 
	/* 
	selID = id; 
	frame.visible = true; 
	frame.x = gems[selID].x; 
	frame.y = gems[selID].y;*/ 
}

Now there shouldn't be any problems, if you accidentally click too high, nothing bad will happen. That's all it was about! We can test the game now:

It works!


Step 31: Create Check for Match Method

Now it's time to write the function that will recognise a match. It won't be as complicated as our fall() function, so you can rest assured. This function will count how many gems of the same type there are above the gem we swapped, below it, and to either side of it. Let's start by declaring the function in GameState.as:

 
public function preCheck(clears:Array, id:uint, type:uint, skipID:uint):Boolean 
{ 
}

clears will be used as a reference to an array that will hold variables we want to turn into the numbers of gems of the same type as our gem to the sides of it. For example if we declared a new array and called it someArray, and if we passed this array as an argument to this function, the function would change the values of someArray[0], someArray[1], someArray[2], someArray[3] to that of the numbers of gems with the same type as our checked gem to it's right, left, down and up respectively.

clears[0] will hold how many gems of the same type is on the right side of the checked gem, clears[1] on the left side, clears[2] below the checked gem and clears[3] above it. I hope this doesn't confuse you, it's actually easier to get this by simply using the function and watching what's happening!

The next arguments are id, which basically is an ID of our checked gem, type that's basically a type of our checked gem (note that because we'll need to check() for match before we swap the gems, we need to use a type of the gem we swap with), and finally skipID which tells which ID should we skip while checking whether the next gem is of the same type or not (that's also because we need to check() for match before we actually swap the gems, since we're checking for the type of the gem we swap with, we can't include that gem as the gem of the same type, because it's type is going to be changed at the end of the swap! It could work only if both swapped gems were of the same type, but swapping those doesn't make much sense does it? It certainly couldn't produce a new match!).

Note that we're also returning a Boolean value in this function. We'll return true if there is a match, and false if there isn't. Oh, and remember that we had to use an array, because if we used simple type like int, uint, number, then it wouldn't be passed to this functions as a reference, but as a copy.


Step 32: Check the Gems on the Right

Now since everything is explained we can get to writing. Firstly we should set all clear variables to 0, so we won't accidentally get in any kind of trouble:

 
public function check(clears:Array, id:uint, type:uint, skipID:uint):Boolean 
{ 
	/* 
	* 0 - RIGHT 
	* 1 - LEFT 
	* 2 - DOWN 
	* 3 - UP 
	*/ 
	clears[3] = clears[2] = clears[1] = clears[0] = 0;

Secondly, let's check for the gems that are on our right side, remember that since we're checking IDs of the gems, we also need to make sure that we don't go to the next row! If we remember that, then the rest is simple, we're checking whether the gem to the right is of the type we're searching for. If that's correct, then we increment our clears[0] variable and check the next gem, if it's again of the same type, we increment clears[0] yet again, and so on, and so on. Of course if we stumble on the ID we need to skip then we simply break the loop:

 
public function check(clears:Array, id:uint, type:uint, skipID:uint):Boolean 
{ 
	/* 
	* 0 - RIGHT 
	* 1 - LEFT 
	* 2 - DOWN 
	* 3 - UP 
	*/ 
	clears[3] = clears[2] = clears[1] = clears[0] = 0; 
 
	//going right, checking if we are in the same row 
	for (var i:int = 1; Math.floor((id + i)/columns) == Math.floor((id)/columns); ++i)  
	{ 
		if (gems[id + i].type == type && id + i != skipID) 
			++clears[0]; 
		else 
			break; 
	}

The condition to continue our loop is that the row of the gem with the ID we're checking ((id + i)) is equal to our checked gem's ID (id). It's pretty straightforward.


Step 33: Check for the Rest of Directions

Now it's time to check the rest of directions. For the left the code is nearly identical, but now since we're going left, we'll need to decrease id, instead of increasing it with each checked gem:

 
public function check(clears:Array, id:uint, type:uint, skipID:uint):Boolean 
{ 
	/* 
	* 0 - RIGHT 
	* 1 - LEFT 
	* 2 - DOWN 
	* 3 - UP 
	*/ 
	clears[3] = clears[2] = clears[1] = clears[0] = 0; 
 
	//going right, checking if we are in the same row 
	for (var i:int = 1; Math.floor((id + i)/columns) == Math.floor((id)/columns); ++i)  
	{ 
		if (gems[id + i].type == type && id + i != skipID) 
			++clears[0]; 
		else 
			break; 
	} 
 
	//nearly the same loop, but we will go left this time 
	for (var j:int = 1; Math.floor((id - j)/columns) == Math.floor((id)/columns); ++j)  
	{ 
		if (gems[id - j].type == type && id - j != skipID) 
			++clears[1]; 
		else 
			break; 
	}

After that, we're going down, so we'll need to change rows. If this is the case, we need to be careful not to step out from the board's bounds!

 
public function check(clears:Array, id:uint, type:uint, skipID:uint):Boolean 
{ 
	/* 
	* 0 - RIGHT 
	* 1 - LEFT 
	* 2 - DOWN 
	* 3 - UP 
	*/ 
	clears[3] = clears[2] = clears[1] = clears[0] = 0; 
 
	//going right, checking if we are in the same row 
	for (var i:int = 1; Math.floor((id + i)/columns) == Math.floor((id)/columns); ++i)  
	{ 
		if (gems[id + i].type == type && id + i != skipID) 
			++clears[0]; 
		else 
			break; 
	} 
 
	//nearly the same loop, but we will go left this time 
	for (var j:int = 1; Math.floor((id - j)/columns) == Math.floor((id)/columns); ++j)  
	{ 
		if (gems[id - j].type == type && id - j != skipID) 
			++clears[1]; 
		else 
			break; 
	} 
 
	//now we're going down, we need to check whether we are not going out of lower bounds 
	for (var z:int = columns; id + z < gems.length; z += columns)  
	{ 
		if (gems[id + z].type == type && id + z != skipID) 
			++clears[2]; 
		else 
			break; 
	}

Finally let's check for the gems above. We still need to be careful not to step out from our board's bounds:

 
public function check(clears:Array, id:uint, type:uint, skipID:uint):Boolean 
{ 
	/* 
	* 0 - RIGHT 
	* 1 - LEFT 
	* 2 - DOWN 
	* 3 - UP 
	*/ 
	clears[3] = clears[2] = clears[1] = clears[0] = 0; 
 
	//going right, checking if we are in the same row 
	for (var i:int = 1; Math.floor((id + i)/columns) == Math.floor((id)/columns); ++i)  
	{ 
		if (gems[id + i].type == type && id + i != skipID) 
			++clears[0]; 
		else 
			break; 
	} 
 
	//nearly the same loop, but we will go left this time 
	for (var j:int = 1; Math.floor((id - j)/columns) == Math.floor((id)/columns); ++j)  
	{ 
		if (gems[id - j].type == type && id - j != skipID) 
			++clears[1]; 
		else 
			break; 
	} 
 
	//now we're going down, we need to check whether we are not going out of lower bounds 
	for (var z:int = columns; id + z < gems.length; z += columns)  
	{ 
		if (gems[id + z].type == type && id + z != skipID) 
			++clears[2]; 
		else 
			break; 
	} 
 
	//and finally up, this time we just check for upper bound 
	for (var m:int = columns; id - m >= 0; m += columns)  
	{ 
		if (gems[id - m].type == type && id - m != skipID) 
			++clears[3]; 
		else 
			break; 
	}

That's it! Now we know how many gems are at every direction of our gem (excluding diagonal ones).


Step 34: Check for Match

Now we need to return true or false, depending whether there is a match of at least three gems either in a row or a column:

 
//return true if there is a streak of 3 or more gems along any axis 
if (clears[3]  + clears[2] >= 2 || clears[1] + clears[0] >= 2) 
	return true; 
else 
	return false;

And we're done! Or we would be, if not a one little detail that would come out to the daylight sooner or later... to notice it, let's consider such a special case:


As you can see, with our function all four gems would be considered as matched! The function would return true, and clears[0] would have a value of 1, and later on we would clear that additional gem up, unfortunately. To repair that we should simply set the variables back to 0 if there is no match on their axis:

 
//if there are fewer than 3 gems in a row on either of the axes, we don't care about them 
if (clears[3] + clears[2] <= 1) 
{ 
	clears[3] = 0; 
	clears[2] = 0; 
} 
if (clears[1] + clears[0] <= 1) 
{ 
	clears[1] = 0; 
	clears[0] = 0; 
} 
 
//return true if there is a streak of 3 or more gems at any axis 
if (clears[3]  + clears[2] >= 2 || clears[1] + clears[0] >= 2) 
	return true; 
else 
	return false;

This time we're really done! Let's have a look at our final function:

 
public function check(clears:Array, id:uint, type:uint, skipID:uint):Boolean 
{ 
	/* 
	* 0 - RIGHT 
	* 1 - LEFT 
	* 2 - DOWN 
	* 3 - UP 
	*/ 
	clears[3] = clears[2] = clears[1] = clears[0] = 0; 
 
	//going right, checking if we are in the same row 
	for (var i:int = 1; Math.floor((id + i)/columns) == Math.floor((id)/columns); ++i)  
	{ 
		if (gems[id + i].type == type && id + i != skipID) 
			++clears[0]; 
		else 
			break; 
	} 
 
	//nearly the same loop, but we will go left this time 
	for (var j:int = 1; Math.floor((id - j)/columns) == Math.floor((id)/columns); ++j)  
	{ 
		if (gems[id - j].type == type && id - j != skipID) 
			++clears[1]; 
		else 
			break; 
	} 
 
	//now we're going down, we need to check whether we are not going out of lower bounds 
	for (var z:int = columns; id + z < gems.length; z += columns)  
	{ 
		if (gems[id + z].type == type && id + z != skipID) 
			++clears[2]; 
		else 
			break; 
	} 
 
	//and finally up, this time we just check for upper bound 
	for (var m:int = columns; id - m >= 0; m += columns)  
	{ 
		if (gems[id - m].type == type && id - m != skipID) 
			++clears[3]; 
		else 
			break; 
	} 
 
	//if there's less than 3 gems in a row on either of the axes, we don't care about them 
	if (clears[3] + clears[2] <= 1) 
	{ 
		clears[3] = 0; 
		clears[2] = 0; 
	} 
	if (clears[1] + clears[0] <= 1) 
	{ 
		clears[1] = 0; 
		clears[0] = 0; 
	} 
 
	//return true if there is a streak of 3 or more gems at any axis 
	if (clears[3]  + clears[2] >= 2 || clears[1] + clears[0] >= 2) 
		return true; 
	else 
		return false; 
}

Step 35: Check, Swap, Clear & Fall Part 1

It's the time to gather all the functionality we worked on, and to use it to make the game playable. First thing we should do is to go to our update() function in GameState.as, and return it to the previous state, that is, swapping the gems after clicking on them:

 
if (selID == -1) 
{ 
	selID = id; 
	frame.visible = true; 
	frame.x = gems[selID].x; 
	frame.y = gems[selID].y; 
} 
else 
{ 
	gems[selID].move(gems[id].x, gems[id].y, gems[id].type); 
	gems[id].move(gems[selID].x, gems[selID].y, gems[selID].type); 
	selID = -1; 
	frame.visible = false; 
}

Now let's add some restrictions to the swapping, the player should be able to swap only neighboring gems. The selected gem's ID can be:

  • Higher by 1, but the gem must still be in the same row
  • Lower by 1, but again, the gem must still be in the same row
  • Higher by columns
  • Lower by columns
 
if (selID == -1) 
{ 
	selID = id; 
	frame.visible = true; 
	frame.x = gems[selID].x; 
	frame.y = gems[selID].y; 
} 
else if (((selID == id - 1 || selID == id + 1) && rowID == Math.floor(selID / columns)) 
	|| selID == id + columns || selID == id - columns) 
{ 
	gems[selID].move(gems[id].x, gems[id].y, gems[id].type); 
	gems[id].move(gems[selID].x, gems[selID].y, gems[selID].type); 
	selID = -1; 
	frame.visible = false; 
}

Finally, we also should deselect the gem if the player didn't click on any of the neighboring gems:

 
if (selID == -1) 
{ 
	selID = id; 
	frame.visible = true; 
	frame.x = gems[selID].x; 
	frame.y = gems[selID].y; 
} 
else if (((selID == id - 1 || selID == id + 1) && rowID == Math.floor(selID / columns)) 
	|| selID == id + columns || selID == id - columns) 
{ 
	gems[selID].move(gems[id].x, gems[id].y, gems[id].type); 
	gems[id].move(gems[selID].x, gems[selID].y, gems[selID].type); 
	selID = -1; 
	frame.visible = false; 
} 
else 
{ 
	border.visible = false; 
	selID = -1; 
}

And we're done with creating restrictions. Let's run the game and see if we certainly can swap only the neighboring gems:

That's right, everything works just as planned.


Step 36: Check, Swap, Clear & Fall Part 2

Now that we know the player clicked on a gem that can be swapped (it's directly next to, above or below it), we need to check if these gems after being swapped will create a match. We'll use our check() function for that. Now let's create two arrays that will hold clears[3], clears[2], clears[1], clears[0] and two booleans that will hold the returned value of the check() function, for each swapped gem:

 
private var stCheck:Boolean = false;//boolean that holds the value returned by check() for the first swapped gem 
private var ndCheck:Boolean = false;//boolean that holds the value returned by check() for the second swapped gem 
private var st:Array = new Array(); //array that will hold numbers of cleared gems to the sides of the first swapped gem 
private var nd:Array = new Array(); //array that will hold numbers of cleared gems to the sides of the second swapped gem

Let's also push four uints into them in our constructor:

 
	frame.visible = false; 
			 
	for (var j:int = 0; j < 4; ++j) 
	{ 
		st.push(0); 
		nd.push(0); 
	} 
}

Step 37: Check, Swap, Clear & Fall Part 3

Let's go back to update() and check for a match before we swap the gems. We also need to prevent our selID changing to -1 immediately (if we found a match), because we will still need it:

 
else if (((selID == id - 1 || selID == id + 1) && rowID == Math.floor(selID / columns)) 
	|| selID == id + columns || selID == id - columns) 
{ 
	stCheck = check(st, selID, gems[id].type, id); 
	ndCheck = check(nd, id, gems[selID].type, selID); 
					 
	if (stCheck || ndCheck) 
	{ 
		gems[selID].move(gems[id].x, gems[id].y, gems[id].type); 
		gems[id].move(gems[selID].x, gems[selID].y, gems[selID].type); 
	} 
	else			 
		selID = -1; 
 
	frame.visible = false; 
}

To make things more elegant, we assign the returned value from check() to the variables we created for this purpose. Then simply before swapping the gems we check whether we'll get any matches after swapping. If we do, then we're letting the gems move, if not, we do nothing but deselect the gem.


Step 38: Check, Swap, Clear & Fall Part 4

Now we would have to clear the gems after swapping them, but we can't really do it right away. We need a timer that will let us handle clearing a bit later than immidiately, and for that we also need a few additional variables. Some of these will indicate that we're waiting until we can handle for example clearing the gems and some of these will be constants that will hold a time we need to wait until we can perform a specific actions. Let's add them to the top of our GameState.as:

 
private const swapTime:Number = 1.0; //time needed for swapping the gems to finish 
private const clearTime:Number = 1.0; //time needed for clearing the gems to finish 
private const fallTime:Number = 1.0; //time needed for falling gems to finish
 
private var swapping:Boolean = false;	//are the gems swapping? 
private var clearing:Boolean = false;	//are the gems being cleared? 
private var falling:Boolean = false;	//are the gems falling? 
 
private var timer:Number = 0.0;		//all prupose timer

Step 39: Check, Swap, Clear & Fall Part 5

Again, let's go back to our update() function in GameState.as, and when we're moving the gems, let's set swapping to true:

 
if (stCheck || ndCheck) 
{ 
	gems[selID].move(gems[id].x, gems[id].y, gems[id].type); 
	gems[id].move(gems[selID].x, gems[selID].y, gems[selID].type); 
	swapping = true; 
}

Now that our game knows that we're swapping the gems, let's handle it separately:

 
if (swapping) 
{ 
	timer += FlxG.elapsed; 
	if (timer >= swapTime) 
	{ 
		swapping = false; 
		timer = 0.0; 
	} 
}

When the swapping is finished, we should also start clearing the matched gems. We shouldn't put too much code in our update() function because it will grow cumbersome, so let's declare new function named clear(), which we'll use here later on.


Step 39: Check, Swap, Clear & Fall Part 6

Let's start creating our function. First step would be declaring it, let's say, above the update() function (we're still in GameState.as):

 
public function clear():void 
{ 
 
}

There are three cases that we should be aware of: the first one is, that both swapped gems appeared to be in a match after the swap, and we need to clear both matches; the second is, only the first gem did find a match; and the final, third case is that only the second gem has found a match:

 
public function clear():void 
{ 
	if (stCheck && ndCheck) 
	{ 
		 
	} 
	else if (stCheck) 
	{ 
			 
	}	 
	else 
	{ 
 
	} 
}

Now our only task is to make all the gems in a match to die(). That's one creepy name for a function now that I think of it... anyway, let's start from the most difficult case, the first one. For simplicity, let's create new variables that we'll be able to use instead of st[0], st[1] and so on, because that would be quite uncomfortable:

 
public function clear():void 
{ 
	if (stCheck && ndCheck) 
	{ 
		var clearRight:uint = st[0]; 
		var clearLeft:uint = st[1]; 
		var clearDown:uint = st[2]; 
		var clearUp:uint = st[3]; 
	} 
	else if (stCheck) 
	{ 
			 
	}	 
	else 
	{ 
 
	} 
}

Now we can use clearRight, clearLeft, clearDown, clearUp, which is quite a bit easier. The first thing we need to do, is to know whether we've got to clear the gems in horizontal axis, vertical one, or maybe in both. We do that the similar way we checked whether there are any matches or not:

 
public function clear():void 
{ 
	if (stCheck && ndCheck) 
	{ 
		var clearRight = st[0]; 
		var clearLeft = st[1]; 
		var clearDown = st[2]; 
		var clearUp = st[3]; 
 
		if (clearLeft + clearRight >= 2 && clearUp + clearDown >= 2) 
		{ 
 
		} 
		else if (clearUp + clearDown >= 2) 
		{ 
		 
		} 
		else 
		{ 
 
		} 
	} 
	else if (stCheck) 
	{ 
			 
	}	 
	else 
	{ 
 
	} 
}

Here again, let's start from the first case. So basically, we need to clear all the gems from top to the bottom and from left to right, but we need to remember that we can't (or rather shouldn't, because nothing bad will really happen here...) make any gem die() twice. That's why while going from top to bottom, we'll simply skip the gem we checked earlier:

 
public function clear():void 
{ 
	if (stCheck && ndCheck) 
	{ 
		var clearRight = st[0]; 
		var clearLeft = st[1]; 
		var clearDown = st[2]; 
		var clearUp = st[3]; 
 
		if (clearLeft + clearRight >= 2 && clearUp + clearDown >= 2) 
		{ 
			for (var i:int = selGem - clearUp*columns; i <= selGem + clearDown*columns; i += columns) 
			{ 
				if (i != selGem) 
					gems[i].die(); 
			} 
					 
			for (var j:int = selGem - clearLeft; j <= selGem + clearRight; ++j) 
				gems[j].die(); 
		} 
		else if (clearUp + clearDown >= 2) 
		{ 
		 
		} 
		else 
		{ 
 
		} 
	} 
	else if (stCheck) 
	{ 
			 
	}	 
	else 
	{ 
 
	} 
}

And that would do it. Now we don't have to be careful about making our gem die() twice when the match is only horizontal or only vertical, so we simply copy and paste the loops from the first case (of course we need to modify vertical loop a bit) to the other cases:

 
public function clear():void 
{ 
	if (stCheck && ndCheck) 
	{ 
		var clearRight = st[0]; 
		var clearLeft = st[1]; 
		var clearDown = st[2]; 
		var clearUp = st[3]; 
 
		if (clearLeft + clearRight >= 2 && clearUp + clearDown >= 2) 
		{ 
			for (var i:int = selGem - clearUp*columns; i <= selGem + clearDown*columns; i += columns) 
			{ 
				if (i != selGem) 
					gems[i].die(); 
			} 
					 
			for (var j:int = selGem - clearLeft; j <= selGem + clearRight; ++j) 
				gems[j].die(); 
		} 
		else if (clearUp + clearDown >= 2) 
		{ 
			for (var n:int = selGem - clearUp*columns; n <= selGem + clearDown*columns; n += columns) 
				gems[n].die(); 
		} 
		else 
		{ 
			for (var m:int = id - clearLeft; m <= id + clearRight; ++m) 
				gems[m].die(); 
		} 
	} 
	else if (stCheck) 
	{ 
			 
	}	 
	else 
	{ 
 
	} 
}

Finished!


Step 40: Check, Swap, Clear & Fall Part 7

Note that we did clearing only for the first gem (which ID is selGem). We would need to do the same thing for the second one, and in the two other cases we would have to copy and paste them separately as well. Because that would generate too much code, let's wrap up what we wrote up to this moment into one single pretty function:

 
public function kill(first:Boolean):void 
{ 
	var clearRight:uint; 
	var clearLeft:uint; 
	var clearDown:uint; 
	var clearUp:uint; 
	var id:uint; 
	 
	if (first) 
	{ 
		id = selID; 
		clearRight = st[0]; 
		clearLeft = st[1]; 
		clearDown = st[2]; 
		clearUp = st[3]; 
	} 
	else 
	{ 
		id = swapID; 
		clearRight = nd[0]; 
		clearLeft = nd[1]; 
		clearDown = nd[2]; 
		clearUp = nd[3]; 
	} 
	 
	if (clearLeft + clearRight >= 2 && clearUp + clearDown >= 2) 
	{ 
		for (var i:int = id - clearUp*columns; i <= id + clearDown*columns; i += columns) 
		{ 
			if (i != id) 
				gems[i].die(); 
		} 
		 
		for (var j:int = id - clearLeft; j <= id + clearRight; ++j) 
			gems[j].die(); 
	} 
	else if (clearUp + clearDown >= 2) 
	{ 
		for (var n:int = id - clearUp*columns; n <= id + clearDown*columns; n += columns) 
			gems[n].die(); 
	} 
	else if (clearLeft + clearRight >= 2) 
	{ 
		for (var m:int = id - clearLeft; m <= id + clearRight; ++m) 
			gems[m].die(); 
	} 
}

I named the function kill() because we're making gems die() after all. The only argument is, whether we're clearing match for the first gem, or for the second one. If argument (first) is set to true, then the match for the first gem is cleared, if it's set to false then the match for the second. Basing on that, we make clearLeft, clearRight, clearUp, clearDown take their values either from the st or nd array. We also change the id of the gem we're checking the match for. One little problem arises, as you can see, we have ID for a first gem (which is selID), but we didn't save the id of the second gem! Temporarily I named it swapID (there isn't even such a variable yet), and we're going to declare it now! And while we are at it, we should go back to update() and save id of the swapped gem to this variable:

 
private var selID:int = -1; //selID holds an ID of a selected gem, -1 = no gem selected 
private var swapID:int = -1;//id of the gem that got swapped with selID
 
if (stCheck || ndCheck) 
{ 
	swapID = id; 
	gems[selID].move(gems[id].x, gems[id].y, gems[id].type); 
	gems[id].move(gems[selID].x, gems[selID].y, gems[selID].type); 
	swapping = true; 
}

And we're done with this!


Step 41: Check, Swap, Clear & Fall Part 8

Go back to the clear() function, and let's fill the missing space with our freshly created kill() function:

 
public function clear():void 
{ 
	if (stCheck && ndCheck) 
	{ 
		kill(true); 
		kill(false); 
	} 
	else if (stCheck) 
	{ 
		kill(true);	 
	}	 
	else 
	{ 
		kill(false); 
	} 
}

And let's not forget to indicate that we're now in the middle of clearing, so the animation (scaling down) can safely execute itself:

 
public function clear():void 
{ 
	if (stCheck && ndCheck) 
	{ 
		kill(true); 
		kill(false); 
	} 
	else if (stCheck) 
	{ 
		kill(true);	 
	}	 
	else 
	{ 
		kill(false); 
	} 
	 
	clearing = true; 
}

Step 42: Check, Swap, Clear & Fall Part 9

Finally, let's put clear() function into swap handling, so we can see if the function we worked on so hard is working properly:

 
if (swapping) 
{ 
	timer += FlxG.elapsed; 
	if (timer >= swapTime) 
	{ 
		swapping = false; 
		clear(); 
		timer = 0.0; 
		selID = -1; 
	} 
} 
else if (released && mouseX > begX && mouseX < begX + columns * spaceX 
	&& mouseY > begY && mouseY < begY + rows * spaceY)

We need to set selID to -1, since after clearing there should be no gem selected. We also should prohibit the player from selecting any gems while we're handling the clearing, that's why we added an else just before the condition for handling a user input. Let's run our game and see if gems are getting cleared properly after swapping:

Feels more and more like an actual game! Although the new gems don't fall yet, we can see our check() function in action. The proof that it's working properly, is the proper working of the clear() function. :)


Conclusion

The clearing of gems upon finding a match finishes the first part of this tutorial.

We started out while having absolutely nothing under our hand, but now we have plenty of functionality to use to complete the game. We began by rendering the board of gems on the screen, then we slowly made the game more playable by adding possibility of swapping the gems, then to clear them from board and spawn new ones. Lastly we created the functionality to be able to check whether the gems are in a match, and we used it in the game so we can actually clear the whole matches instead of random gems.

But all that is just a half of the work that's needed to create a playable game! I encourage you to come back and finish this project with the second part. Thanks for reading!