Talking about new frameworks and API's is necessary for a Swift blogger but from time to time it's good to sit down and to code a little. Small Projects provide us with an opportunity to experiment and play without having to worry about implications on an existing code base. Hence I sat down a few weeks back and implemented a game of Tic Tac Toe.
The game itself is simple. There is a board with 3 columns and 3 rows. It's a turn based game and at the start of the game each player chooses a color, red or green which are usually represented by red X's and green Circles. Then every turn a player marks a cell on the board with his color. The goal of the game is to get three succeeding X's or circles horizontally, vertically or diagonally.
Since it's an iOS game, a human player plays versus an AI or an algorithm in this case. Since it's a simple game, the interesting bits for me were the implementation of the underlying architecture and the calculation of the AI's counter move.
We are talking iOS and Swift. So it is Model - View - Controller (MVC).
Let's start with the View. The board implementation is in the class TTTBoard. TTTBoard has 9 subviews of type TTTCell. TTTCell is a small subclass of UIImageView to show the X or the Circle. It's got a state variable of type TTTState for the three states GreenSelected, RedSelected and Undefined. TTTBoard that manages TTTCell only visualizes the model which is basically a list of 9 TTTStates. TTTBoard doesn't know anything about the game or the rules involved. Beyond visualizing the current model state to the user, it reports any user taps to TTTController.
TTTController is in typical MVC manner responsible for mediating between the View and the Model. When a human player makes a move, the controller's delegate is called. The delegate updates the model based on the user's selection and propagates the new model back to the view. This is handled in the controller's play() method. If the game isn't over after the human player made his move, the counter move is calculated.
From an implementation perspective the counter move is basically the same as the human player's move. The only difference is of course its calculation which is based on evaluating following three scenarios:
- if there is a move in the current board configuration that enables the machine player to win, choose that move
- if there is no winning move, evaluate if the human player is one move away from winning the game. If so, determine move that prevents human player to win the game.
- otherwise determine best move to win the game
Once the machine's counter move has been calculated, the controller's play() method is called again to update the model with the new move and update the view.
It was important to me that each class had one responsibility only. TTTCell visualizes one given state, TTTBoard manages various TTTCells, TTTBoardConfig calculates a machine's counter move and TTTController mediates between the former and the latter.
I also wanted TTTBoardConfig to be immutable. Whenever a new move is made, a new TTTBoardConfig will be generated. So once the configuration is calculated, it cannot be changed. This design excludes any potential side effects.
Apart from that I wanted to use as few classes as possible and favor structs instead to further reduce side effects. Since it's only a small project it might not matter that much. But one should adhere to best design practices whenever possible.
The project gave me also a chance to play a little with Generators (or IteratorProtocols as they are called now) and Sequences which I honestly don't get the chance to work with that often. Since the board is addressed in rows and columns, I moved the generation of those into a Generator which is made available via a Sequence to allow for an easy iteration over the various cells in a board.
As I said in the beginning, a new project is a chance to try out new things. In my day to day work I am currently still writing in Swift 2.2. So this project provided me also with a chance to hone my Swift 3 skills. The current implementation is not the result of my first iteration. There were several iterations which mainly consisted of moving code around to get instances with single responsibilities and to make the implementation as dry as possible.
On a different note, I'd like to point out that I implemented the first version a few weeks back. In the meantime, XCode Beta 6 had been released. When I started this blog entry I had to discover that the compilation was broken in Beta 6 and so I had to make some changes to address this. Admittedly Swift 3 is still in Beta but this shows again the fragility of current Swift code and its strong dependency on the underlying compiler version. This won't probably stop with Swift 3.0 but continue on although less turbulent with forthcoming versions of Swift 3.x.