For this post I am going to take a break from my regular story about how I rewrote the minesweeper game to Swift. This is more about the beautiful refactor and how I was able to eliminate lot of boilerplate code with simple algorithm to achieve what the old and poor code did.

Goal:

To explain the goal, let me show it from the visual perspective. Let's say we're playing the Minesweeper in 3 X 3 grid which looks like this with each tile numbered going from left to right and then top to bottom.

Given this my goal was to get the sequence of all the neighbors for the given tile. This was to keep highlighting neighboring tiles when user taps on one of them. In the beginning I had considered 9 different cases based on where tile is located.

  1. 4 corners
  2. Any tile in 4 border rows (Except the ones at corner)
  3. Any tile falling inside the grid

This prompted me to write very poor and verbose code to get such neighbors. The original code looked like this,

func neighboringTilesForGivenMineTile(with minesTileSequenceNumber: Int, totalNumberOfTilesInRow: Int) -> [Int] {

    var resultantNeightbors: [Int] = []

    let topRightCorner = totalNumberOfTilesInRow - 1;
    let bottomLeftCorner = totalNumberOfTilesInRow * topRightCorner

    if (minesTileSequenceNumber == 0) {
    resultantNeightbors = [minesTileSequenceNumber + 1, minesTileSequenceNumber + totalNumberOfTilesInRow, minesTileSequenceNumber + totalNumberOfTilesInRow + 1]
    } else if (minesTileSequenceNumber == topRightCorner) {
    resultantNeightbors = [minesTileSequenceNumber - 1, minesTileSequenceNumber + totalNumberOfTilesInRow - 1, minesTileSequenceNumber + totalNumberOfTilesInRow]
    } else if minesTileSequenceNumber == bottomLeftCorner {
    resultantNeightbors = [minesTileSequenceNumber + 1, minesTileSequenceNumber - totalNumberOfTilesInRow + 1, minesTileSequenceNumber - totalNumberOfTilesInRow]
    } else if minesTileSequenceNumber == topRightCorner + bottomLeftCorner {
    resultantNeightbors = [minesTileSequenceNumber - 1, minesTileSequenceNumber - totalNumberOfTilesInRow - 1, minesTileSequenceNumber - totalNumberOfTilesInRow]
    } else if (minesTileSequenceNumber < topRightCorner) {
        // Top horizontal Row
        resultantNeightbors = [minesTileSequenceNumber - 1, minesTileSequenceNumber + 1, minesTileSequenceNumber + totalNumberOfTilesInRow - 1, minesTileSequenceNumber + totalNumberOfTilesInRow + 1, minesTileSequenceNumber + totalNumberOfTilesInRow]

    } else if ((minesTileSequenceNumber + 1) % totalNumberOfTilesInRow == 0) {
        // Extreme right vertical row
        resultantNeightbors = [minesTileSequenceNumber - 1, minesTileSequenceNumber + totalNumberOfTilesInRow, minesTileSequenceNumber + totalNumberOfTilesInRow - 1, minesTileSequenceNumber - totalNumberOfTilesInRow, minesTileSequenceNumber - totalNumberOfTilesInRow - 1]

    } else if (minesTileSequenceNumber % totalNumberOfTilesInRow == 0) {
        // Extreme left vertical row
        resultantNeightbors = [minesTileSequenceNumber + 1, minesTileSequenceNumber - totalNumberOfTilesInRow, minesTileSequenceNumber - totalNumberOfTilesInRow + 1, minesTileSequenceNumber + totalNumberOfTilesInRow, minesTileSequenceNumber + totalNumberOfTilesInRow + 1]

    } else if (minesTileSequenceNumber > bottomLeftCorner) {
        // Bottom horizontal row
        resultantNeightbors = [minesTileSequenceNumber - 1, minesTileSequenceNumber + 1, minesTileSequenceNumber - totalNumberOfTilesInRow - 1, minesTileSequenceNumber - totalNumberOfTilesInRow + 1, minesTileSequenceNumber - totalNumberOfTilesInRow]

    } else {
        // Any tile inside grid and not touching any adjacent boundary
        resultantNeightbors = [minesTileSequenceNumber - 1, minesTileSequenceNumber + 1, minesTileSequenceNumber + totalNumberOfTilesInRow, minesTileSequenceNumber - totalNumberOfTilesInRow, minesTileSequenceNumber + totalNumberOfTilesInRow - 1, minesTileSequenceNumber + totalNumberOfTilesInRow + 1, minesTileSequenceNumber - totalNumberOfTilesInRow - 1, minesTileSequenceNumber - totalNumberOfTilesInRow + 1]
    }
    return resultantNeightbors;
}

As you can see I am also passing minesTileSequenceNumber and totalNumberOfTilesInRow which are required parameters to get list of neighbors in any given position

Sure, it does what I want it to do, but it feels like I am assuming so much and hardcoding so many things. It's also difficult to reason about this code just looking at it. Just as you will be in any primitive code, it heavily relies on lot of if-else conditions.

So I thought why not make it more generic with some algorithm? Result was I ended up writing this code like this which produces exact same result.

func neighboringTilesForGivenMineTile(with minesTileSequenceNumber: Int, totalNumberOfTilesInRow: Int) -> [Int] {

    let row = minesTileSequenceNumber / totalNumberOfTilesInRow
    let col = minesTileSequenceNumber % totalNumberOfTilesInRow

    let resultantNeighbors = [Coordinate(row: row - 1, column: col - 1),
                              Coordinate(row: row - 1, column: col),
                              Coordinate(row: row - 1, column: col + 1),
                              Coordinate(row: row, column: col - 1),
                              Coordinate(row: row, column: col + 1),
                              Coordinate(row: row + 1, column: col - 1),
                              Coordinate(row: row + 1, column: col),
                              Coordinate(row: row + 1, column: col + 1)
        ].filter { $0.isCoordinateInGivenRange(low: 0, high: totalNumberOfTilesInRow - 1) }

    let result = resultantNeighbors.compactMap { value -> Int in
        return value.row * totalNumberOfTilesInRow + value.column
    }

    return result
}

struct Coordinate {
    let row: Int
    let column: Int

    func isCoordinateInGivenRange(low: Int, high: Int) -> Bool {
        return self.row.isInTheInclusiveRange(low: low, high: high) && self.column.isInTheInclusiveRange(low: low, high: high)
    }
}

  1. First off it takes two parameters as input. The tile sequence number which has a range of [0, pow(numberOfTilesInRow, 2))
  2. For the given tile sequence, we can easily compute its row and column using sequence number and number of tiles in a row
  3. One we get coordinates of given tile, we compute the 8 immediate neighbors of a given tile on next line and store them into resultantNeighbors
  4. Now, we don't really need all these 8 neighbors. But we need to filter them. Some of them might fall outside the boundary we set for the given box
  5. This is where the filter comes in the picture. Here we iterate over every coordinate and eliminate those where either value falls outside the given range [0, totalNumberOfTilesInRow - 1]
  6. Next, we have an array of coordinates which are valid immediate neighbors of given tile. But we don't really want coordinates. We want sequence numbers of those neighbors
  7. Now we take every coordinate from the array and convert that into sequence number corresponding to that tile number - Essentially mapping array of coordinates into array of ints and return this value