It’s not a secret in a lot of cases games with a simple and immediate gameplay become a blockbuster.
One example of these kind of games is the match 3 game, which generated absolutely top selling games like Bejeweled, Candy Crush Saga and Farm Heroes Saga among the others, although my all-time favourite is Puzzle Quest.
In this tutorial, we’ll learn the basics of these games using some basic concepts and a pseudo-code which will allow you to create your match 3 game on every platform in every language. Then, I’ll show you some examples made with the most important languages and frameworks.
AN ARRAY TO RULE THEM ALL
Believe or not, the whole game is built on an array. There’s no physics, no collisions, no bullets, nothing. It’s just you and an array. Every item in the array has a value which represents the gem (or the fruit, or the animal, or…) placed in the respective position.
The first thing it comes to mind is a 2-dimension array, since the game has rows and columns, but since I want this tutorial to be as much adaptable to any language as I can, we will be using a simple, basic, one dimension array.
Why are we doing this? Because different languages has different way to initialize and handle multi dimensional arrays, while the basics of single dimension arrays are quite the same.
This way, the classic 8×8 match 3 game field can be represented with an array just like this:
In the picture, you can see every cell has an index going from 0 to 63. Array indexes are always zero based, which means the first element starts with an index at zero.
Then, any array element accessible with the above indexes, must have a value. Since we don’t want to fill an array with eggs and apples, we are going to assign each symbol a different number, such as 4 for an apple, 5 for an egg, 1 for the strawberry and so on, as you can see in this picture:
I don’t want symbols to be zero based because some languages working with frames such as AS3 refer to frame 1 as the first frame. Since it’s very likely you’ll want to place each symbol in a different frame, starting with 1 is the best thing to do.
A good definition of constants involved in the game will make our life easier when it comes the time to make some changes to the game engine. Let’s see the constants we should use in a match 3 game:
TILESIZE: we know each tile is a square, now we should decide how many pixels should be the side of the square.
FIELDSIZE: most match 3 games are built on a 8×8 field, but you can make it bigger of smaller if you want. FIELDSIZE will store the size of the field, in tiles. Remember you don’t have to use all tiles in your field, most Farm Heroes Saga levels only use a small portion of the entire field size, but they all are built on a 8×8 field.
This way, FIELDSIZE*TILESIZE will return game field width and height, in pixels.
TILETYPES: here we will store the amount of tile/fruit/gem types used in the game. If we have 8 different jewels in the game, then this is the number I am talking about. Obviously you haven’t to use all tile types in each level.
OFFSETX and OFFSETY: normally the tiles aren’t placed in the upper left corner of the stage, but in a more convenient position. That’s how OFFSETX and OFFSETY come into play: assuming each tile has its registration point in the upper left corner, they represent the distance between the first tile registration point and the upper left corner of the stage.
SOME BASIC USEFUL FUNCTIONS
This first part will end with some useful functions to make our life easier when coding a match 3 game.
rowNumber(i): given a tile with array index i, will return the number of the row, which is
Once we know a the row, we have to know the column, with
colNumber(i) which basically works in the same way, operating on columns:
isHorizontalMatch(i) will say if a tile with array index i is part of an horizontal match.
Basically an horizontal match can be done from left to right or from right to left, but to simplify the whole process we assume an horizontal match can be only done from right to left.
That is, the first two leftmost tiles of every column can’t be used to determine if they are part of a match, because there aren’t enough tiles on the left. We will start checking from the 3rd tile of each column, so the pseudocode is:
return colNumber(i)>=2 and array[i]==array[i-1] and array[i] == array[i-2]
The same concept applies to vertical matches:
isVerticalMatch(i): again, we suppose a match can only be made from bottom to top, so we will exclude the first two topomost rows.
return rowNumber(i)>=2 and array[i]==array[i-FIELDSIZE] and array[i] == array[i-2*FIELDSIZE]
And now we also know if we have horizontal or vertical matches.
That’s all at the moment, next time I’ll show you how to do it in at least a couple of different languages.