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.
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
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
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.
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.
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
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
int_startTime = new Date(); //get the current time
timerActive(); // run the timer script
var int_currentTime = new Date();
var int_gameDuration = parseInt(int_currentTime—int_startTime);
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.