Minesweeper in Javascript and Canvas

Grid Tile object

Each tile on our grid will be represented by a Tile object, that will store the x and y position of the tile, a boolean indicating in the tile hasMine, the danger posed by the number of adjacent tiles that have mines also, and the currentState of the tile, which is hidden by default.

function Tile(x, y)
{
	this.x			= x;
	this.y			= y;
	this.hasMine		= false;
	this.danger		= 0;
	this.currentState	= 'hidden';
}

The Tile object needs a method, calcDanger, which we use to calculate the danger value for a given tile by checking all of the neighbouring tiles, starting at [(x - 1), (y - 1)], ending at [(x + 1), (y + 1)]. In the example grid, where T marks the current Tile, X marks the tiles which are examined:

- 1 2 3 4 5 6 7 8 9
1
2 X X X
3 X T X
4 X X X
5
6

Here, the tile we're checking is x = 2, y = 3, so we check tiles x = 1, y = 2 through x = 3, y = 4. Our code to begin checking these neighbours is as follows:

Tile.prototype.calcDanger = function()
{
	var cDiff = difficulties[gameState.difficulty];
	
	for(var py = this.y - 1; py <= this.y + 1; py++)
	{
		for(var px = this.x - 1; px <= this.x + 1; px++)
		{

For readability sake, we've also created a reference to the current difficulty information, cDiff.

We can now check and see if the current neighbour we're looking at is either out of bounds (px or py are less than 0 or greater than or equal to the current difficulties (cDiff) width or height, or if it's the Tile we're calculating danger for. In either of the cases, we continue on to the next tile without doing anything.

			if(px==this.x && py==this.y) { continue; }
			
			if(px < 0 || py < 0 ||
				px >= cDiff.width ||
				py >= cDiff.height)
			{
				continue;
			}

If it's a valid tile but not the tile we're calculating danger for itself, we check if the tile has a mine. If so, we increase the danger level.

			if(grid[((py*cDiff.width)+px)].hasMine)
			{
				this.danger++;
			}
		}
	} 
};

After this check we can also close the nested loops and the method itself.

The Tile class also has a flag method. This allows the player to flag tile they believe have a mine so they do not accidentally click on them, and to keep track of their progress in isolating mines. This method, when called, simply sets the tiles currentState to flagged if it was hidden, or hidden if it was flagged (to allow flags to be removed).

Tile.prototype.flag = function()
{
	if(this.currentState=='hidden') { this.currentState = 'flagged'; }
	else if(this.currentState=='flagged') { this.currentState = 'hidden'; }
};

The click handler for tiles firstly checks if the tiles currentState is hidden - if it is not, we don't want to handle click events for this tile so we return our of the method immediately:

Tile.prototype.click = function()
{
	if(this.currentState!='hidden') { return; }

If, however, the tile hasMine value is true, the player has lost the game, and we call the gameOver method to handle this.

	if(this.hasMine) { gameOver(); }

If there is no mine but the danger level is not 0, we make the tile visible:

	else if(this.danger>0) { this.currentState = 'visible'; }

Otherwise, when the danger is 0, we'll both show the tile and reveal its neighbours:

	else
	{
		this.currentState = 'visible';
		this.revealNeighbours();
	}

As we may have completed the game by revealing this tile, we'll call the checkState function to see if all safe tiles are cleared, and close the method.

	checkState();
};

Revealing neighbours

If a tiles danger is 0, when it is revealed we also reveal all neighbours that are currently hidden, as we know they cannot possibly be mines. We do so with the revealNeighbours methods, which begins in the same way as the calcDanger method, by creating a shorthand reference to the current difficulty (cDiff), and then iterating over all neighbouring tiles:

Tile.prototype.revealNeighbours = function()
{
	var cDiff = difficulties[gameState.difficulty];
	
	for(var py = this.y - 1; py <= this.y + 1; py++)
	{
		for(var px = this.x - 1; px <= this.x + 1; px++)
		{

As with the calcDanger method we also skip any tiles that fall outside of grid bounds, and do not need to process the tile for which this method is called.

			if(px==this.x && py==this.y) { continue; }
			
			if(px < 0 || py < 0 ||
				px >= cDiff.width ||
				py >= cDiff.height)
			{
				continue;
			}

To make the code simpler to read, we now calculate the index in the grid array of the current neighbouring tile we're looking at:

			var idx = ((py * cDiff.width) + px);

...and check this neighbours currentState is hidden. If so, we set the currentState to visible, and if it also has a danger level of 0 we also reveal its neighbours:

			if(grid[idx].currentState=='hidden')
			{
				grid[idx].currentState = 'visible';
				
				if(grid[idx].danger==0)
				{
					grid[idx].revealNeighbours();
				}
			}
		}
	}
};

We also closed the nested loops and ended this method, as we're done with the Tile object.

Page loaded in 0.01 second(s).