All colleagues, hello.
I am a beginner FlashGame developer. I liked the lesson on creating a game on the mechanics of match-three. I understood the lesson myself the first time, but why did I do the translation? First of all, I hope this will help someone who is not good at English. Secondly, the translation allowed me to parse the entire code by bone, very carefully. And thirdly, starting from this mechanics you can start creating the mach-3 of your dreams. And it doesn't have to be in Flash.
Before I begin, I will issue a couple of agreements. The lesson is taken by me from the book Gary Rosenzweig -
"ActionScript 3.0 Game Programming University" . I already wrote in my personal blog that not all of our flash developers have a positive attitude towards this book. The translation may seem a bit wooden and not very pleasant by ear. Some words, expressions may have more appropriate analogues in Russian. If this is critical, correct. The program uses an additional class PointBurst. I will not describe it now, but most likely I will do it in a personal blog, because This is quite an interesting class. Just until we know that this class gives the effect of pop-up points in a certain place.
And finally, I did not invent anything new, but simply made a translation, i.e. all thanks to the author of the book Gary Rosenzweig.

')
Field (board) = game board where chips are located, visual display
Chip (piece) = an element that we combine with others.
Line (match) = row or column, a sequence of at least 3 chips of the same type.
Grid (grid) = 2-dimensional matrix, which in digital form duplicates the board.Review of the functionality of the game.The sequence of all events in the game includes 12 steps, where each step is a separate task.
1. Creating a randomly generated playing field.Creating a field of 8x8 with randomly spaced chips, each of which can have 7 different display options.
2. Check on line.There are some restrictions on the initial placement of chips on the field. First, the field at the start of the game should not contain lines.
3. Check on the possibility of the first move.The second limitation is to give the player at least one turn. That is, the field should not be initially unsolvable composition.
4. The player chooses 2 chips.Chips must be next to each other (vertically or horizontally) and their exchange of places occurs with the aim of forming a line.
5. Chips are swapped.Here we use the simplest animation.
6. Check on line.After the exchange we are looking for lines on the field. If the lines are not found, change the chips back in places.
7.When finding the line, reward the player with points.8. Remove lines from the field.9. We shift the upper pieces to the place of the disappeared.10. We fill the formed emptiness.11. Check again on the line. Returning to paragraph 6.After all the chips have fallen down on the empty seats, and new ones have filled the voids, you need to re-check on the line.
12. Check on the possibility of a move.Before you transfer a move to a player, you need to make sure that there are possible moves on the field.
Our clip and class MatchThreeClipThree.fla clip
is very simple. In addition to the
Arial font in the library, we have here a clip of seven frames. In the
Color layer in each frame a different type of chips. The upper
Select layer is used to frame (highlight) the selected chip and will be activated by the
visible property.

Let's take a look at the basic class definitions, while not looking at the logic of the game. Here we have only the most basic imports and nothing more.
package {
import flash. display . * ;
import flash. events . * ;
import flash. text . * ;
import flash. utils . Timer ;
We have the following constants: one indicates the type of the chip (seven different variants) and three constants for orienting the position on the screen.
public class MatchThree extends MovieClip {
// constants
// number of chip types
static const numPieces: uint = 7 ;
// distance between two chips
static const spacing: Number = 45 ;
// left indent
static const offsetX: Number = 120 ;
// indent from above
static const offsetY: Number = 30 ;
Game settings will be stored in 5 different variables. First, the grid will contain links to all the chips (
Pieces ). The grid is a two-dimensional array. Each element of the grid (
grid ) will contain an array of 8 chips (
Pieces ). All of this will look like a matrix, an 8x8 array, and we can access any chip through the
grid [x] [y] link.
The
GameSprite sprite will contain all the sprites and movie clips we create. So we will separate them from any other graphics already existing on the stage.
The variable
firstPiece will contain a link to the first clicked chip.
Two logical (
Boolean ) variables
isDropping ,
isSwapping will keep track of which chips we need to animate at the moment. The
gameScore variable will hold player points.
// game grid and necessary settings
private var grid: Array ;
private var gameSprite: Sprite;
private var firstPiece: Piece;
private var isDropping, isSwapping: Boolean ;
private var gameScore: int ;
Grid SetupThe first functions will determine the variables of the game, including the setting of the grid.
Setting game variables
To start the game, it is necessary to determine, initialize all game variables. Let's start by creating a grid (
grid ), a 8x8 two-dimensional array. Then use the
setUpGrid function to populate this array.
Note.
There is no need to fill all elements of the array with empty slots for initialization. When setting a value for any element of the array, all previous elements are filled with the value undefined . For example, in the newly created array, we assign the third element (under the index [2]) the value “My String” . The array will have a length ( length ) equal to 3, and the elements [0] and [1] will get the values undefined .Next we define the variables
isDropping ,
isSwapping and
gameScore . We also set a listener on the
ENTER_FRAME event to start all the movements of the chips in the game.
// initialize the grid () and start the game
public function startMatchThree ( ) {
// creation and initialization of the grid (grid)
grid = new Array ( ) ;
for ( var gridrows: int = 0 ; gridrows < 8 ; gridrows ++ ) {
grid. push ( new Array ( ) ) ;
}
setUpGrid ( ) ;
isDropping = false ;
isSwapping = false ;
gameScore = 0 ;
addEventListener ( Event. ENTER_FRAME , movePieces ) ;
}
Grid SetupTo create and initialize the grid (
grid ) use the loop with the condition
while (true) . In the loop, create mesh elements. Also create a
gameSprite sprite that will contain the movie clips of our chips. Then add 64 random chips using the
addPiece function. We will consider this function later, but for now we’ll just know that it adds a chip to the grid and to
gameSprite .
public function setUpGrid ( ) {
// loop until we create a playable mesh
while ( true ) {
// create sprite
gameSprite = new Sprite ( ) ;
// add 64 random chips
for ( var col: int = 0 ; col < 8 ; col ++ ) {
for ( var row: int = 0 ; row < 8 ; row ++ ) {
addPiece ( col, row ) ;
}
}
Next, check the two conditions necessary to start the game. The
lookForMatches function returns an array of found lines. This function will also be discussed later. At the moment we know that the function will return 0 if there are no lines on the screen. The
continue statement skips the rest of the loop and returns us to its beginning.
After that, call the function
lookForPossibles , which will check if there are any possible moves on the field. If there are no moves, the function will return
false , and this will mean that you cannot start the game.
If we have passed both of these conditions, the
break statement will interrupt the cycle, and we will add a
gameSprite to the scene.
// try again if there are lines on the field
if ( lookForMatches ( ) . length ! = 0 ) continue ;
// try again if there are no moves on the field
if ( lookForPossibles ( ) == false ) continue ;
// no lines, and there is a move, interrupt the cycle
break ;
}
// add sprite
addChild ( gameSprite ) ;
}
Adding chipsThe
addPiece function creates a random chip in a specific column and column, and assigns it a location on the screen.
// create a random chip, add it to the sprite and the grid
public function addPiece ( col, row: int ) : Piece {
var newPiece: Piece = new Piece ( ) ;
newPiece. x = col * spacing + offsetX;
newPiece. y = row * spacing + offsetY;
Each piece is set in the assigned row and column. To do this, use two dynamic properties of
col and
row . Also, a token has a
type property that contains a number corresponding to the type of the token and the frame in which its movie clip is located.
newPiece. col = col;
newPiece. row = row;
newPiece. type = Math . cell ( Math . random ( ) * 7 ) ;
newPiece. gotoAndStop ( newPiece. type ) ;
The
Select Movie Clip inside the
Piece clip is a frame that hovers over the clicked chip. Initially, its
visible property will be
false . Clip
Piece chips we add to
gameSprite . In order to add a chip to the grid (
grid ) we use double straight brackets
grid [col] [row] = newPiece . On each chip we will hang the listener (
click listener ). Then return the link to the chip (
Piece ). We will not use this link in the
setUpGrid function, but use it later when creating new tokens to replace voids.
newPiece. select . visible = false ;
gameSprite. addChild ( newPiece ) ;
grid [ col ] [ row ] = newPiece;
newPiece. addEventListener ( MouseEvent. CLICK , clickPiece ) ;
return newPiece;
}
Interaction with the playerWhen choosing a player to click a chip, our further actions depend on whether it is the first or second clicked chip. If it was the first chip, it stands out. If the player clicks on this chip again, the selection will be removed.
// player clicks on the chip
public function clickPiece ( event: MouseEvent ) {
var piece: Piece = Piece ( event. currentTarget ) ;
// first chip selected
if ( firstPiece == null ) {
piece. select . visible = true ;
firstPiece = piece;
// re-click on the first chip
} else if ( firstPiece == piece ) {
piece. select . visible = false ;
firstPiece = null ;
When a player clicks on a second chip, different from the first one, we must determine whether it is possible to make an exchange. First we remove the selection from the first chip. Then we will check whether they are neighbors vertically or horizontally from the second. In both cases, call the function
makeSwap . She will make an exchange and establish whether lines have formed. In any case, the variable
firstPiece is nullified (
null ) and becomes ready for the next player choice. If the chips are not neighbors, we believe that the player dropped his choice from the first chip and started from the second
// click on the second chip
} else {
firstPiece. select . visible = false ;
// same row, check the neighborhood in the column
if ( ( firstPiece. row == piece. row ) && ( Math . abs ( firstPiece. col- piece. col ) == 1 ) ) {
makeSwap ( firstPiece, piece ) ;
firstPiece = null ;
// same column, check the neighborhood in the row
} else if (( ( firstPiece. col == piece. col ) && ( Math . abs ( firstPiece. row -piece. row ) == 1 ) ) {
makeSwap ( firstPiece, piece ) ;
firstPiece = null ;
// no neighborhood, discard the first chip selection
} else {
firstPiece = piece;
firstPiece. select . visible = true ;
}
}
}
The
makeSwap function swaps chips and checks if lines are formed on the field. If not, change chips back in places. If yes, and the exchange is possible, the variable
isSwapping takes the value
true , which gives the signal to start the animation of the movement of the chips.
// start of the two chip exchange animation
public function makeSwap ( piece1, piece2: Piece ) {
swapPieces ( piece1, piece2 ) ;
// check if the exchange was successful
if ( lookForMatches ( ) . length == 0 ) {
swapPieces ( piece1, piece2 ) ;
} else {
isSwapping = true ;
}
}
To make an exchange, we must save the location of the first chips in a temporary storage. Next, move the first chip to the second one, and the second one to the saved coordinates of the first one.

After the exchange, we need to update the values in the grid.
// exchange two chips
public function swapPieces ( piece1, piece2: Piece ) {
// exchange the row and col values
var tempCol: uint = piece1. col ;
var tempRow: uint = piece1. row ;
piece1. col = piece2. col ;
piece1. row = piece2. row ;
piece2. col = tempCol;
piece2. row = tempRow;
// change the position in the grid (grid)
grid [ piece1. col ] [ piece1. row ] = piece1;
grid [ piece2. col ] [ piece2. row ] = piece2;
}
The exchange is completely reversible and it is very important, because in the future we will often have to use it. Indeed, in reality, we do not know whether the exchange will lead to the formation of lines until we produce it. Therefore, we first change the chips, check on the line and return them back if no lines are found.
Chip Motion AnimationWe use an interesting, but not obvious method of animation. We know about each chip in which row and column it is, due to the dynamic properties of
row and
col . And also we can find out its location on the screen based on the properties of
x and
y . Do not forget about the constants
spacing ,
offsetX ,
offsetY . For example, a chip in column 3 will get the value
x = 3 * spacing + offset . What happens if the chip moves to another column? If we assign a
col value of 4 to the chip, then
x = 4 * spacing + offset , which is 45 pixels to the right. Therefore, we will make the chip move to the right, closer to its destination. If you do this every frame, then soon the chip will rise to its new destination and stop moving (its values
col and
x will match each other).
Using this technique, you can animate any piece as it moves to a new place. We don't even have to set up animation at the level of the movie clip. All we have to do is change the
col property or
row of chips (
Piece ). And the
movePieces function will take care of the rest.
The
movePieces function
is called every frame, because we set it up at the very beginning of the class, with the help of the listener. It checks all chips for matching values of
col and
row with
x and
y .
Note.In the
movePieces function
, we use step 5 every frame. This value must always be a multiple of the
spacing value. If
spacing were equal, for example, 48, we would use 4, 6 or 8.
// if a chip is out of place, move it a little closer
// this happens in the case of exchange, or falling chips
public function movePieces ( event: Event ) {
var madeMove: Boolean = false ;
for ( var row: int = 0 ; row < 8 ; row ++ ) {
for ( var col: int = 0 ; col < 8 ; col ++ ) {
if ( grid [ col ] [ row ] ! = null ) {
// shift down
if ( grid [ col ] [ row ] . y < grid [ col ] [ row ] . row * spacing + offsetY ) {
grid [ col ] [ row ] . y + = 5 ;
madeMove = true ;
// move up
} else if ( grid [ col ] [ row ] . y > grid [ col ] [ row ] . row * spacing + offsetY ) {
grid [ col ] [ row ] . y - = 5 ;
madeMove = true ;
// shift to the right
} else if ( grid [ col ] [ row ] . x < grid [ col ] [ row ] . col * spacing + offsetX ) {
grid [ col ] [ row ] . x + = 5 ;
madeMove = true ;
// shift left
} else if ( grid [ col ] [ row ] . x > grid [ col ] [ row ] . col * spacing + offsetX ) {
grid [ col ] [ row ] . x - = 5 ;
madeMove = true ;
}
}
}
}
At the beginning of the
movePieces function
, we set the
madeMove flag to
false . Then, in the case of any offset, reset it to
true . If there was no offset in either direction,
madeMove remains equal to
false . Then we compare this flag with the
isDropping and
isSwapping properties . If
isDropping is true , and
madeMove is false , then all the falling chips are in place. It's time to check the field on the line.
If
isSwapping is true and
madeMove is false , then two chips have just completed the exchange. And in this case, check the field on the line.
// all drops completed
if ( isDropping && ! madeMove ) {
isDropping = false ;
findAndRemoveMatches ( ) ;
// all exchanges completed
} else if ( isSwapping && ! madeMove ) {
isSwapping = false ;
findAndRemoveMatches ( ) ;
}
}
Line searchOur program has two difficult tasks. And the first one is the search for lines. The task of finding them is quite difficult and is not solved by a simple method.
Dividing the task into small steps.
Let's try to divide the task into smaller subtasks, and we will do this until we reduce the subtask to a simple solution.
So, the
findAndRemoveMatches function is divided into two parts. Find lines and remove them. The second task, removal, is simple in its essence. Remove chips from
gameSprite , set zeros in the grid to the place of the deleted chips and reward the player with points.
Note
The number of points depends on the number of chips in the line. Three chips mean (3-1) * 50 or 100 points for each chip. Four chips, (4-1) * 50 or 150 points for a chip, at least 600 points.Also, after removing the chips, it is necessary to reset down those that were over the deleted ones. This is also a rather difficult task.
So, we have two challenges, find the lines and decide what to do with the chips over the disappeared ones. We will assign these tasks to the
lookForMatches and
affectAbove functions . We will do the rest right in the
findAndRemoveMatches function.
Function findAndRemoveMatchesWe loop through all the lines and put them into an array. We give points for each line. Next, we go through all the chips that need to be removed and remove them.
Note
When we take a complex task and assign its solution to functions that we have not yet defined, this is called top-down programming . Instead of thinking and puzzling over how to look for lines, we will shift this to the lookForMatches function. That is, we build our program from top to bottom, taking care of how everything looks as a whole, and the functions to which we shift the tasks, we consider later.public function findAndRemoveMatches ( ) {
// create a list of lines
var matches: Array = lookForMatches ( ) ;
for ( var i: int = 0 ; i < matches. length ; i ++ ) {
var numPoints: Number = ( matches [ i ] . length - 1 ) * 50 ;
for ( var j: int = 0 ; j < matches [ i ] . length ; j ++ ) {
if ( gameSprite. contains ( matches [ i ] [ j ] ) ) {
var pb = new PointBurst ( this , numPoints, matches [ i ] [ j ] . x , matches [ i ] [ j ] . y ) ;
addScore ( numPoints ) ;
gameSprite. removeChild ( matches [ i ] [ j ] ) ;
grid [ matches [ i ] [ j ] . col ] [ matches [ i ] [ j ] . row ] = null ;
affectAbove ( matches [ i ] [ j ] ) ;
}
}
}
The
findAndRemoveMatches function performs two tasks. First,
addNewPieces fills in the missing number of chips in a column. Then call
lookForPossibles to make sure that the player still has moves on the field. It is only needed if new lines are no longer found. This happens if
findAndRemoveMatches was called after new chips fell, and no lines were found.
// add a new chip at the top of the field
addNewPieces ( ) ;
// no lines found, check for the possibility of running
if ( matches. length == 0 ) {
if ( ! lookForPossibles ( ) ) {
endGame ( ) ;
}
}
}
LookForMatches functionThe purpose of the function is to create an array of the found lines. We determine the lines of more than 2 chips. To do this, do a round in a loop, first in rows, then in columns. Check the segment of the first 6 chips in each row. It makes no sense to check from 7 and 8, as they will not be able to form lines from more than 2 chips.
The functions
getMatchHoriz and
getMatchVert determine the length of the line from the beginning of the element passed to them. For example, if the element
[3] [6] is a type 4 token,
[4] [6] is also a type 4 token, and
[5] [6] is a token of type 1, the call to
getMatchHoriz (3.6) will return 2 because the line is found from 2 chips.
If a line is found, it will be effective to skip a couple of cycles and jump a couple of steps forward. For example, we have a line in
[2] [1] ,
[2] [2] ,
[2] [3] and
[2] [4] , we simply check
[2] [1] , return the result 4 and skipping
[2] [2] ,
[2] [3] ,
[2] [4] to immediately start with
[2] [5] .
For each line found using
getMatchHorizon or
getMatchVert, they return an array containing each chip in the line. We add these arrays to the
lookForMatches function in the
matches array.
// return an array of all found lines
public function lookForMatches ( ) : Array {
var matchList: Array = new Array ( ) ;
// search for horizontal lines
for ( var row: int = 0 ; row < 8 ; row ++ ) {
for ( var col: int = 0 ; col < 6 ; col ++ ) {
var match: Array = getMatchHoriz ( col, row ) ;
if ( match. length > 2 ) {
matchList. push ( match ) ;
col + = match. length - 1 ;
}
}
}
// search for vertical lines
for ( col = 0 ; col < 8 ; col ++ ) {
for ( row = 0 ; row < 6 ; row ++ ) {
match = getMatchVert ( col, row ) ;
if ( match. length > 2 ) {
matchList. push ( match ) ;
row + = match. length - 1 ;
}
}
}
return matchList;
}
GetMatchHorizon and
getMatchVert functionsLet's
sort the getMatchHorizon function. Given the column and row passed into it, she checks the next chip for a match. If so, it is added to the array. Continues to move horizontally until it encounters a mismatch. Then she reports that the array is compiled. It can even be composed of a single chip, if the next does not match. And it can return a few.
// search for horizontal lines from a given point
public function getMatchHoriz ( col, row ) : Array {
var match: Array = new Array ( grid [ col ] [ row ] ) ;
for ( var i: int = 1 ; col + i < 8 ; i ++ ) {
if ( grid [ col ] [ row ] . type == grid [ col + i ] [ row ] . type ) {
match. push ( grid [ col + i ] [ row ] ) ;
} else {
return match;
}
}
return match;
}
The
getMatchVert function
is almost identical to
getMatchHorizon , except that the search is performed not by rows but by columns.
// search for vertical lines from a given point
public function getMatchVert ( bol, row ) : Array {
var match: Array = new Array ( grid [ col ] [ row ] ) ;
for ( var i: int = 1 ; row + i < 8 ; i ++ ) {
if ( grid [ col ] [ row ] . type == grid [ col ] [ row + i ] . type ) {
match. push ( grid [ col ] [ row + i ] ) ;
} else {
return match;
}
}
return match;
}
Function affectAboveConsider
affectAbove . We pass the chip into it, and we expect her to tell all the chips above her that she can move a step down. In the cycle we look through the chips in the column above the current one. For example, if the current
[5] [6] , then check
[5] [5] ,
[5] [4] ,
[5] [3] ,
[5] [2] ,
[5] [1] ,
[ 5] [0] in that order. The row value of these chips is increased by 1. In addition, they transmit new data about their location to the grid. Remember that with the
movePieces function
, we don’t have to worry about animation. We just give the chip a new location.
// causes all the chips above the passed into the function to move down
public function affectAbove ( piece: Piece ) {
for ( var row: int = piece. row - 1 ; row > = 0 ; row-- ) {
if ( grid [ piece. col ] [ row ] ! = null ) {
grid [ piece. col ] [ row ] . row ++;
grid [ piece. col ] [ row + 1 ] = grid [ piece. col ] [ row ] ;
grid [ piece. col ] [ row ] = null ;
}
}
}
AddNewPieces functionThe next function we need to write is
addNewPieces . It checks all empty (
null ) cells in the grid and fills them with new chips. Although the
col and
row values get their final value, y gets the value at the top of the screen, so the chips fall down. The variable
isDropping takes
true , which indicates the animation of the falling chip.
// if there is no chip in the column, add a new one falling from above.
public function addNewPieces ( ) {
for ( var col: int = 0 ; col < 8 ; col ++ ) {
var missingPieces: int = 0 ;
for ( var row: int = 7 ; row > = 0 ; row-- ) {
if ( grid [ col ] [ row ] == null ) {
var newPiece: Piece = addPiece ( col, row ) ;
newPiece. y = offsetY-spacing-spacing * missingPieces ++;
isDropping = true ;
}
}
}
}
Search for possible movesFinding possible lines is not much easier than finding lines.
The easiest way is to go through the board, making an exchange for each piece.
[0] [0] with
[1] [0] , then
[1] [0] with
[2] [0] , etc. With each exchange, we look for lines, and when we find the first one, we stop searching and return
true . Such a brute-force approach will work, but it will be very slow, especially on older machines. There is a more efficient way.
What options can we have to draw a line? Usually these are two chips of the same type in a row. The third chip is different in type, but can be exchanged for any of the three in free directions. Or, two chips of the same type are separated by one chip of another type, and now an exchange in 2 directions can occur.
The figure shows us these two cases divided into 6 patterns.

Now, knowing that there are only a few templates that we have to find, we can begin using the
lookForMatches function on the principle of top-down programming, and we will take care of the template search function later.
Looking at the picture we will see two black chips that are included in the line and 3 chips that may possibly be of the same type. Denote the leftmost black chip as
[0] [0] . We see that the chip
[1] [0] of the same type. It remains to find the same chip on the position
[-1] [- 1] ,
[-2] [0] or
[-1] [1] . And also on the other hand
[2] [- 1] ,
[2] [1] and
[3] [0] . So, we have to find in 6 positions at least one matching chip.

When you call a function, we will pass into it an array of two chips matching in type, and an array of chips surrounding a third, of which at least one must match. It will look something like this.
matchPattern ( col, row, [[1, 0]] , [[-2, 0], [- 1, -1], [- 1, 1], [2, -1], [2, 1], [3, 0]] )We also need a similar function for the other examples of patterns in fig. 8.9. They are both vertical.
The
lookForPossibles function searches all board positions.
// check for possible moves on drawing up lines on the field
public function lookForPossibles ( ) {
for ( var col: int = 0 ; col < 8 ; col ++ ) {
for ( var row: int = 0 ; row < 8 ; row ++ ) {
// Possible horizontal, two in a row
if ( matchPattern ( col, row, [ [ 1 , 0 ] ] , [ [ - 2 , 0 ] , [ - 1 , - 1 ] , [ - 1 , 1 ] , [ 2 , - 1 ] , [ 2 , 1 ] , [ 3 , 0 ] ] ) ) {
return true ;
}
// Possible horizontal, two on opposite sides
if ( matchPattern ( col, row, [ [ 2 , 0 ] ] , [ [ 1 , - 1 ] , [ 1 , 1 ] ] ) {
return true ;
}
// possible vertical, two in a row
if ( matchPattern ( col, row, [ [ 0 , 1 ] ] , [ [ 0 , - 2 ] , [ - 1 , - 1 ] , [ 1 , - 1 ] , [ - 1 , 2 ] , [ 1 , 2 ] , [ 0 , 3 ] ] ) ) {
return true ;
}
// Possible vertical, two on opposite sides
if ( matchPattern ( col, row, [ [ 0 , 2 ] ] , [ [ - 1 , 1 ] , [ 1 , 1 ] ] ) ) {
return true ;
}
}
}
// no possible lines found
return false ;
}
Although the
matchPattern function will perform an important task, it is not a big one in itself. She gets the type of chips from certain
col and
row . Then she goes through the
mustHave list and checks the chips in the appropriate positions. If no match is found, the double line is not found, there is no point in continuing and the function returns
false . Otherwise, every
needOne token is checked. If at least one token is the same type, return
true . If none, return
false .
public function matchPattern ( col, row: uint, mustHave, needOne: Array ) {
var thisType: int = grid [ col ] [ row ] . type ;
// make sure there is a second chip of the same type
for ( var i: int = 0 ; i < mustHave. length ; i ++ ) {
if ( ! matchType ( col + mustHave [ i ] [ 0 ] , row + mustHave [ i ] [ 1 ] , thisType ) ) {
return false ;
}
}
// make sure that the third chip matches the other two
for ( i = 0 ; i < needOne. length ; i ++ ) {
if ( matchType ( col + needOne [ i ] [ 0 ] , row + needOne [ i ] [ 1 ] , thisType ) ) {
return true ;
}
}
return false ;
}
All comparisons in
matchPattern are made via
matchType . Let's design it as a separate function. we will often refer to chips that are not in the grid. For example, if we pass in
matchPattern col and
row [5] [0] , then it makes no sense to check the chips -1, for example 4 -1, since they do not fall into the net.
The function will check if the chip is on the field, and if so, it will compare its type with the required one.
public function matchType ( col, row, type : int ) {
// make sure the chip does not go beyond the field
if ( ( col < 0 ) || ( col > 7 ) || ( row < 0 ) || ( row > 7 ) ) return false ;
return ( grid [ col ] [ row ] . type == type ) ;
}
Score and End GameIn the
findAndRemoveMatches function
, we called
addScore to add points to the player. This simple function summarizes the points and transfers the necessary data to a text field on the screen.
public function addScore ( numPoints: int ) {
gameScore + = numPoints;
MovieClip ( root ) . scoreDisplay . text = String ( gameScore ) ;
}
If there are no more possible moves on the field, the endGame function takes us on the timeline to the
gameover frame. It also uses
swapChildIndex to push the gameSprite into the background, so all the sprites of the
gameover frame will be above the playing field.
We do this in order not to remove the playing field after the end of the game, but leave it to the player for consideration.
public function endGame ( ) {
// move to background
setChildIndex ( gameSprite, 0 ) ;
// go to the game end screen
gotoAndStop ( "gameover" ) ;
}
We will remove the grid when the player is ready to move on. To do this, call the
cleanup function.
public function cleanUp ( ) {
grid = null ;
removeChild ( gameSprite ) ;
gameSprite = null ;
removeEventListener ( Event. ENTER_FRAME , movePieces ) ;
}
On the timeline, the
cleanUp function binds to the
playAgain button and starts before starting a new game.
Modification of the gameThe important decision to take is how many types of chips you want to use in the game. Most match3 games use six. Using seven faster will result in an unsolvable combination.
Well, that's basically all. We have a ready-made engine for creating a game based on the match3 mechanics. I hope that someone will give impetus to the creation of games.
Sources can be downloaded
here .
And I do not insist, but you can always buy this book, than express the most effective thanks to the author.UPD. A small addition.How else can you improve the game. Well, first hints. Everyone probably noticed tips in games on such mechanics. When a player does nothing for a long time, two chips begin to wink with which you can make an exchange. All this can be done with the lookForPossibles function . And how, will remain as homework.Second, bonus chips. You can always include another Bonus layer of the same type as Select in our USB flash drive . And fit the bonus property to the chip . And then I think it is clear how to use the click on this chip and additional points.Now an important note, prompted from the comments in a personal blog. This is not mentioned in the book anywhere, but it is better not to miss this moment.1. In the setUpGrid function , we in a loop create the initial field of the game. And each cycle we add chips, regardless of whether the field was created playable or not.2. In the addPiece function , we hang a listener ( addEventListener ) on each piece . And with its (chips) removal, we do not remove it ( removeEventListener ).What should we get out of here? Such defects will sooner or later lead to memory leaks. What corrections can we make to our code?1. Add chips when we have a playable field in front of us.2. Use the weakReference flag .Example: object.addEventListener (Event.CLICK, handleClick false, 0, true) ;When removing chips, it is recommended to use removeEventListener .Thanks for the tips.