Building a Minesweeper-type game in JavaScript

JavaScript can be fun. We show you how to build a simple Minesweeper game with nothing but a bit of JavaScript legerdemain.

After taking on a student for his placement year, we gave him some training on the skills he would need to use in his role. This was primarily JavaScript, CSS, and HTML. Then we gave him a little project to see how much he knew and how he looked for information using the variety of methods at his disposal—other team members, books, or the Internet, for example.

The project was to produce a Web-based, stand-alone version of the classic Windows game Minesweeper. As his mentor, I also had to develop a working version so that I could work through any issues with him as they appeared, and so that I could evaluate and provide feedback on the final version he submitted. This article covers my implementation of the project.

The basics
Minesweeper is a very simple game—a board is populated randomly with a known number of mines and the object of the game is to locate all the mines on the board in the best time possible without actually landing on one. To assist in this process, whenever you click on a square, the square tells you how many "mines" are in surrounding squares. If none are found, the program expands outwards from that location until a square is found in each direction that has at least one mine as its neighbor.

Building the board
To build the game board, I decided to use a table containing DIV elements, each able to be uniquely referenced. I had written a board-generation function for a previous JavaScript game—a Chess implementation—and so was able to reuse that code. But in Minesweeper the board can be of variable length, so I added two variables to define the bounds of the board, which could be amended by the user. The source for this function can be seen in this listing.

This function is fairly straightforward. First we initialize a variable—int_cellNo—to store the total number of squares created so far. We then print the start of our table tag and enter a loop to create each row, within which is a loop that creates the columns in that row. Each of the table cells in the row has a unique name and reference, and each cell contains a single DIV element. The number of rows and columns that we create is determined by the values of int_rows and int_columns, respectively. This demonstration shows the result of the above function and, therefore, the initial state of the board.

Placing the mines
Having built the board, the next challenge was to decide on how many mines to place on the board and how to place them in a random distribution across the playing area. I settled on the following function to determine the number of mines to be used:
((Number of Rows) * (Number of Columns)) * 0.25

This seems to give an decent number of mines on any size of board. Next came the function to place the mines on the board. For this I used the random function of the JavaScript Math class and some coding. The process for associating a mine to a square on the board was defined by the following pseudocode:
If we have Mines left to lay
    Generate a random Row reference.
    Generate a random Column reference.
    If the random Cell reference is on the board
        If there is not a mine already on that square
            Associate the mine with that square.
        End if
    End if
End if

After some initial testing, which showed a large number of mines at the top left and bottom right of the board and rarely any in the opposite corners, I added a weight to the random row generation lines to ensure a more even spread. I decided to pull out the function that checks if a mine already exists at a given square into a separate function—mineAt—so that it could be reused during the game when the player clicks on a square. The final code for the "minelayer" function can be found in this listing.

User dialog
In addition to the board, a user dialog is required to show the time taken, the type of move selected—normal, set flag, unset flag—the number of turns the user has taken, and some other information.

At this point, I had to decide how many flags the user would be allowed to use during the game. The player needs to have at least as many as there are mines on the board, but not so many that they can simply cover large swathes of the board with them to avoid actively playing the game. Eventually I settled on the following algorithm :
Number of Mines + (Number of Mines * 0.2)

I felt that this number provided an appropriate number of flags for the user to use. The user dialog can be seen in this demonstration.

Let the game begin
Now that the board and the supporting dialog are created, we can begin looking at the mechanics of playing the actual game. The first action that a player performs is to click on a starting square, so I added event handlers to each of the DIV elements on the board. The first one—theCell—assigns the reference of the current square to a global variable, the second—sweepSqrClicked—was a handler function that used the following pseudocode :
If the Game is not over
    Increment the number of player moves
    Perform the Move
    Update the user dialog
End if

The perform-the-move function has three main actions to handle—a normal click on a square, the placing of a flag on a square, and the removal of a flag from a square. Also, if it is the first time that the function has been run, it needs to start the timer. This function uses the following pseudocode.

Time gentlemen, please
The first thing that the function checks is if this is the first run of this function, and if so we start the timer running. The timer functionality simply sets the current time in milliseconds and then calls another function—timerActive—which updates the timer on the user display and then calls itself via the JavaScript setTimeout function to continue updating the timer. The code for the Timer component is shown below:
function startTimer()
    int_startTime = new Date(); //get the current time
    timerActive(); // run the timer script
function timerActive()
    var int_currentTime = new Date();
    var int_gameDuration = parseInt(int_currentTime—int_startTime);
    var int_seconds;
    change(timer,int_seconds); // update the timer
    timerID=setTimeout("timerActive()",1000); // self call

Making a move
The function checks the user dialog to see what type of move the user has made, then runs the appropriate code, depending on the selection. In all cases, we use the EVAL command to convert the provided coordinates into an object reference to that square.

For a normal move, we check that the square selected contains the default value. Then we call the mineAt function discussed earlier to see if the selected square contains a mine. If it does, we call the gameOver function. If not, we count the number of mines in surrounding squares using the checkSurrounding function, which simply calls mineAt on the surrounding squares and updates the board with the total number of mines found.

Originally, the checkSurrounding function was part of the main "move handler" function, but during testing it caused an out-of-memory error in IE when trying to deal with the expansion of a large number of squares with no adjacent mines. That's why it is now a separate function.

If at least one of the surrounding squares contains a mine, we update the square to show the number of mines surrounding the current square. If there are no mines, we call the current function for each square surrounding the current one. This approach initially caused errors when the selected square was on the edge of the game board because it would try and process squares that did not actually exist on the board. To resolve this, an additional IF statement was added to ensure that the given square was within the game board before it was processed. The final action in the case of a normal move was to update the moves-made counter in the user interface.

If the user is laying a flag, the functionality is similar. We get a reference to the selected square, and if there are flags left, we check that only the default value exists on that square. This ensures that you don't waste a flag by placing it on a square that has already been checked or currently contains a flag. Once these conditions are satisfied, we place the flag in the square, then update the user interface to show the remaining number of flags and unchecked squares before resetting the move type to normal. If there are no flags left, we inform the user with a pop-up.

The final move type is the removal of an existing flag; in this case, we check to ensure that the selected square currently contains a flag. If so, we ask the user to confirm that this is what he or she wants to do, and if the user confirms the action, we reset the value of the square to the default value and update the user interface.

The final check this function performs is to see if there are any squares remaining. If not, and if the user has not clicked on a mine, the user has completed the game and we inform him or her of this fact.

Winning and losing
There are two outcomes of the game: Either the user clicks on a mine, or the user has checked or flagged every available square on the board. In the former case, the gameOver function is called. This function stops the timer and sets the game-over flag before calling the showMines function to highlight the location of all the mines on the board. If the user has won by finishing the game without landing on a mine, the same end-of-game functions are completed, plus the user is informed that he or she has completed the game.

The showMines function simply loops through the list of mine locations created by the mineLayer function and sets the background of those cells to red to indicate that a mine was present on that square. This does not overwrite the contents of the cell, so the user can see how many of his or her flags were placed correctly.

This was as far as I needed go to produce a working solution to the problem. While there were some differences between my version and the version written by the placement student, on the whole they functioned the same. The final source code can be seen in this listing, and the game can also be played online.

Editor's Picks