Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about o:anquan game, Game development, HTML5, Javascript and Phaser.

Recently I played o:anquan on my iPhone, published by Pine Entertainment. The idea behind o:anquan is inspired by the control of a traditional Vietnamese children’s board game. You play on a 6×6 board, and each turn three random numbers between 1 and 9 are placed in three random empty spots on the board. The player at this time can select a number and drag it around the board, only passing across empty spots. At each step, the number decreases and leaves a “1” behind it. At the end of the turn, all matching numbers are removed from the board, and three new numbers appear. The game ends when there aren’t possible moves. It’s really easier to play than to explain, so have a go, since it’s free. The game engine is a simplified version of Globez, the same engine I used in the making of Dungeon Raid tutorial series and in the making of DrawSum HTML5 game which I also ported as a free Google Play Android game, which I invite you to donwload and play as well as read the “making of”. Back to the prototype we are making, at the moment we manage tile movement but we don’t remove tiles from the board and this will be made in next step. If you want to try by yourself, it’s just a flood fill operation. You can have some more information about flood fill at this page. Now, let’s play: select and drag numbers, you can also backtrack.
Being the source code almost a copy of Dungeon Raid series, it won’t be commented at the moment, but I will during next step when I’ll finish the game. One of the most interesting things in writing a lot of code, is you can always reuse your code for new projects.
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
var game;
 
var gameOptions = {
    gameWidth: 800,
    gameHeight: 1400,
    tileSize: 120,
    fieldSize: {
          rows: 6,
          cols: 6
     },
     fallSpeed: 250,
     diagonal: false,
     colors: [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff]
}
 
window.onload = function() {   
    game = new Phaser.Game(gameOptions.gameWidth, gameOptions.gameHeight);
     game.state.add("TheGame", TheGame);
     game.state.start("TheGame");
}
 
var TheGame = function(){};
 
TheGame.prototype = {
     preload: function(){
          game.stage.backgroundColor = 0x222222;
          game.load.spritesheet("tiles", "assets/sprites/tiles.png", gameOptions.tileSize, gameOptions.tileSize);
     },
    create: function(){
          game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
        game.scale.pageAlignHorizontally = true;
        game.scale.pageAlignVertically = true;  
        this.createLevel();
          game.input.onDown.add(this.pickTile, this);
    },
    createLevel: function(){
          this.tilesArray = [];
        this.tileGroup = game.add.group();
          this.tileGroup.x = (game.width - gameOptions.tileSize * gameOptions.fieldSize.cols) / 2;
          this.tileGroup.y = (game.height -  gameOptions.tileSize * gameOptions.fieldSize.rows) / 2;
        for(var i = 0; i < gameOptions.fieldSize.rows; i++){
               this.tilesArray[i] = [];
            for(var j = 0; j < gameOptions.fieldSize.cols; j++){
                this.addTile(i, j);
            }
        }
          this.placeNumbers();
    },
     placeNumbers: function(){
          var emptySpots = [];
          for(var i = 0; i < gameOptions.fieldSize.rows; i++){
            for(var j = 0; j < gameOptions.fieldSize.cols; j++){
                if(this.tilesArray[i][j].value == 0){
                         emptySpots.push(this.tilesArray[i][j].coordinate);
                    }
            }
        }
          for(i = 0; i < 3; i++){
               var item = Phaser.ArrayUtils.removeRandomItem(emptySpots);
               if(item){
                    var randomValue = game.rnd.integerInRange(1, 9);
                    this.tilesArray[item.y][item.x].value = randomValue;
                    this.tilesArray[item.y][item.x].frame = randomValue;
               }    
          }  
     },
    addTile: function(row, col){
        var tileXPos = col * gameOptions.tileSize + gameOptions.tileSize / 2;
        var tileYPos = row * gameOptions.tileSize + gameOptions.tileSize / 2;
        var theTile = game.add.sprite(tileXPos, tileYPos, "tiles");
        theTile.anchor.set(0.5);
          theTile.value = 0;
          theTile.picked = false;
          theTile.coordinate = new Phaser.Point(col, row);
          this.tilesArray[row][col] = theTile;
         this.tileGroup.add(theTile);  
    },
     pickTile: function(e){
          this.visitedTiles = [];
          this.visitedTiles.length = 0;
          if(this.tileGroup.getBounds().contains(e.position.x, e.position.y)){
               var col = Math.floor((e.position.x - this.tileGroup.x) / gameOptions.tileSize);
               var row = Math.floor((e.position.y - this.tileGroup.y) / gameOptions.tileSize);
               if(this.tilesArray[row][col].value > 0){
                    this.tilesArray[row][col].alpha = 0.5; 
                    game.input.onDown.remove(this.pickTile, this);
                 game.input.onUp.add(this.releaseTile, this);
                    game.input.addMoveCallback(this.moveTile, this);  
                    this.visitedTiles.push(this.tilesArray[row][col].coordinate);
               }
          }
     },
     moveTile: function(e){
          if(this.tileGroup.getBounds().contains(e.position.x, e.position.y)){
               var col = Math.floor((e.position.x - this.tileGroup.x) / gameOptions.tileSize);
               var row = Math.floor((e.position.y - this.tileGroup.y) / gameOptions.tileSize);
               if(row != this.visitedTiles[this.visitedTiles.length - 1].y || col != this.visitedTiles[this.visitedTiles.length - 1].x){
                    var distance = new Phaser.Point(e.position.x - this.tileGroup.x, e.position.y - this.tileGroup.y).distance(this.tilesArray[row][col]);
                    if(distance < gameOptions.tileSize * 0.4){
                         var previousTileValue = this.tilesArray[this.visitedTiles[this.visitedTiles.length - 1].y][this.visitedTiles[this.visitedTiles.length - 1].x].value;
                         if(!this.tilesArray[row][col].picked && this.checkAdjacent(new Phaser.Point(col, row), this.visitedTiles[this.visitedTiles.length - 1]) && previousTileValue > 1 && this.tilesArray[row][col].value == 0){
                              this.tilesArray[row][col].picked = true;
                              this.tilesArray[row][col].alpha = 0.5;
                              this.tilesArray[row][col].value = previousTileValue - 1;
                              this.tilesArray[row][col].frame = previousTileValue - 1;
                              this.tilesArray[this.visitedTiles[this.visitedTiles.length - 1].y][this.visitedTiles[this.visitedTiles.length - 1].x].value = 1;
                              this.tilesArray[this.visitedTiles[this.visitedTiles.length - 1].y][this.visitedTiles[this.visitedTiles.length - 1].x].frame = 1;
                              this.visitedTiles.push(this.tilesArray[row][col].coordinate);
                         }                        
                         else{
                              if(this.visitedTiles.length > 1 && row == this.visitedTiles[this.visitedTiles.length - 2].y && col == this.visitedTiles[this.visitedTiles.length - 2].x){
                                   this.tilesArray[this.visitedTiles[this.visitedTiles.length - 1].y][this.visitedTiles[this.visitedTiles.length - 1].x].value = 0;
                                   this.tilesArray[this.visitedTiles[this.visitedTiles.length - 1].y][this.visitedTiles[this.visitedTiles.length - 1].x].frame = 0;
                                   this.tilesArray[this.visitedTiles[this.visitedTiles.length - 1].y][this.visitedTiles[this.visitedTiles.length - 1].x].picked = false;
                                   this.tilesArray[this.visitedTiles[this.visitedTiles.length - 1].y][this.visitedTiles[this.visitedTiles.length - 1].x].alpha = 1;
                                   this.visitedTiles.pop();
                                   this.tilesArray[this.visitedTiles[this.visitedTiles.length - 1].y][this.visitedTiles[this.visitedTiles.length - 1].x].value = previousTileValue + 1;
                                   this.tilesArray[this.visitedTiles[this.visitedTiles.length - 1].y][this.visitedTiles[this.visitedTiles.length - 1].x].frame = previousTileValue + 1;
                              }
                         }
                    }
               }
          }
     },
     releaseTile: function(){
          game.input.onUp.remove(this.releaseTile, this);
        game.input.deleteMoveCallback(this.moveTile, this);
          game.input.onDown.add(this.pickTile, this);  
          for(var i = 0; i < this.visitedTiles.length; i++){
            this.tilesArray[this.visitedTiles[i].y][this.visitedTiles[i].x].picked = false;
               this.tilesArray[this.visitedTiles[i].y][this.visitedTiles[i].x].alpha = 1;      
        }
          if(this.visitedTiles.length > 1){
               this.placeNumbers();
          }
     },
     checkAdjacent: function(p1, p2){
          if(gameOptions.diagonal){
               return (Math.abs(p1.x - p2.x) <= 1) && (Math.abs(p1.y - p2.y) <= 1);
          }
          else{
               return (Math.abs(p1.x - p2.x) == 1 && p1.y - p2.y == 0) || (Math.abs(p1.y - p2.y) == 1 && p1.x - p2.x == 0);  
          }
     }
}
Now download the source code and try to add flood fill algorithm on your own.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.