Introduction
Minesweeper is a classic game, known from the Microsoft Windows OS. The objective of the game is to clear an abstract minefield without detonating a mine. The game has been made for many platforms. In this article, we are going to show you how to create this game for smartphones. Since we are developing this game cross-platform, it could be played on Apple iOS, Android and Bada devices.
Background
To overcome platform differences, we will use Moscrif SDK. Thanks to object-oriented JavaScript used as its native language, development is easy fast and cheap. Supported platforms are iOS, Android and Bada.
User interface
User interface is very simple. It consists only from a table with mines, timer, mines counter and menu. After user loses or wins the game, menu drops down and shows ending message and time. The whole user interface is created using vector graphics. The main pros of using vector graphics are that they maintain all details when they are resized and their sizes are smaller.
Mines table
The mines table has nine rows and nine columns and contains ten mines. It is similar to beginner’s level in Microsoft Windows version of this game. Every cell of this table has few different designs:
- normal uncovered cell
- uncovered cell with number
- uncovered cell with mine
- uncovered cell with zero mines in neighbour cells
- marked cell
Mine explosion
When user finds a mine all mines in the table explode. This explosion is created from about 40 frames to create really smooth animations
Image: explosion frames
Development process
We used Moscrif game framework to create this game. The main part of our game is gameScene, which is for table of mines, and includes menu layer for menu buttons.
Table cell
All cells in the table are created as an instance of MineCell class. This class has property state, which says how many mines are in neighbour cells, otherwise it is labeled with -1 and then that particular cell contains a mine. The cell has many different appearances in different situations (covered cell, uncovered cell, marked cell etc..). Appearance for every different situation is drawn by separate functions
Example: Choose suitable draw method
function draw(canvas)
{
canvas.save();
canvas.translate(this.x, this.y);
canvas.scale(0.95, 0.95);
if (this._uncovered == false)
if(this._mark)
this._drawMarked(canvas);
else
this._drawCovered(canvas);
else
this._drawUncovered(canvas);
canvas.restore();
}
Draw method for covered and marked cells are really simple, They only draw rounded rect filled with gradient and vector graphics into it (for marked cell).
Example: draw marked cell
function _drawMarked(canvas)
{
canvas.drawRoundRect(this.width / -2, this.width / -2, this.width / 2, this.width / 2, this.width / 10, this.width / 10, res.paints.markedCell);
canvas.drawPath(res.vectors.flag, res.paints.flag);
}
Drawing an uncovered cell is a little bit more complicated. If the cell has some mines in its neighbour cells, only number of mines are drawn on the same background as used for covered cell. This method may also draw a frame of animation for mine explosion. This frame is drawn if current frame is more than zero. All animation frames are stored in resources into explosion array.
Example: draw uncovered cell
function _drawUncovered(canvas)
{
if (this._state > 0)
canvas.drawRoundRect(this.width / -2, this.width / -2, this.width / 2, this.width / 2, this.width / 10, this.width / 10, res.paints.coveredCell);
else
canvas.drawRoundRect(this.width / -2, this.width / -2, this.width / 2, this.width / 2, this.width / 10, this.width / 10, res.paints.uncoveredCell);
if (this.frame > 0) {
canvas.drawBitmapNine(res.images.explosion[this.frame], this.width / -2, this.width / -2, this.width / 2, this.width / 2);
return;
}
switch (this._state)
{
case _STATE_MINE:
res.paints.text.color = 0xffffffff;
canvas.drawPath(res.vectors.mine, res.paints.text);
break;
case 0:
break;
case 1:
res.paints.text.color = 0xff7DFAFF;
canvas.drawText("1", this.textDimensions[0].w / -2, this.textDimensions[0].h / 2, res.paints.text);
break;
case 2:
res.paints.text.color = 0xff6EFF5B;
canvas.drawText("2", this.textDimensions[1].w / -2, this.textDimensions[0].h / 2, res.paints.text);
break;
case 3:
res.paints.text.color = 0xffFFC300;
canvas.drawText("3", this.textDimensions[2].w / -2, this.textDimensions[0].h / 2, res.paints.text);
break;
default:
res.paints.text.color = 0xffFFC300;
canvas.drawText(this._state.toString(), this.textDimensions[3].w/ -2, this.textDimensions[0].h / 2, res.paints.text);
break;
}
}
Mine explosion
Mine explosion is a simple animation of explosion under water. It consists from 39 frames, which are drawn into table cell in _drawUncovered method. The explosion starts by explode function. This function starts timer, which increases frame number in regular intervals.
Explode: start explosion
function explode(delay = 0)
{
this.timer = new Timer(res.integers.explosionDuration / res.images.explosion.length, res.images.explosion.length);
this.timer.onTick = function()
{
if (this super.frame < res.images.explosion.length-1) {
this super._uncovered = true;
this super.frame += 1;
} else {
this super.timer = 0;
}
}
this.timer.start(delay);
}
User events
The mine cells react onto two user events:
- pointerPressed -> called when user taps the screen
- pointerReleased -> called when user releases his finger from the screen.
The events invokes following three reactions:
- mark cell -> when user presses the cell for a time interval of 400 ms
- unmark cell -> when user taps or presses marked cell for 400 ms
- uncover cell -> when user taps on cell which is not markedv
Pointer pressed
When user taps on some cell the timer starts to check the cell for a long press. After 400 ms the cell is marked or unmarked.
Example: start timer
function pointerPressed(x, y)
{
super.pointerPressed(x, y);
if(this._uncovered || this.scene.paused)
return;
this._timer = new Timer(1, false);
this._timer.onTick = function()
{
var self = this super;
if(!self._mark) {
self._mark = true;
this super.scene.counter.count --;
} else {
self._mark = false;
this super.scene.counter.count ++;
}
self._timer = null;
}
this._timer.start(res.integers.timeInterval);
}
Pointer released
If this event is called before the timer, started in pointer pressed, ends, it means that user taps on the cell for less than 400 ms (short tap). In that case, a cell is unmarked or uncovered.
Pointer released is called inside the object of MineCell class. However, it is needed to let know for game scene purposes that cell was uncovered to f.e.: start explosion of all mines if user found the mine or to uncover more cells, if user found the cell without mines in neighbour cells. To inform game scene that cell was uncovered we used open function.
Example: pointer release
function pointerReleased(x, y)
{
super.pointerReleased(x, y);
if(this._uncovered || this.scene.paused)
return;
if (this._timer != null) {
this._timer.dispose();
this._timer = null;
if (this._mark) {
this._mark = false;
this.scene.counter.count ++;
} else {
this._uncovered = true;
if(this._state == 0) {
this.uncover(this.row, this.column);
} else {
if(this._state == _STATE_MINE) {
this.open(true);
} else
this.open(false);
}
}
}
}
Game scene
Game scene creates table of mines, mine counter, timer and menu. Mines are randomly distributed across the table with nine rows and nine columns.
Create table
The table is created by two for loops - one for rows and one for columns.
Example: create table
function _createTable()
{
var cellSide = res.integers.cellWidth;
var left = (System.width - 9*cellSide) / 2 + cellSide / 2;
var top = System.height - this.rows*cellSide;
for (var i = 0; i<this.columns; i++) {
this.table[i] = new Array();
for (var q = 0; q<this.rows; q++) {
this.table[i][q] = new MineCell({
row : i,
column : q,
x : left + i*cellSide,
y : top + q*cellSide,
});
this.table[i][q].uncover = function(x,y) {this super._uncoverNull(x,y); };
this.table[i][q].open = function(mine) { this super._open(mine); };
this.table[i][q].scene = this;
this.add(this.table[i][q]);
}
}
}
Mines distribution
Mines are distributed randomly. Therefore, The Minesweeper cannot be always solved with 100% certainty. Distribution algorithm consists from two loops. The “for loop” is repeated once for every mine. Into the for loop we added one do-while loop. Coordinates of mines are generated randomly inside this loop. If there are some mines already placed on the generated position, new coordinates are generated. This algorithm is not probably the fastest existing, but it is fast and simple enough to use in our game. In the worst case, the random positions are generated max (mines count) 2 / 2 (na druhu deleno dva).
Example: generate random mines’ positions
function _placeMines()
{
var x,y;
for(var i = 0; i<this.mines; i++) {
do {
x = rand(this.columns - 1);
y = rand(this.rows - 1);
} while (this.table[x][y]._state == -1);
this.table[x][y]._state = -1;
this.minesList[i] = new Array();
this.minesList[i][0] = x;
this.minesList[i][1] = y
this._addToNeighbours(x,y)
}
}
Image: generate random mines’ positions
Digits calculator
Digits in the cells indicate the number of the mines in surrounding squares. These digits are calculated by addToNeighbours function - mentioned in previous code. Behavior of this function is very simple. It only adds one digit to every surrounding cell which doesn’t contain mine. This function is called for every mine.
Example: calculate numbers
function _addToNeighbours(x,y)
{
for (var i = x-1; i < x + 2; i++) {
for (var q = y-1; q < y + 2; q++) {
if (this._inTable(i,q) && !(y == q && x == i) && this.table[i][q]._state != -1)
this.table[i][q]._state += 1;;
}
}
}
Uncover null cells
When user uncovers a cell with no mines, the whole area without mines in surrounding squares will be uncovered. In our game, we solve this problem with _uncoverNull function. It is a recursive function. This function uncovers the cell and then if the uncovered cell was zero it calls _uncoverNull to all surrounding cells.
Example: Uncover area of zero cells
function _uncoverNull(r, c)
{
this.table[r][c].uncovered = true;
this._open(false);
if (this.table[r][c]._state == 0) {
for (var i = r-1; i < r + 2; i++) {
for (var q = c-1; q < c + 2; q++) {
if (this._inTable(i,q) && !this.table[i][q].uncovered && !this.table[i][q].mark)
this._uncoverNull(i, q);
}
}
}
}
Menu
Menu is created as a separate layer added to the game scene. It draws Menu, and creates menu buttons. Menu contains buttons to start new game, restart game and quit game (quit game is displayed only on Andoid and Bada). When user finds a mine or wins the game the menu scrolls down and shows a message together with the game time.
Image: menu
The menu is scrolls down and up with animation effect. This effect is created by Animator object. The animator object calls call-back function set in addSubject function according to duration and transition of animation. The callback function has only one parameter state-> which determines current position in animation (from 0 - start to 1- end). In this function we changed y position of menu which causes its vertical movement.
Example: Scroll menu down
function show()
{
var animator = new Animator({
transition: Animator.Transition.easeInOut,
duration: res.integers.menuAnimationDuration, });
animator.addSubject(function(state) { this super.y = res.integers.menuHeight / -4 + state * 3* res.integers.menuHeight / 4;
});
animator.onComplete = function()
{
this super.showed = true;
}
animator.play();
}
Summary:
This article shows how to create a minesweeper game for mobile platforms, which can be played on about 90% of smartphones on the market with only one code. This free sample shows how to create basic - beginer level game, but you can easily improve the game by additing more levels and other features you like to make it an awesome game.