Minesweeper clone in HTML5

In my two previous block entries I wrote about one possible ways to do simple Slots and Car games on HTML5 technologies. This writeup combines some of those methods and introduces new ones to implement Minesweeper game with a twist.

mine

This game is actually “reverse” minesweeper, or should we say Applesweeper. Players mission is to find all the apples without clicking on of the empty tiles. Score is increased when player finds an apple and is decreased when he misses. Game ends when all the apples are found.

Try out the game here: http://ikonen.me/examples/mine/

As in previous games, hud is a html div, and the game grid is drawn on a canvas. Grey slab that hides tiles content is procedurally generated on offscreen canvas at the startup.

this.slab = document.createElement('canvas');
var ctx = this.slab.getContext('2d');
this.slab.width = this.resolution;
this.slab.height = this.resolution;

ctx.fillStyle = 'grey';
ctx.fillRect(0, 0, this.resolution, this.resolution);

ctx.beginPath();
ctx.fillStyle = 'white'
ctx.moveTo(0, 0);
ctx.lineTo(this.resolution, 0);
ctx.lineTo(this.resolution, this.resolution);
ctx.lineTo(0, 0);
ctx.closePath();
ctx.fill();

ctx.fillStyle = 'lightgrey';
ctx.fillRect(4, 4, this.resolution-8, this.resolution-8);

The this.slab holds off-screen canvas that contains the generated slab image.

Game area size and number of apples are function of screen size, to adapt to different screensizes.

var width  = window.innerWidth;
var height = window.innerHeight;

GRID_W = Math.min( 12, ~~(width / GRID_RESOLUTION));
GRID_H = Math.min( 12, ~~(height / GRID_RESOLUTION)) - 1;
var APPLE_COUNT = ~~((GRID_W * GRID_H) / 8);

For example, Here is the game on iPhone 3Gs.

mine_iphone

Game main loop is passive, when user clicks on the screen click handler sets the location on object that holds the clicked tile x and y coordinates

$('#container').click( function( e ){
    var p = $('#canvas').offset();        
    game.click = {
        x:parseInt((e.pageX-p.left)/game.resolution),
        y:parseInt((e.pageY-p.top)/game.resolution)};
    }
);

Main update handler is called on each frame and it checks if button has been clicked and updates the grid and redraws it if required. Grid is simple array where x and y are mapped as position.

Game.prototype.pos = function( x, y ) {
    return y*this.width+x;
}
Game.prototype.xy = function( pos ) {
    return {
        x:parseInt(pos%this.width),
        y:parseInt(pos/this.width)
    }
}

Grid array values are integers where content is bit masked. Higher bits are used to flag if grid location has slab and apple, empty or number.

var SLAB_MASK = Math.pow(2, 16);
var APPLE_MASK = Math.pow(2, 15);

// grid location (5, 6) has slab and number 3
var pos = this.pos(5, 6);
this.grid[pos] = 3;
this.grid[pos] |= SLAB_MASK;

The slab is removed from location just by negating it

this.grid[pos] &= ~SLAB_MASK;

Draw loop just checks for each position and checks with mask what it contains

for ( var y=0; y < this.height; y++ ) {
    for ( var x=0; x < this.width; x++ ) {
        // Draw each tile
        var s = this.pos(x,y);
       if (s & SLAB_MASK) {
          // Still covered tile
         this.ctx.drawImage( this.slab, x * this.resolution, y * this.resolution )

       } else if (s & APPLE_MASK) {
         // Uncovered apple
         this.ctx.drawImage( tile, x * this.resolution + 2, y * this.resolution + 2 )
       } else if (s > 0) {
         // Neighbour number
         this.ctx.fillText( '' + s ,
                            x * this.resolution + this.resolution/2,
                            y * this.resolution + this.resolution/2)
       }
    }
}

When player clicks on empty tile, recursive function walks through the grid clearing slabs from adjacent empty tiles.

function _empty( x, y, force ) {
    if ( x < 0 || x >= that.width || y < 0 || y >= that.height ) return;

    var pos = that.pos(x, y);
    var d = that.grid[pos];

    if (d && (d & SLAB_MASK) && (force || !(d & APPLE_MASK))) {

        that.grid[pos] &= ~SLAB_MASK; // clear out slab

        // Clear next neighbor if this is empty tile
        if (that.grid[pos] == 0) {
            _empty(x, y - 1) // north
            _empty(x, y + 1) // south
            _empty(x - 1, y) // west
            _empty(x - 1, y - 1) // north west
            _empty(x - 1, y + 1) // south east
            _empty(x + 1, y) // east
            _empty(x + 1, y - 1) // north east
            _empty(x + 1, y + 1) // south east
        }
    }
}

Code is available at GitHub.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: