HTML5 Drag and Match engine updated to Phaser 2.6.2 with masks and a lot of custom options

Talking about Drag and Match game, Game development, HTML5, Javascript and Phaser.

Here we go with another prototype updated to the last Phaser official release, 2.6.2. After updating – and improving – Space is Key and String Avoider prototypes, now it’s time to update – and improve – the drag and match engine. The improvement has been made over three main features: * A mask has been added to hide tiles outside the game field, this is very important to grant the effect of a tile disappearing from a side of the board and appearing to the other side. * A lot of custom options have been added, inside gameOptions global object. You can change several things such as tile size, board size and offset, as well as the number of different colors on the board * All the code which handles something which moves – basically the tiles – has been placed inside update method which Phaser runs at each frame, to prevent strange flickering which appears in latest Phaser versions when you move sprites outside update method. Now, it’s time to play:
I placed the board inside a way bigger canvas to show you how mask works, hiding the part of the sprites which are outside the board. Drag and move the tiles around the board. If you have a mobile device, you can play directly fron this link. And here we go with the completely commented source code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
// the game itself
var game;
 
// global game options
var gameOptions = {
 
     // width of the game, in pixels
     gameWidth: 600,
      
     // height of the game, in pixels
     gameHeight: 600,
 
     // size of the sprite sheet, in pixels
     spritesheetSize: 50,
      
     // size of each tile, in pixels
     tileSize: 50,
      
     // size of the field, in tiles
     fieldSize: 6,
      
     // different tile types
     tileTypes: 6,
      
     // distance from the left of the screen to the left of the board, in pixels
     offsetX: 150,
      
     // distance from the top of the screen to the top of the board, in pixels
     offsetY: 150
}
 
// some constants to be used in the game
 
// I am not dragging
var NO_DRAG = 0;
 
// I am dragging horizontally
var HORIZONTAL_DRAG = 1;
 
// I am dragging vertically
var VERTICAL_DRAG = 2;
 
// The game state is "doing nothing"
var GAME_STATE_IDLE = 0;
 
// When the player is dragging a row/column
var GAME_STATE_DRAG = 1;
 
// When the player stops dragging
var GAME_STATE_STOP = 2;
 
// when the window has been fully loaded
window.onload = function() {           
 
     // creation of a Game instance
    game = new Phaser.Game(600, 600);
      
     // adding "PlayGame" state
    game.state.add("PlayGame", playGame)
      
     // starting "PlayGame" state
    game.state.start("PlayGame");
}
 
var playGame = function(game){}
playGame.prototype = {
 
     // when the state preloads
    preload: function(){
      
          // loading the spritesheet with all tile images
          game.load.spritesheet("tiles", "tiles.png", gameOptions.spritesheetSize, gameOptions.spritesheetSize);
           
          // setting the game on maximum scale mode to cover the entire screen
          game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
          game.scale.pageAlignHorizontally = true;
          game.scale.pageAlignVertically = true;
    },
      
      // once the state has been created
    create: function(){
      
          // tileArray is the array which will contain all tiles
          this.tileArray = [];
           
          // creation of the group which will contain all tiles
          this.tileGroup = game.add.group();
           
          // adjusting group position according to offset
          this.tileGroup.x = gameOptions.offsetX;
          this.tileGroup.y = gameOptions.offsetY;
           
          // creation of a mask with the same size of the board to be placed in the same position of the group
          // this way we are hiding everything is outside the board 
          this.tileMask = game.add.graphics(this.tileGroup.x, this.tileGroup.y);
          this.tileMask.beginFill(0xffffff);
          this.tileMask.drawRect(0, 0, gameOptions.fieldSize * gameOptions.tileSize, gameOptions.fieldSize * gameOptions.tileSize);
          this.tileGroup.mask = this.tileMask;
          this.tileMask.visible = true;
           
          // filling the board with tiles thanks to "addTile" method
          for(var i = 0; i < gameOptions.fieldSize; i++){
            this.tileArray[i] = [];
            for(j = 0; j < gameOptions.fieldSize; j++){
                this.addTile(i, j);
            }
        }
           
          // adding the temporary tile thanks to addTempTile method
        this.addTempTile();
           
          // waiting for player input to call pickTile method
        game.input.onDown.add(this.pickTile, this);
           
          // the game has just been created, so we are doing nothing
          this.gameState = GAME_STATE_IDLE;
    },
      
     // function to add a tile at a given row and column
     addTile: function(row, col){
      
          // choosing a random tile
          var randomTile = game.rnd.integerInRange(0, gameOptions.tileTypes - 1);
           
          // creation of the sprite in the proper position
        var theTile = game.add.sprite(col * gameOptions.tileSize, row * gameOptions.tileSize, "tiles");
         
          // setting tile width and height
          theTile.width = gameOptions.tileSize;
          theTile.height = gameOptions.tileSize;
           
          // showing the frame according to tile value
          theTile.frame = randomTile;
           
          // saving the value inside a custom property
        theTile.value = randomTile;
           
          // inserting the tile in tileArray array
        this.tileArray[row][col] = theTile;
           
          // adding the sprite to tileGroup group
          this.tileGroup.add(theTile); 
     },
      
     // function to add the temporary tile
     addTempTile: function(){
      
          // creation of the sprite, no matter the position, we won't show it at the moment
          this.tempTile = game.add.sprite(0, 0, "tiles");
           
          // setting its width and height
          this.tempTile.width = gameOptions.tileSize;
          this.tempTile.height = gameOptions.tileSize;
           
          // setting the sprite to non visible
        this.tempTile.visible = false;
           
          // adding the sprite to tileGroup group
          this.tileGroup.add(this.tempTile);           
     },
      
     // function to triggered when the player touches/clicks on the canvas
     pickTile: function(e){
           
          // determining row and column according to input position, tile size and offset
          this.movingRow = Math.floor((e.position.y - gameOptions.offsetY) / gameOptions.tileSize);
          this.movingCol = Math.floor((e.position.x - gameOptions.offsetX) / gameOptions.tileSize);
           
          // if row and column are actually inside game field...
          if(this.movingRow >= 0 && this.movingCol >= 0 && this.movingRow < gameOptions.fieldSize && this.movingCol < gameOptions.fieldSize){
                
               // at the moment we aren't dragging
               this.dragDirection = NO_DRAG;
                
               // removing the listener which waits for the input to begin
               game.input.onDown.remove(this.pickTile, this);
                
               // adding a listener which waits for the input to end then call releaseTile method
               game.input.onUp.add(this.releaseTile, this);
                
               // adding a listener which waits for the input to move then call moveTile method
               game.input.addMoveCallback(this.moveTile, this);
          }
     },
      
     // function to be executed at each frame
     update:function(){
      
          // checking game state to see what to do
          switch(this.gameState){
           
               // we are dragging
               case GAME_STATE_DRAG:
                
                    // call handleDrag method
                    this.handleDrag();
               break;
                
               // we just stopped dragging
               case GAME_STATE_STOP:
                
                    // call handleStop method
                    this.handleStop();
               break;
          }
           
          // at the end of the function, we set gameState again to idle
          this.gameState = GAME_STATE_IDLE;    
      
           
     },
      
     // function to handle - and draw on the canvas - the game when the player drags a row/column
     handleDrag:function(){
           
          // two different things to do according to drag direction
          switch(this.dragDirection){
                
               // horizontal drag
               case HORIZONTAL_DRAG:
                
                    // hiding temporary tile
                this.tempTile.visible = false;
                     
                    // placing the temporary tile in the proper row
                this.tempTile.y = this.movingRow * gameOptions.tileSize;
                     
                    // deltaX is the amount of tiles we are moving
                var deltaX = (Math.floor(this.distX / gameOptions.tileSize) % gameOptions.fieldSize);
                     
                // deltaX >= 0 means we are moving to the right (or not moving)
                    if (deltaX >= 0) {      
                     
                         // temporary tile frame is now the same as the rightmost visible tile                         
                    this.tempTile.frame = this.tileArray[this.movingRow][gameOptions.fieldSize - 1 - deltaX].value;
                }
                     
                    // we are moving to the left
                else{
                     
                         // temporary tile frame is now the same as the leftmost visible tile
                    deltaX = deltaX * -1 - 1;
                    this.tempTile.frame = this.tileArray[this.movingRow][deltaX].value;    
                }
                     
                    // looping through all the moving row
                for(var i = 0; i < gameOptions.fieldSize; i++){
                     
                         // adjusting each tile horizontal position
                    this.tileArray[this.movingRow][i].x = (i * gameOptions.tileSize + this.distX) % (gameOptions.tileSize * gameOptions.fieldSize);
                     
                         // if tile position is less than zero...
                         if (this.tileArray[this.movingRow][i].x < 0) {
                          
                              // ... place it on the opposite side of the game field
                            this.tileArray[this.movingRow][i].x += gameOptions.tileSize * gameOptions.fieldSize;
                    }
                }
                    // tileX is the amount of pixels we are moving, capped to gameOptions.tileSize
                      var tileX = this.distX % gameOptions.tileSize;
                     
                    // if the amount is greater than zero (moving to the right)
                    if(tileX > 0){
                          
                         // placing temporary tile before the leftmost tile
                         this.tempTile.x = tileX - gameOptions.tileSize;
                          
                         // showing temportary tile
                        this.tempTile.visible = true;
                    }
                     
                    // if the amount is less than zero (moving to the left)
                    if(tileX < 0){
                          
                         // placing temporary tile before the leftmost tile
                         this.tempTile.x = tileX;     
                          
                         // showing temportary tile
                        this.tempTile.visible = true;
                    }
            break;
                
               // vertical drag, same concept seen in horizontal drag, just applied to Y axis
               case VERTICAL_DRAG:
                this.tempTile.visible = false;
                this.tempTile.x = this.movingCol * gameOptions.tileSize;
                var deltaY = (Math.floor(this.distY / gameOptions.tileSize) % gameOptions.fieldSize);
                if (deltaY >= 0) {                                  
                    this.tempTile.frame = this.tileArray[gameOptions.fieldSize - 1 - deltaY][this.movingCol].value;
                }
                else{
                    deltaY = deltaY * -1 - 1;
                    this.tempTile.frame = this.tileArray[deltaY][this.movingCol].value;    
                }
                for(var i = 0; i < gameOptions.fieldSize; i++){
                    this.tileArray[i][this.movingCol].y = (i * gameOptions.tileSize + this.distY) % (gameOptions.tileSize * gameOptions.fieldSize);
                    if (this.tileArray[i][this.movingCol].y < 0) {
                            this.tileArray[i][this.movingCol].y += gameOptions.tileSize * gameOptions.fieldSize;
                    
                }
                    var tileY = this.distY % gameOptions.tileSize;
                    if(tileY > 0){
                         this.tempTile.y = tileY - gameOptions.tileSize;
                         this.tempTile.visible = true;
                    }
                    if(tileY < 0){
                         this.tempTile.y = tileY;     
                         this.tempTile.visible = true;
                    }
            break;    
          }
     },
      
     // function to handle - and draw on the canvas - the game when the player stops dragging a row/column
     handleStop:function(){
      
          // hiding temporary tile
          this.tempTile.visible = false;
      
          // two different things to do according to drag direction
          switch(this.dragDirection){
           
               // horizontal drag
            case HORIZONTAL_DRAG:
                
                    // we have to find how many "half tiles" we dragged
                var shiftAmount = Math.floor(this.distX / (gameOptions.tileSize / 2));
                     
                    // and now let's see how many tiles we dragged, with a modulo operation because the max amount is fieldSize - 1
                shiftAmount = Math.ceil(shiftAmount / 2) % gameOptions.fieldSize;
                     
                    // creation of a temporary array
                var tempArray = [];
                     
                    // now the idea is to insert in tempArray array the tileArray items in the order they are at the end of the drag
                    // when shiftAmount is greater than 0, we dragged to the right
                if(shiftAmount > 0){
                    for(var i = 0; i < gameOptions.fieldSize; i++){
                        tempArray[(shiftAmount + i) % gameOptions.fieldSize] = this.tileArray[this.movingRow][i].value;
                    }
                }
                     
                    // when shiftAmount is less than zero, we dragged to the left
                else{
                    shiftAmount *= -1;
                    for(var i = 0; i < gameOptions.fieldSize; i++){
                        tempArray[i] = this.tileArray[this.movingRow][(shiftAmount + i) % gameOptions.fieldSize].value;
                    }
                }
                     
                    // copying content from tempArray to tileArray and adjust tile position
                for(i = 0; i < gameOptions.fieldSize; i++){
                    this.tileArray[this.movingRow][i].value = tempArray[i];
                    this.tileArray[this.movingRow][i].frame = tempArray[i];
                    this.tileArray[this.movingRow][i].x = i * gameOptions.tileSize;
                    }
            break;
                
               // vertical drag follows the same concepts seen in horizontal drag
            case VERTICAL_DRAG:
                var shiftAmount = Math.floor(this.distY / (gameOptions.tileSize / 2));
                shiftAmount = Math.ceil(shiftAmount / 2) % gameOptions.fieldSize;
                var tempArray = [];
                if(shiftAmount > 0){
                    for(var i = 0; i < gameOptions.fieldSize; i++){
                        tempArray[(shiftAmount + i) % gameOptions.fieldSize] = this.tileArray[i][this.movingCol].value;
                    }
                }
                else{
                    shiftAmount *= -1;
                    for(var i = 0; i < gameOptions.fieldSize; i++){
                        tempArray[i] = this.tileArray[(shiftAmount + i) % gameOptions.fieldSize][this.movingCol].value;
                    }
                }
                for(var i = 0; i < gameOptions.fieldSize; i++){
                    this.tileArray[i][this.movingCol].value = tempArray[i];
                    this.tileArray[i][this.movingCol].frame = tempArray[i];
                    this.tileArray[i][this.movingCol].y = i * gameOptions.tileSize;
                    }
            break
        }
           
          // we aren't dragging anymore
        this.dragDirection = NO_DRAG;
     },
      
     // function to be triggered when the player moves the mouse/finger
     moveTile: function(e){
      
          // we are dragging
          this.gameState = GAME_STATE_DRAG;
      
          // determining horizontal and vertical distance between start and current input position
          this.distX = e.position.x - e.positionDown.x;
          this.distY = e.position.y - e.positionDown.y;
           
          // if we aren't dragging yet...
          if(this.dragDirection == NO_DRAG){
                          
               // how many pixels are we travelling with our finger/mouse?
            var distance = e.position.distance(e.positionDown);
                
               // more than 5 pixels? ok, that's enough to determine the direction
            if(distance > 5) {
                
                    // trigonometry to know drag angle
                var dragAngle = Math.abs(Math.atan2(this.distY, this.distX));
                     
                    // if drag angle is between PI/4 and 3PI/4...
                if((dragAngle > Math.PI / 4 && dragAngle < 3 * Math.PI / 4)) {
                     
                         // ... we can say it's a vertical drag
                         this.dragDirection = VERTICAL_DRAG;
                }
                else {
                     
                         // else it's an horizontal drag
                    this.dragDirection = HORIZONTAL_DRAG;
                }
            }
          }            
     },
      
     // function to be triggered when the player releases the mouse/finger
     releaseTile: function(){
      
          // we stopped dragging
          this.gameState = GAME_STATE_STOP;
           
          // updating listeners
          game.input.onDown.add(this.pickTile, this);
          game.input.onUp.remove(this.releaseTile, this);
          game.input.deleteMoveCallback(this.moveTile, this);
     }
}
There’s an upcoming book about this kind of tile games, so if you want to learn more you will have plenty of examples, meanwhile download the source code.