My Project in PHYS291 was to create a functioning version of the classic computer game “Minesweeper”.
Minesweeper is a single player puzzle game with a long history in computing. The base game consists of a m*n grid of cells, each of which may contain a mine. At the start of the game all cells are hidden, and the goal of the game is to open all the cells not containing mines. To help achieve this each open cell shows the total number of mines in the 8 neighbouring cells. The number shown will be called the danger level of the cell throughout this document.
The standard rules also state that the first cell opened should always be safe, and that if a cell with a danger level of zero becomes uncovered all neighbouring cells should automatically be opened. Commonly the player may visual mark a cell if they think the cell contains a mine, flagging it. While this doesn’t advance towards the win condition of the game (opening all safe cells), it does prevent the player from (accidently) opening the cell until it is unflagged.
Figure 1 shows a typical view of game of minesweeper. As is common, the cells with no adjacent mines are blank rather than having a 0. The number in the top left is the number of mines on the board minus the number flagged cells. The number on the top right is the timer, which doubles as the scoring system for the game.
The difficulty of the game can be varied by changing the dimensions of the board and the number of mines. In my game I have set the difficulties as ‘beginner’, that contains a 10*10 board with 10 mines, ‘intermediate’ that has a 16*16 board with 40 mines and ‘expert’ that has a 20*24 board of 99 mines. It is also a ‘custom’ difficulty that allows the player to choose dimensions and number of mines.
What follows is a short description of the various parts of the code. Towards the end of this document is a more in-depth explanation of various parts of the code, the choices I took, the problems I encountered and what I would do differently if I was to do it again. The code itself can be found here.
I decided to do this in three main parts, one handling the game logic and two variants of a user interface; one via console printouts and inputs, and one via the GUI capabilities included in ROOT.
As mentioned, I have created two different versions of the game, both of which can be found
The files in the zip file consists of
The two versions should be possible to run simply by running the respective macros, or, in the case of the console game, by running the executable file.
See Figure 2 for a screenshot of the console game on beginner difficulty. The console game first gives the option of what difficulty to choose. Typing ‘b’, ‘i', ‘e’ or ‘c’ chooses beginner, intermediate, expert or custom respectively. With any of the pre-set difficulties the board is the created, while with custom you need to choose the height, width and number of mines. The height and width need to be between 2 and 26 while the mines have absolute limits at minimum 2 and maximum one less than the total number of cells on the board. It automatically constrains the limits if it is outside the scope.
When that is done, the board will print with the given dimensions. The symbols used is ‘?’ for unopened cells, ‘*’ for an open cell with a mine, ‘F’ if it is flagged and the numbers ‘0’ till ‘8’ for its danger level. Surrounding it is letters that is used as a coordinate system. When interacting with a cell you use these letters to specify the cell, so for example ‘bc’ would point to the [2,3] coordinates.
To open a cell type ‘o’ then the wanted coordinates (in letters) separated by a single space, like “o bc” to open the cell at row 2 column 3. To flag a cell start with the letter ‘f’ instead. To start a new game type ‘r’ to return to the difficulty select stage. Type ‘q’ to quit the game.
When you first start the game, a board with the beginner difficulty will be created. See Figure 3 for a screenshot of the graphical game on beginner difficulty. On the top left you see the four difficulties. Change these before clicking the new game button to use them. On the top right you see the dimensions and number of mines of the currently selected difficulty. These are editable when selecting the custom difficulty.
Below you see to the left a counter that shows how many mines there are minus how many cells are flagged. Next to it is the ‘New game’, that creates a new game from the settings stated above. Then there is the ‘Flagged modus’ check button. This needs to be on to flag cells, as I did not find out how to detect right clicks on buttons. You can however use the keys alt+f to toggle its state. Last on this row is a small text box stating if the game is waiting to start, is running, has been lost or has been won.
Below is the grid itself. Note that using X window to get the graphics is excruciatingly slow, as it slowly updates button after button. There is also a graphical glitch that I’m not sure if is artefacts from the way X window works or is actually there, namely that the buttons from previous games remains in the graphics as ghost buttons. This is many cases not a problem, as they are either overwritten by the new buttons or ends up outside the automatically resizing window, but it is a problem when the board is narrower than the options menu.
Before I started coding the core logic, I used some time planning how I wanted it to work. I therefore had a good idea of how and what wanted code, making it a straightforward process.
The most noteworthy challenge was how to find the danger level of each cell and how to open adjacent cells if the danger level of a cell was zero. Both of theses was in large part solved by having each cell keeping track of all its neighbours. This required some logic to avoid invalid neighbours in the literal edge-cases but was quickly solved. To calculate danger levels each cell with a mine increase the danger level of all neighbouring cells. When opening cells, if its danger level is 0 all its neighbours are added to a queue to be resolved the next run-through of the opening logic loop (technically a stack rather than queue, as it is a first in last out solution).
When I was in large part done with the core, I started on the text-based display. As it was created it was also used to find bugs in the core and an overview of what outputs I needed to send from the core. There were no mayor problems making it.
I ended up making this part mostly as you would program a non-object-orientated language by using functions and passing variables between them. The idea to make this part a separate class, or even structure the core in a way that allowed the displays to inherit from the core game did not occur until I was mostly finished with the GUI display, making it too late to change.
The last and by far the most challenging part was creating a graphical display using ROOT.
This one is structured differently from the others, as it is a selfsustained class. Simply constructing it starts the game. The options and info displays are a variety of buttons of different types, text fields and some number entries. It was mostly straight forwards coding them. The challenges were in the grid.
I decided to create the board by using creating TGTextButtons in a grid. After quite some trouble I managed to find out how to use TGMatrixLayout, which enables one to set a width and/or a height and then simply add more (in this case) buttons that automatically orders themselves in the grid.
The alternative of using buttons would be to make squares myself, find a way to line them up in a grid and differentiate the different states of the cell (closed, open, flagged, etc).
There are two large disadvantages I found using buttons for my purpose. The first one is that I did not find out how detect right clicks on the button. This would be a lot easier if I made my own squares/buttons. This is because there is functionality detect what the mouse does in a Canvas, but not in the classes the TGButton is derived from. This made it necessary to make a ‘Flagging’ button that switch from opening cells to toggling the flag on cells.
The second disadvantage is that I found no way of hiding TGButtons from view. Different difficulties have a different number of cells, so if the game downscales it needs to hide or destroy buttons. Ideally you would hide the excess, making it possible to reuse if a bigger game was then chosen. I did not find out how to hide buttons, and a few indicators that there is no such capability, making it necessary to delete the buttons. This was somewhat cumbersome and seemingly leaving ghost buttons on the board. It would also not surprise me, despite my best efforts, if there is a memory leak related to that process.
On a different note, observant readers may have noticed that there has been no mention of the timer or score system. This is mostly because I quickly realised that to have a live clock you need, as far as I could find, multiple threads working in parallel. Having absolutely no experience with that I decided to make it a low priority, and in the end didn’t have time to do it. There may be easy ways to do it in the ROOT library, but I did not stumble over it. If it only was to be the console version on the other hand, I could have simply noted the system time at first click and printed the time difference each time the display updated.
This was my first proper project using ROOT, and among the biggest projects using C++ so far. This resulted in learning a lot about the two as I coded, making me realise lots of things I should have done often too late to fix it.
One such realisation how you actually program in a more object orientated way. Until I was working with the GUI part, and fiddling with all the classes there, I had in large part seen on classes as structs with get and set functions. This can be seen quite clearly in my console game, and it more C style structure. I now have a somewhat better awareness of how classes also can be like an isolated environment with functions and essentially “global” variables, allowing you among other things to make isolated programs. As such a better approach to this project would be to make the core similar to what it is now, but have the display functions inherit from it, making it unnecessary to pass data in and out of the functions.
A different problem was the use of the premade buttons, although I’m not sure making my own would have been any easier. On the plus side self-made buttons would have easily enabled the use of right click and an easier time removing them (to avoid the graphical bug previously mentioned), but it would require me to create (or find) methods to create the buttons in a grid, make the graphics to differentiate the different states. All in all, I don’t know what would have been better. A more experienced programmer would probably benefit from making it themselves (or knowing how to change the buttons to accept right clicks), but I would possible not have time to do it.
In the end I did manage to make a acceptable product, that while containing quite a few flaws has been an effective learning experience.