This blog post is the third and final update about my Tic-tac-toe game, written in Swift. The source code is available on GitHub:
The app now has a user interface, which allows you to play against the computer or against another person on the same iPhone.
In my article about the Tic-tac-toe gameplay and data model I presented a diagram of domain components, such as the Game and GameBoard classes. The article about artificial intelligence reviewed a perfect-opponent strategy implementation. Now that the app has a user interface which ties these components together, we can see how they fit into the bigger picture.
The containment relationship between Game and GameBoard (i.e. a Game has a GameBoard) is mirrored by GameViewController and GameBoardView. When GameViewController begins a new game of Tic-tac-toe, it creates a GameBoard used as the data source for both a Game and GameBoardView.
As the code comments above indicate, creation of the layout and renderer objects is delayed until drawRect(_:) is called to ensure that the view’s graphics context and size are valid. Each object has two properties; one with an underscore prefix to store the object and another, without a prefix, that creates the object when necessary. This is similar to having a custom property accessor that uses an instance variable, in Objective-C.
The rendering work is performed by GameBoardRenderer:
All of the rendering routines are defined in a private class extension:
This code might seem odd if you have experience with CoreGraphics, which is an API of C functions and structures. Thanks to Swift’s all-encompassing support for adding methods to types, I was able to create this wrapper API to hide the ugly details of using CGContext.
Rendering a user interface does not, and should not, involve determining the size and location of each thing in the user interface. This separate responsibility is often referred to as layout calculation. I separated these two responsibilities by having the renderer rely on GameBoardLayout for the size and location of every user interface element. The rendering methods seen above use these properties of a layout object:
These properties are all lazy in order to avoid calculating their values more than once. This optimization is appropriate because a layout object is used multiple times throughout a game.
There is a lot more going on in this app, so if you’re interested be sure to clone the repository and dig in!