Click here to Skip to main content
Click here to Skip to main content

Minesweeper

, 20 Sep 2012 BSD
Rate this:
Please Sign up or sign in to vote.
How to create a minesweeper game for three platforms with only one code?

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

// draws the cell
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)
{
    // draw bacground
    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);
 
    // draw animation if the cell is animated
    if (this.frame > 0) {
        canvas.drawBitmapNine(res.images.explosion[this.frame], this.width / -2, this.width / -2, this.width / 2, this.width / 2);
        return;
    }
 
    // draw text or mine icon
    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

// 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()
    {
        // increase frame number
        if (this super.frame < res.images.explosion.length-1) {
            this super._uncovered = true;
            this super.frame += 1;
        } else {
            // zero timer variable after last tiper repetition (after dimer dispose)
            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);
    // do nothing if user press uncovered cell or game is paused
    if(this._uncovered || this.scene.paused)
        return;
 
    // start timer
    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);
    // do nothing if user press uncovered cell or game is paused
    if(this._uncovered || this.scene.paused)
        return;
 
    // dispose timer
    if (this._timer != null) {
        this._timer.dispose();
        this._timer = null;
        // unmark or uncover cell
        if (this._mark) {
            this._mark = false;
            this.scene.counter.count ++;
        } else {
            this._uncovered = true;
            if(this._state == 0) {
                // if _state is 0 we need to uncover larger area (all 0 in surounding cells). it manages uncover function
                this.uncover(this.row, this.column);
            } else {
                // if _state is _state_MINE we found mine
                if(this._state == _STATE_MINE) {
                    // inform game scene that mine was uncovered
                    this.open(true);
                } else
                    // inform game scene that cell without mine was uncovered
                    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;
    // left and top border of table
    var left = (System.width - 9*cellSide) / 2 + cellSide / 2;
    var top = System.height - this.rows*cellSide;
    // create cells
    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

// deploy mines tu the table and also calculate all numbers
function _placeMines()
{
    var x,y;
    // calculate random positions of mines
    for(var i = 0; i<this.mines; i++) {
        do {
            // generate x and y position
            x = rand(this.columns - 1);
            y = rand(this.rows - 1);
            // if there is mine on generated position generate new position
        } 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

// add one to all neighbours of mine
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

// uncover cell with no number with all null neighbours.
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,       // length of animation in miliseconds
    });
    animator.addSubject(function(state) {       // state starts from 1.0 to 0.0
        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.

License

This article, along with any associated source code and files, is licensed under The BSD License

Share

About the Author

PavolSatala

Slovakia Slovakia
Author develops in various programming languages included: C++, Javascript, and PHP. Last year he creates mobile cross platform applications in Moscrif SDK.
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmemberMember 1109084111-Oct-14 4:40 
GeneralVery good PinprofessionalPaulo Augusto Künzel21-Oct-13 3:08 
GeneralMy vote of 5 PinmemberHossainCo28-Oct-12 7:04 
GeneralMy vote of 5 PinmemberMihai MOGA15-Oct-12 23:03 
GeneralMy vote of 5 Pinmemberamanarora BB24-Sep-12 1:05 
GeneralRe: My vote of 5 PinmemberPavolSatala24-Sep-12 6:19 
GeneralMy vote of 4 PinmemberSlacker00712-Sep-12 1:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411028.1 | Last Updated 20 Sep 2012
Article Copyright 2012 by PavolSatala
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid