Swift enum case study Feature Image example

Tic tac toe, through enum



On the last two blog post, Love for enum & continue…, we build our base for a more advance talk. Which leads us to this blog post were we will implement our already learned knowledge from those two blog post to build a case study here. No doubt enum plays an important role on Swift learning. A case study buildup through step by step cloud guide us to the why and how of enum on Swift, study through example and real time implementation will solidify our enum learning. So let us not delay any further. Let’s build the case study, A simple Tic tac toe game.

For a more in-depth knowledge on enum we can visit the enum hub page, where we listed all the blog post related with Swift enum.

Special note: To build up this case study our first choice was always enum among other options, struct or class. We can always go through the apple blog on which the choosing among Value Type and Reference Type are beautifully describe. If you haven’t read that one, I will request you do.

So we gonna build a very basic game Tic tac toe. Ok no UI, sorry! Logic part only. Come on we are building a Swift enum based case study not a full phase Tic tac toe with example UI. And if you have the brain part then how long to build the UI. Ready?

Background

Player

Tic tac toe is a two player game. So two cases, Perfect for enum:

enum Player {
    case First
    case Second
}

Q/A:

  • Why do we choose enum over struct and class?
  • How about Bool as it has only two possibilities?

class, Why not

We do not need any instance of NSObject and also we are not to change the status of a player, first player will not be second and vice versa. So class is not an option.

struct, why not

Ok from the above assertion it should be a value type object. Now we can clearly see it has two state, First and Second, and at a time only one will be active, so this makes it very simple to choose. struct can be used if there are some vars or state which will live together. So struct not a good option.

Bool, Why not

Player has two state, First and Second. So isFirstPlayer: Bool instead of Player is a good option, ahh cloud be! But wait, will we always use something like isFirstPlayer? doSomething : doAnotherThing whenever we need to make a decision? even to assign a var? Bool type checking is not pretty 😉.

Square

On 3X3 Tic tac toe, there will be total of 9 square. We can define them as follows:

enum Square {
    case One_One, One_Two, One_Three
    case Two_One, Two_Two, Two_Three
    case Three_One, Three_Two, Three_Three
}

Again we do not want value mutability, class. And Square will have only one instance and that instance do not have any other var/instance to live together, struct.

Additional Q/A
Why Array or any other collection type was not chosen instead of the enum, Square?
We already answered that on Love for Enum blog post.

Square to be a RawRepresentable

So far we have only declared the nine squares, but have not assign any value to those squares. We need to have some kind of identifier to uniquely identify each square.

So can we use Int as the RawRepresentable of Square?

Hmm it solves the unique identifier issue. But there is more. We also need to compare the values among square to find the winning streak. And one of them is the diagonal winning streak. Single value Int will not be enough. The winning streak is always a two dimensional value to be compared, unless someone wants to write down all the winning combination/streak one after another on a static way.

So now we know we need to have a two dimensional value, that mean tuple. We can call this tuple as Position. So here we go:

extension Square: RawRepresentable{
    typealias Position = (row: Int, column: Int)
    
    var rawValue: Position{
        switch self {
        case .One_One: return (row: 1, column: 1)
        case .One_Two: return (row: 1, column: 2)
        case .One_Three: return (row: 1, column: 3)
            
        case .Two_One: return (row: 2, column: 1)
        case .Two_Two: return (row: 2, column: 2)
        case .Two_Three: return (row: 2, column: 3)
            
        case .Three_One: return (row: 3, column: 1)
        case .Three_Two: return (row: 3, column: 2)
        case .Three_Three: return (row: 3, column: 3)
        }
    }
    
    init?(rawValue: Position) {
        switch rawValue {
        case (row: 1, column: 1): self = .One_One
        case (row: 1, column: 2): self = .One_Two
        case (row: 1, column: 3): self = .One_Three
            
        case (row: 2, column: 1): self = .Two_One
        case (row: 2, column: 2): self = .Two_Two
        case (row: 2, column: 3): self = .Two_Three
            
        case (row: 3, column: 1): self = .Three_One
        case (row: 3, column: 2): self = .Three_Two
        case (row: 3, column: 3): self = .Three_Three
            
        default: return nil
        }
    }

On the above Square is conforming the RawRepresentable protocol. As a result Square need to provide the conversion from and into the rawValue, Position. We had a detail talk on previous enum blog post.

Winning streak

We can have one of the three winning streak, horizontal or vertical or diagonal. So by considering two thing, immutable values(discard class) and only one among three case will be active(discard struct), we can use enum.

enum WinningStreak: String{
    case horizontal
    case vertical
    case diagonal
}

We are setting String as the raw type of WinningStreak, because later down the road we will print the winning combination. BTB we already published the raw value blog talk.

horizontal WinningStreak can happen either on the first, second or third row. Similarly vertical and diagonal has multiple possibilities. We are discarding those possibilities for making this blog post concise.

Game states

We can have either inProgress, draw or a win state for a game. Again one of three options/case. And immutable value is not needed. So what do you think? It should be an enum:

enum GameState {
    case inProgress
    case draw
    case win(Player, WinningStreak)
}

The win case have an associated value and it is a tuple of Player and WinningStreak.

Why associated Value

Q/A: Why the associated value on win case and why others don’t have the associated value?

Hmm seems like a silly question, but not so. Answer to this question will help us to add associated value to our case when needed.
The functionality of associatedValue is to provide a way to that corresponding case to have an extra storage for additional info. You can read more on our previous blog post.

Now the inProgress and draw do not need any more info. Because both of these two cases are stating the game state where both the Payers are involved. Also the game state is self explanatory for those two cases.

But on the case of win two obvious question comes up. Who win? and which combination was used? So these two questions are pointing to Player and WinningStreak.

Game state as CustomStringConvertible

GameState is the end of game, once we got either draw or win, we can publish the game result on a String format. For inProgress we can just say the game is still going. The CustomStringConvertible protocol will serve us the best on this regard. It has a description var, which we need to implement for all the cases of GameState.

extension GameState: CustomStringConvertible{
    var description: String{
        switch self {
        case .inProgress:
            return "Game is running"
        case .draw:
            return "Game ended up in a draw"
        case .win(let player):
            return "\(player) won on \(streak) combination"
        }
    }
}

Game logic

Now we need to calculate the WinningStreak. First We will write down a function to calculate the result. We will feed this function with the sequence of a single player.

func result(for sequence: [Square]) -> WinningStreak?{
    guard sequence.count == 3, sequence[0] != sequence [1], sequence[1] != sequence[2], sequence[2] != sequence[0] else { return nil }
    
    if sequence[0].rawValue.row == sequence[1].rawValue.row && sequence[1].rawValue.row == sequence[2].rawValue.row {
        return .horizontal
    }
    if sequence[0].rawValue.column == sequence[1].rawValue.column && sequence[1].rawValue.column == sequence[2].rawValue.column {
        return .vertical
    }
    if (sequence[0].rawValue.row == sequence[0].rawValue.column && sequence[1].rawValue.row == sequence[1].rawValue.column && sequence[2].rawValue.row == sequence[2].rawValue.column) ||
        Set(arrayLiteral: sequence) == Set(arrayLiteral:  [Square.One_Three, .Two_Two, .Three_One]){
        return .diagonal
    }
    return nil
}

We will return nil on three situation.

  • If the sequence is lower or higher than 3.
  • If any two of the sequence elements are same.
  • And if no WinningStreak can be formed from the sequence.

The horizontal and vertical checking is pretty simple. Same row for all three combination will result on horizontal win, on the other hand three same column will result on vertical win.

For diagonal win, there are two possibilities. On one possibility all the elements of sequence will have the same row and column individually. And another possibility is kind of a static check of the square.

For the static check we are using Set because we do not want any fixed chronology of elements, they can appear on any combination.

Game play

This is the last part of our Tic tac toe. We already laid down the necessary parts of the game. Now Game On 👊.

We will call our game play as TicTacToe. We will put one Square at a time. And will get the GameState immediately. Once we have a win we will also get that.

class TicTacToe{
    
    private var sequence = [Square]()
    
    func progress(_ square: Square) -> GameState? {
        guard sequence.count < 9 else { return nil }
        
        sequence.append(square)
        
        guard sequence.count > 4 else { return .inProgress }
        
        var firstPlayerSequence = [Square]()
        var secondPlayerSequence = [Square]()
        
        for (index, square) in sequence.enumerated(){
            index % 2 == 0 ? firstPlayerSequence.append(square) : secondPlayerSequence.append(square)
        }
        
        if let streak = result(for: firstPlayerSequence) {
            return .win(Player.First, streak)
        }else if let streak = result(for: secondPlayerSequence){
            return .win(Player.Second, streak)
        }else{
            return .draw
        }
    }
}

We used class here rather than enum or struct. Because we need to mutate the sequence var. So as it is mutating it’s own state we need to use class.

Now let us move our focus to progress function. As sequence will have max 9 elements so any more than that will result in nil. Once sequence got the 4 elements we will start calculating the WinningStreak. On the process of calculating the WinningStreak we are separating the elements into two array. Even index element goes to firstPlayerSequence and odd populates the secondPlayerSequence.

End talk

So that is it. Tic tac toe is ready. Again our main concern here was to watch the use of enum, not the Tic tac toe game. So there are still some works can be done to improve this game. You can find the source code on GitHub. This Swift enum case study based talk will not be successful unless the example code been studied. So please go through the example code of this case study to have a solid idea on Swift enum implementation.

TDD teaser

If you already saw the code on GitHub, you will see a lot of test codes, Actually 1.4 times than the source code. What happens there !!!

Well it was TDD. This project was started using TDD and continue till the end. To be honest it is always thrilling to write test code first then the production code. If you are interested then you can have a look at the Cycles of TDD on Robert C. Martin (Uncle Bob)’s blog.

On some future we will have a TDD series on iOS using Swift. And I can guarantee you one thing, it will not be a blog series it will be a video series. So much talk is not possible to be written down on just one or two blog post. Best possible way is to describe through video series. Also the short code to run the test on a playground file directly taken from Writing unit tests on playground blog post written by Sundell.

So thats actually ends our talk with enum. See you around. Till then take care. 🖖

2 thoughts on “Tic tac toe, through enum

Leave a Reply

Notifications for mobidevtalk! Cool ;) :( not cool