Creating a Turn-Based Game for iOS: Nougat Tic-Tac-Toe
Over the past couple of weeks, i have been working on a new sample project for Oxygene “Nougat” on the side. After having gotten Browse500 into the App Store in January and showing how to reimplement a simple native UIKit control from Objective-C in Nougat last month, i wanted to create another example that illustrates the benefits of how Nougat interacts directly with the native APIs, and i picked a technology i knew nothing about at all to do so: Game Center.
I’m not a big gamer; i don’t find myself playing games much, and i have certainly never written one before, so this was a great opportunity to explore something new.
For those of you not familiar with it, Game Center is a technology Apple ships in iOS (and now also Mac OS X) that allows for games to interact across the network to create multi-player games, and to keep track of achievements. Game Center is not about graphics, or game UI, or anything of the sort (there’s other frameworks for that), it’s only for bringing players together.
I decided to create a simple turn-by-turn game, that is, a game where multiple players (in this case two) can interact, but only one player is active at a given time. Once a player has made their move, it’s the next player’s turn. To keep things simple, i picked a game that everybody knows and that is dead simple to implement, game playing wise: Tic Tac Toe. This way, having to explain the game itself won’t get in the way of the sample code.
To start with, i had to dig myself into the Game Center documentation and i watched a couple of videos on Turn-by-Turn gaming from WWDC 2011 (sessions i didn’t attend at the time). As it turns out, the API for interacting with Game Center for such a game is surprisingly easy and straightforward, and i was up and coding in no time.
Before we get started looking at the code, let’s look at the prerequisites:
- Here’s the amount of effort anyone from the Oxygene team had spent on thinking about or working on making GameKit (the framework for interacting with Game Center) available to Nougat, before i started this endeavor: 0. Nada. Nil.
- Here’s the amount of legwork i had to do before i could work with the GameKit APIs, just like every other Cocoa developer would: i added a reference to GameKit to my project. Done.
That’s it. No begging someone to translate header files, or waiting for someone to create a crappy abstraction wrapper class. Add a reference, and use the APIs as Apple intended them — that’s the way development should be.
The Code
Let’s dive into the code.
First of all, the full code of Tic-Tac-Toe is included in the latest Nougat beta, and it’s also available in my Github account for anyone to explore.
There’s really three separate parts that are of interest as a sample project in this app:
- The GameKit integration
- The game board, implemented as a custom UIControl
- The computer player algorithm, generously provided by my good friend and long-time Oxygene user, Jeremy Knowles.
Let’s look at GameKit first.
Working with GameKit
There’s four main classes or concepts that you will work with for GameKit.
The first is the local player, a singleton object that can be obtained via a call to GKLocalPlayer.localPlayer
. This objects provides the game with information about the local player — most importantly their ID, and whether the local player is in fact registered and authenticated with Game Center or not. If the player is not set up for Game Center, Tic Tac Toe will simply disable the appropriate UI and just allow local vs. computer game play (although it would be possibly for a game to provide a signup UI, if so desired).
Next is the GKTurnBasedMatchmakerViewController
, which provides access to the standard GameCenter UI. It gives the user access to any and all games they might already have going on, as well as the chance to start a new game. In Tic-Tac-Toe, i make this UI available via a button in the navigation bar, and all that’s needed to show the view is code like the following:
var request :=new GKMatchRequest; request.minPlayers:=2; request.maxPlayers:=2; var mmvc :=new GKTurnBasedMatchmakerViewController withMatchRequest(request); mmvc.turnBasedMatchmakerDelegate:=self; mmvc.showExistingMatches:=true; presentViewController(mmvc) animated(true) completion(nil); |
Going back to the code level, what we are looking out for is a callback on the turnBasedMatchmakerDelegate
that we assigned above. Our root view controller class implements some 4 methods from the IGKTurnBasedMatchmakerViewControllerDelegate
interface, and the match-maker view controller will call us back on these when interesting things happen. This is a very common pattern in Cocoa, and is how the platform handles “events”.
In particular, the callback we care about for starting a new game is:
method turnBasedMatchmakerViewController(aViewController: GKTurnBasedMatchmakerViewController) didFindMatch(aMatch: GKTurnBasedMatch); |
This callback receives the third important class, a GKTurnBasedMatch
. “All” we need to do when this method is called is load the passed match as the active game. In the Tic-Tac-Toe app, we do this by calling our own loadCurrentMatch()
method, which does all the heavy lifting.
Every match contains a binary blob in the matchData
property that describes the current game state. The content of this binary blob is entirely up to our game, but we can use it to store up to 4KB of data, and this data will automatically be passed between all the participants as they take their turns. In turn, our Game will read the data, let the active player make their move, and then store the updated game state, as we will see in a few moments.
Loading a Game
Loading a game is done with two method calls, first, a a small helper function loads the binary data into an NSDictionary
, and then we ask the board to load its game state from that dictionary (the idea being that in a future version, our game might store additional info in the dictionary that the board does not care about — that’s why i decoupled the data the Board
class reads/writes from the actual binary format).
var lDictionary := dictionaryFromData(fCurrentMatch.matchData); fBoard.loadFromNSDictionary(lDictionary, …); |
The second part, in addition to loading the board data, is determining the active player (if any) and enabling the game play. Each GKTurnBasedMatch
has a currentParticipant
property of type GKTurnBasedParticipant
which links to the current player (it also has a participants
array that contains all (two) players). We can compare this participant’s playerID
to that of the local player.
If they match, it’s the local player’s turn, so we display “your turn”, and enable the board. If they don’t match, it’s either the remote players’ turn (if currentParticipant
is assigned), or the game has finished. In either case, we show the proper status message, and leave the board disabled.
Playing a Turn
If it was the local player’s turn and we enabled the board, we’re done for now, and await the player to make a move. This all happens inside the Board
class, which will call us back via the board() playerDidSelectGridIndex()
method, once the player made their move.
What we need to do then is determine the game status (the player might have made a winning move, for example), re-encode the game status, and pass control back to game center.
There are three scenarios we need to cover:
- The player has won by managing to place 3 “X” markers in a row.
- The board has been filled (and the game just tied).
- Any other case — the game is still on.
In the two game-over scenarios, we set the appropriate status (Won/Lost/Tied) on each of the game participants, and then call endMatchInTurnWithMatchData() completionHandler()
to tell Game Center that the match has ended as part of the player taking his turn.
In the other case, we call endTurnWithNextParticipant() matchData() completionHandler()
to let Game Center know that the local player is done, and game play can move to the next player.
In either case, we will pass the updated information on what our game board looks like now, via the matchData
parameter.
It is worth noting that it’s up to our game to tell Game Center “who’s next”. In our case, that decision is trivial (it’s the player that’s not the local player), but Game Center allows for turns to be passed in arbitrary ways, depending on your game’s needs — it doesn’t always have to go around the (virtual) table.
Once we called either of the above fund methods, it’s no longer our turn, and all we can do is wait for the remote player to make a move.
Handing Remote Turns
Of course, eventually the remote player will make their move, and GameKit provides a second delegate interface for this case, IGKTurnBasedEventHandlerDelegate
, which we have hooked up to the GKTurnBasedEventHandler.sharedTurnBasedEventHandler
singleton.
What will happen once the remote player moved is that Game Center will show a notification banner to the user, saying something along the lines of “It’s Your Turn”, and play that annoying fanfare sound. When and only when the user taps that notification, will we receive a call to the
method handleTurnEventForMatch(aMatch: GKTurnBasedMatch) didBecomeActive(didBecomeActive: Boolean); |
So all we really need to do is the same as we did in … didFindMatch()
above: load the passed match and enable the board, if appropriate.
House Keeping
This basically takes care of all the Game Center interaction needed for our simple game. There’s a few more minor items that i won’t go into detail for this post; for example, there’s a callback you will want to handle to quit games if the user selects to delete them in the Game Center UI. There also are APIs for providing a custom UI for the list of games, instead of relying on the standard UI provided by Game Center (all Tic-Tac-Toe does with this is adjust the Game Center button to either read “Games” or “Start”, depending on whether there are any games ongoing or not).
In Tic-Tac-Toe, all the GameKit interaction is located in RootViewController.pas, which is only ~500 lines of code total, so it should give you a good overview and starting point for your own game.
Caveats
There are a couple of caveats you might want to be aware of when working with Game Center games; these had me stumped for a short while, so hopefully pointing them out here will help save you frustration:
- In order for Game Center to work, your app needs to be registered on iTunes Connect with the appropriate app ID. Even for the simulator. This is counter-intuitive if you are used to App Store development, because normally you don’t have to go to iTunes Connect to register your app until you’re (almost) ready to go and submit it. For Game Center, you need to register it first. If you don’t,
GKTurnBasedMatchmakerViewController
will just come up looking all empty and dysfunctional. - As best as i can tell, you must specify “
gamekit
” in theUIRequiredDeviceCapabilities
section of your Info.plist file for things to work properly. - Finally, and most importantly, notifications do not work in the Simulator. If you worked with Push Notifications before, this might not come as much of a surprise, but realize that callbacks such as
handleTurnEventForMatch() …
will never, ever be called on the Simulator — which seriously will impact how you test game interaction. (In essence, i found the only way to work around this was to manually invoke theGKTurnBasedMatchmakerViewController
and re-select the active game to trigger the app to reload the game data and detect the changed game state.)
The Game Board
The second piece of interest in the game is the Board
class, which implements all the UI of the game as a really simply UIControl
descendant.
The implementation is fairly straight-forward; the class maintains a 3×3 array to keep track of which player has claimed a particular spot on the game board, and in which move (by keeping track of the move number, the game can show different versions of their “X” and “O” markers, making the board look more dynamic).
The begin/continue/endTrackingWithTouch() withEvent()
methods are overridden to keep track of — you guessed it — the user’s finger, and they use animations to fade the user’s game piece in and out, and to move it to the next appropriate spot, as the finger moves. (You’ll notice that while the user can move their finger all over the board, the game piece will always “snap” to a fixed position in the closest matching grid spot with a smooth animation.)
All the game pieces (the grid, the “X” and “O” game pieces, etc.) are pre-created images, and the game pieces come in 5 versions (since 5 is the maximum number of moves any one player can make).
The entire Board
class is really straightforward and there isn’t much worth covering here that won’t be clear from looking at the code. Worth pointing out is the makeComputerMove
method, which invokes Jeremy’s mean ComputerPlayer
class, which is used when not playing in Game Center mode.
I think that’s it (and it’s probably enough ;). Make sure to check out the code on GitHub, and let me know what you think!
Oh, and: even though the code is written in Oxygene, as are the code snippets here, i hope the general description of how to work with GameKit will also be useful to “regular” Cocoa developers using Objective-C. Another nice thing about Oxygene working directly against the standard Cocoa APIs without any abstractions!