Beginning Turn-Based Gaming with iOS 5 Part 1

Update 10/24/12: If you’d like a new version of this tutorial fully updated for iOS 6 and Xcode 4.5, check out iOS 5 by Tutorials Second Edition! Note from Ray: This is the sixth iOS 5 tutorial in the iOS 5 Feast! This tutorial is a free preview chapter from our new book iOS 5 […] By Jake Gundersen.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Authenticate the Local Player: Strategy

When your game starts up, the first thing you need to do is authenticate the local player.

You can think of this as “logging the player into Game Center.” If he’s already logged in, it will say “Welcome back!” Otherwise, it will ask for the player’s username and password.

Authenticating the local user is easy – you just call authenticateWithCompletionHandler. You can optionally pass in a block of code that will be called once the user is authenticated.

But there’s a trick. There’s another way for the user to log in (or log out!). He can be using your app, switch to the Game Center app, log or out from there, and switch back to your app.

So your app needs to know whenever the authentication status changes. You can find out about these by registering for an “authentication changed” notification.

So, our strategy to authenticate the player will be as follows:

  • Create a singleton object to keep all the Game Center code in one spot.
  • When the singleton object starts up, it will register for the “authentication changed” notification.
  • The game will call a method on the singleton object to authenticate the user.
  • Whenever the user is authenticated (or logs out), the “authentication changed” callback will be called.
  • The callback will keep track of whether the user is currently authenticated, for use later.

Now that you’re armed with this plan, let’s try it out!

Authenticate the Local User: Implementation

In the SpinningYarn Xcode project, create a new file with the Objective-C class template. Name the class GCTurnBasedMatchHelper and make it a subclass of NSObject.

Then replace GCTurnBasedMatchHelper.h with the following:

#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>

@interface GCTurnBasedMatchHelper : NSObject {
    BOOL gameCenterAvailable;
    BOOL userAuthenticated;
}

@property (assign, readonly) BOOL gameCenterAvailable;

+ (GCTurnBasedMatchHelper *)sharedInstance;
- (void)authenticateLocalUser;

@end

This imports the GameKit header file, and then creates an object with two booleans – one to keep track of if game center is available on this device, and one to keep track of whether the user is currently authenticated.

It also creates a property so the game can tell if game center is available, a static method to retrieve the singleton instance of this class, and another method to authenticate the local user (which will be called when the app starts up).

Next switch to GCTurnBasedMatchHelper.m and add the following right inside the @implementation:

@synthesize gameCenterAvailable;

#pragma mark Initialization

static GCTurnBasedMatchHelper *sharedHelper = nil;
+ (GCTurnBasedMatchHelper *) sharedInstance {
    if (!sharedHelper) {
        sharedHelper = [[GCTurnBasedMatchHelper alloc] init];
    }
    return sharedHelper;
}

This synthesizes the gameCenterAvailable property, then defines the method to create the singleton instance of this class.

Note there are many ways of writing singleton methods, but this is the simplest way when you don’t have to worry about multiple threads trying to initialize the singleton at the same time.

Next add the following method right after the sharedInstance method:

- (BOOL)isGameCenterAvailable {
    // check for presence of GKLocalPlayer API
    Class gcClass = (NSClassFromString(@"GKLocalPlayer"));
    
    // check if the device is running iOS 4.1 or later
    NSString *reqSysVer = @"4.1";
    NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
    BOOL osVersionSupported = ([currSysVer compare:reqSysVer     
      options:NSNumericSearch] != NSOrderedAscending);
    
    return (gcClass && osVersionSupported);
}

This method is straight from Apple’s Game Kit Programming Guide. It’s the way to check if Game Kit is available on the current device.

By making sure Game Kit is available before using it, this app can still run on iOS 4.0 or earlier (just without network capabilities).

Next add the following right after the isGameCenterAvailable method:


- (id)init {
    if ((self = [super init])) {
        gameCenterAvailable = [self isGameCenterAvailable];
        if (gameCenterAvailable) {
            NSNotificationCenter *nc = 
            [NSNotificationCenter defaultCenter];
            [nc addObserver:self 
              selector:@selector(authenticationChanged) 
              name:GKPlayerAuthenticationDidChangeNotificationName 
              object:nil];
        }
    }
    return self;
}

- (void)authenticationChanged {    
    
    if ([GKLocalPlayer localPlayer].isAuthenticated && 
      !userAuthenticated) {
        NSLog(@"Authentication changed: player authenticated.");
        userAuthenticated = TRUE;           
    } else if (![GKLocalPlayer localPlayer].isAuthenticated && 
      userAuthenticated) {
        NSLog(@"Authentication changed: player not authenticated");
        userAuthenticated = FALSE;
    }
    
}

The init method checks to see if Game Center is available, and if so registers for the “authentication changed” notification. It’s important that the app registers for this notification before attempting to authenticate the user, so that it’s called when the authentication completes.

The authenticationChanged callback is very simple at this point – it checks to see whether the change was due to the user being authenticate or deauthenticated, and updates a status flag accordingly.

Note that in practice this might be called several times in a row for authentication or deauthentication, so by making sure the userAuthenticated flag is different than the current status, it only logs if there’s a change since last time.

Finally, add the method to authenticate the local user right after the authenticationChanged method:

#pragma mark User functions

- (void)authenticateLocalUser { 
    
    if (!gameCenterAvailable) return;
    
    NSLog(@"Authenticating local user...");
    if ([GKLocalPlayer localPlayer].authenticated == NO) {     
        [[GKLocalPlayer localPlayer] 
            authenticateWithCompletionHandler:nil];        
    } else {
        NSLog(@"Already authenticated!");
    }
}

This calls the authenticateWithCompletionHandler method mentioned earlier to tell Game Kit to authenticate the user. Note it doesn’t pass in a completion handler. Since you’ve already registered for the “authentication changed” notification it’s not necessary.

OK – GCTurnBasedMatchHelper now contains all of the code necessary to authenticate the user, so you just have to use it! Switch to AppDelegate.m and make the following changes:

// At the top of the file
#import "GCTurnBasedMatchHelper.h"
 
// At the end of applicationDidFinishLaunching, right before 
// the return YES
[[GCTurnBasedMatchHelper sharedInstance] authenticateLocalUser];

This creates the Singleton instance (which registers for the “authentication changed” callback as part of initialization), then calls the authenticateLocalUser method.

Usually you’d have to add the Game Kit framework to your project as well, but I’ve already added this for you in the Build Phases\Link Binary with Libraries section of your target settings.

That’s it! Compile and run your project, and if you’re logged into Game Center you should see something like the following:

Welcome Back from Game Center

Turn-Based Gaming Basics

Now that we’re successfully authenticating with Game Center and have a starter project ready, we’re ready to start talking about the meat of this tutorial: Turn-Based Gaming.

In a turn-based game, you don’t need to play at the same time as our opponents (although you can be). For example, you can take your turn while your friend is asleep, and then they can wake up, take their turn while you’re showering, and so on. A player can be in the middle of playing many of these asynchronous matches at the same time.

Visualize control of the game as a baton in a relay race. Only one player can hold the baton (or take a turn) at a time. When the baton is passed, it needs to contain everything that the player needs to know about that game.

To understand more about how it works, let’s start by reviewing the Turn-Based Gaming classes in more detail.

GKTurnBasedMatch

This class stores information about an individual match, such as:

  • creationDate: The date that the match was first created.
    currentParticipant: The GKTurnBasedParticipant who currently holds the baton (his/her turn). More on this below.
  • matchID: An NSString uniquely identifying the match. This is typically long and not easily readable.
  • message: An NSString to be displayed to help the user identify the match in the GKTurnBasedMatchmakerViewController. You can set this to whatever you want.
  • participants: An NSArray of all GKTurnBasedParticipants who are included in the match (includes those who have quit).
  • status: The current state of the match, as an GKTurnBasedMatchStatus. Includes values like Open, Ended, Matching, etc.

GKTurnBasedParticipant

This class stores information about an individual player, such as:

  • playerID: An NSString unique identifier about the player, never changes. This is not the same as the user’s Game Center nickname, and you should usually not display this because it isn’t easily readable.
  • lastTurnDate: An NSDate of last turn. This is null until the player has taken a turn.
  • matchOutcome: The outcome of the match as a GKTurnBasedMatchOutcome. Includes values such as Won, Lost, Tied, 3rd etc.
    status: The current state of the player, as a GKTurnBasedParticipantStatus. Includes values like Invited, Declined, Active, Done, etc.

GKTurnBasedMatchmakerViewController

This is the standard user interface written by Apple to help players work with turn-based gaming matches. It allows players to:

  • Create matches. You can use this view controller to create matches, either by auto match or by invitation. When you create a new match, you get to play the first turn right away (even if the system hasn’t found an auto match partner yet!) When the system has found someone and they take their turn, you’ll get a notification again. Note that there is currently no programmatic was to create new matches except by using this controller.
  • Switch matches. As discussed earlier, you can have many turn-based games going on at once. You can use this view controller to view different games you’re playing – even if it’s not your turn or the game is over (you can view the current game state in that case).
  • Quit matches. Finally, you can use the view controller to quit from a match you no longer want to play.

GKMatchRequest

You use this to initialize a GKTurnBasedMatchmakerViewController, much the same way you create a match for normal (live) Game Center multiplayer games.

When you create a GKMatchRequest, you specify a minimum and maximum number of players. You can also segment players (by location, skill level, custom groups, etc.) using this object.

GKTurnBasedMatchmakerViewControllerDelegate

When you create a GKTurnBasedMatchmakerViewController, you can specify a delegate that implements this protocol. It provides callback methods for when the view controller loads a new match, cancels, fails due to error, etc.

GKTurnBasedEventHandler

Last but not least, this singleton class has a delegate protocol, GKTurnBasedEventHandlerDelegate that provides notifications when the turn passes from one player to another, when we’re invited by a friend to a match, or when the game ends.

Jake Gundersen

Contributors

Jake Gundersen

Author

Over 300 content creators. Join our team.