Rewriting a Minesweeper - Part 2

Rewriting a Minesweeper - Part 2

Happy new year to all. I guess as I am getting old, I am not feeling enthusiastic enough about new years. There is one thing I am excited about though. Writing a rewriting Minesweeper game in Swift.

Yesterday was quite a productive day in terms of my progress. Here's the list of things I did yesterday.

  1. Got rid of as much as external dependencies as possible - I started a new Swift project from scratch so it didn't have any dependencies to start with. As much as possible I would like to keep it that way
  2. Divided the project into their own logic sections - After dividing project into sub-sections, this is how the project structure looks like now.

Well, there aren't many screen in this game, so I kept one screen (And possibly more in the future) under Screens folder. The similar logic goes for grouping other logical parts of the app as well

3. Converted old Objective-C code into Swift piecemeal - This was quite a challenging since it's been over 3 years since I finished the code. However, by going step-by-step I achieved the goal of porting all the required code to make the game running

Header view:

This view represents the top header on the game home screen. It is stored as a separate UIView subclass for easier testing and isolation. It also has its own ViewModel to pass necessary parameters

As you can see from the screenshot ViewModel is contained inside view and it has all the logical flags necessary for setting up view.

In addition to it, it also has few closures to convey the button actions user has executed such as reveal mines, reset, and changing grid size

View Model for Minesweeper home page

I also had to create a separate view model for main minesweeper screen to store all the required constants, configurations, and utility method to get values of computed properties.

As you can see it just shows properties from ViewModel. Our view controller is supposed to use it as a brain to compute view and setting up things around

Generating mines and assigning numbers of adjacent cell

This was the second most difficult task to generate mines and assign number to calls around it (Depending on how many mines it is surrounded with). Let's look at the code,

  1. This method is used to generate random mines location on the screen
  2. Line 275 gets the total number of tiles on the screen. Since we assume grid is always going to be square, we get this number simply by squaring parameter totalNumberOfTilesInRow from ViewModel
  3. Now we run the while look on Line 277 until desired number of mines have been generated (This number too comes from the ViewModel)
  4. Here we use the dictionary names minesLocationHolder to store all the previously generated mines. If we generate a random tile number to place mine on and that number had already been generated before, we will ignore this and run the while loop again
  5. We are also keeping a track of number of mines generated so far so as to keep comparing it with desired number of originally wanted
  6. On the last line we are calling another method neighboringCellsForGivenMine to populate the tiles neighboring mine with desired numbers
  1. On Line 289 we are getting the array of integers which indicate the immediate neighbors of given mine. It computes neighbors in all 8 directions
  2. Second, we are maintaining the array names numberOfSurroundingMinesHolder which is the dictionary of type [Int: Int]. It stores the tile sequence as a key and number of neighboring mines as a value
  3. We iterate over neighboring tiles of each mine. It that tile does not exist in the numberOfSurroundingMinesHolder dictionary, that means we encountered it for the first time. If it already contains, we encounter it for other mine, we increment the count by 1 to indicate that it has been surrounded by 2 mines and so on.

If we visualize these both things - Mines +  Tiles with neighboring mines count denoted on them + Tiles without any sequence on it, it will look like this for 5 X 5 grid,

Mine Button

Before moving forward, let me write a little bit about the custom MineButton that I created for the sake of this game. In the picture above, the each tile you see is an instance of MineButton.  It has following structure

As you can see it too has a ViewModel associated with it. It stores properties such as whether tile is mine or not, sequence of surrounding mines, current state etc. It also has couple of closures for callback. One is used to trigger when tile is selected and other to trigger the end of the game in case selected tile turns out to be a mine.

It also has additional public methods which I haven't listed in the code above, but they are used for following purposes,

  1. Configuring button with information whether it's mine or not or updating title with number of adjacent mines
  2. Showing images of mines when user wants to cheat in the game
  3. Hiding the mind images as user uncheats the game
  4. Updating background color of tile based on the current state - Selected, unselected, question marked

Laying out views

This is the next step. We have all the small pieces, but how do we convert them into a real grid to display all those tiles on the viewport? Let's start laying out these views. First we need to things,

  1. Total grid dimension (length or width) in terms of pixels. We have a utility method on the ViewModel to compute this value

2.  Successive tile increment - To get the point where we will lay the next tile. This can easily be calculate from tileWidth and gutterSpacing

Let's look at the code to start laying out those views,

  1. We have two for loops set up to successively create and style successive tiles horizontally and vertically respectively
  2. On Line 150 we calculate the sequence number to assign to the tile. This number lies between 0-pow(numberOfTilesInRow, 2)
  3. Next we check if that sequence number exists in the minesLocationHolder. If it does, this sequence number belongs to mine
  4. Next we query numberOfSurroundingMinesHolder to get number of surrounding mines for the tile with given sequence number. This is important information for a stateViewModel associated with that tile
  5. Next we set gameOver and tileSelected closure to given tile. For game over, we will simply show the dialogue indicating that game is over.
  6. For what happens when tile is selected, we will call another method highlightNeighboringButtons to recursively highlight neighboring tiles. This will be covered in later parts
  7. Between Line 168 and Line 172 we have another code which stores tiles in either minesButtonsHolder or regularButtonHolder. This is used when we want to enter cheat mode to reveal mines and also while highlighting surrounding tiles when user selects a tile other than the mine
  8. On Line 174 we have an interesting thing. It adds the long press gesture to the tile. This is similar to the feature in original minesweeper where if user is not sure, they can right click to mark that tile with maybe mark and it looks like this,

9. As indicated on Line 178 and Line 180, we also keep incrementing x and y values by successiveTilesDistanceIncrement until we reach the boundary gridDimension set in both directions

10. Once everything is ready, we call the method layoutViewsWithMinesweeperGrid which is basically auto layout magic to lay grid and header nicely so that it looks good without violating any auto layout rules

Finally, after everything, grid looks like this - RED being default tile color when it's in unselected state,


This is it for now. In the next blog post I will cover more things such as highlighting tiles when one is selected, cheat mode, and scoring features. Until later folks!