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.
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.