Minesweeper clone in HTML5
March 25, 2013 Leave a comment
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.
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.
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.