Chapter 16

Game Kit Recipes

In this chapter, you are going to use a simple Hangman game and integrate it with Game Center. Game Center allows you to enhance your game by providing easy methods for implementing social features such as high scores, game achievements, and multiplayer gameplay. Adding these features can not only provide more value to your game but also extend the “replay factor” of your game.

You will start by adding high scores and achievements and ultimately finish by making your game multiplayer. You can find the start of this project at https://github.com/shawngrimes/HangmanMP.

Recipe 16–1: Starting with Game Center

Before you can start using Game Center, you will need to use iTunes Connect. If you have already submitted an app or game to the iTunes App Store, you already know what iTunes Connect is. For those who haven't, iTunes Connect is where you manage the apps that you are publishing. You will enter your apps metadata (description, keywords, support URL, etc.) as well as any Apple-provided features that it will use, such as iAds, In App Purchase, or, in this case, Game Center.

In order to use Game Center, you will need to tell iTunes Connect about your game so you can start using the Game Center sandbox. The Game Center sandbox is a development area where developers can test their Game Center integration without impacting production scores or achievements.

iTunes Connect Setup

The first step to enabling Game Center integration is to create a bundle identifier in the iOS Provisioning Portal if you have not done so for your app already. The bundle identifier is used to identify all the related data for an app. For instance, if you have a free and a paid version of your game and you want them to use the same high score table, you would give them the same bundle identifier.

Visit the iOS Provisioning Portal on developer.apple.com, and click App IDs. Create your new bundle identifier. The information shown in Figure 16–1 is the app information you will use for this game.

Image

Figure 16–1. Configuring your App ID

Now go to itunesconnect.apple.com, and click Manage Your Applications. If you have not already submitted your app for review, click the Add New App button, revealing a page resembling Figure 16–2. Enter the information for your new app, and click Continue.

Image

Figure 16–2. Entering your application information

Once you've entered all of the metadata about your app, including the screenshot and app icon, you can enable Game Center. Click the Manage Game Center button, shown in Figure 16–3, and then click Enable.

Image

Figure 16–3. Accessing the Game Center Management page

Once enabled, you can configure leaderboards and achievements (more on those to follow).

Project Setup

Now get your game code set up to use Game Center. Open your project (or the one provided for this chapter) in Xcode.

Once Xcode is open, click your project name in the project navigator and select the desired target. Go to the Build Phases tab, and expand the Link Binaries With Libraries area. You want to add the Game Kit framework, so click the + button highlighted in Figure 16–4.

Image

Figure 16–4. Build Phases tab for adding frameworks

In the resulting pop-up resembling Figure 16–5, search for Game Kit and click Add.

Image

Figure 16–5. Adding the Game Kit framework to a project

If your game can work without Game Center, then you'll want to make this an optional framework rather than a required one, in which case your frameworks list will resemble Figure 16–6.

Image

Figure 16–6. Configuring the Game Kit framework to be optional

If you want to require Game Center, then feel free to leave it as required, but you'll need to do one other step to require Game Center. Open the Info tab of your target, and add “gamekit” to the list of required device capabilities, as in Figure 16–7.

Image

Figure 16–7. Adding “gamekit” as a required device capability

While you are editing the Info tab, make sure your bundle identifier matches what you put in iTunes Connect, as in Figure 16–8.

Image

Figure 16–8. Confirming the correct bundle identifier in your app to be the same as was used in iTunes Connect

You can now include Game Center functionality in your code by adding the statement #import <GameKit/GameKit.h> to your code. Start by making sure Game Center is available on this device.

Checking for Game Center Support

First, create a method for checking for Game Center support. I'll add it to the app delegate so that you can check the state at any point in the game. Start by importing Game Kit into the implementation file (.m), and create a method for checking the availability of Game Center:

#import "HMAppDelegate.h"
#import <GameKit/GameKit.h>

@implementation HMAppDelegate
@synthesize window = _window;

+(BOOL) isGameCenterAvailable
{
    // Check for presence of GKLocalPlayer class.
    BOOL localPlayerClassAvailable = (NSClassFromString(@"GKLocalPlayer")) != nil;

    // The device must be running iOS 4.1 or later.
    NSString *reqSysVer = @"4.1";
    NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
    BOOL osVersionSupported = ([currSysVer compare:reqSysVer
                                           options:NSNumericSearch] !=
NSOrderedAscending);

    return (localPlayerClassAvailable && osVersionSupported);
}

In your interface file (.h), add a declaration for isGameCenterAvailable so other classes can use it and autocomplete will see it:

#import <UIKit/UIKit.h>

@interface HMAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

+(BOOL) isGameCenterAvailable;

@end

Player Authentication

You want to authenticate your player as soon as possible. This is best done as soon as your game is finished launching, so you'll add a method to your app delegate didFinishLaunchingWithOptions: method. First, though, switch to your interface file and add a property for your local player to your app delegate so you can access it whenever needed.

Since you imported GameKit.h into the implementation file, you'll need to tell your interface file that a class of GKLocalPlayer exists. You can do this with the following line under the import statements in the interface file:

@class GKLocalPlayer;

Then add a property for GKLocalPlayer:

@property(strong, nonatomic) GKLocalPlayer *localPlayer;

The final app delegate interface file looks like this:

#import <UIKit/UIKit.h>

@class GKLocalPlayer;

@interface HMAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property(strong, nonatomic) GKLocalPlayer *localPlayer;

+(BOOL) isGameCenterAvailable;

@end

Switch over to the implementation file and synthesize your new property:

@synthesize localPlayer;

Change your didFinishLaunchingWithOptions: to look like the following. This will authenticate the player and set the localPlayer property to the localPlayer:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    if([HMAppDelegate isGameCenterAvailable]){
        //If GameCenter is available, let's authenticate the user
        GKLocalPlayer *_localPlayer=[GKLocalPlayer localPlayer];
        [_localPlayer authenticateWithCompletionHandler:^(NSError *error) {
            if(localPlayer.isAuthenticated){
                self.localPlayer=localPlayer;
            }
        }];
    }
    return YES;
}

Some notes on this: you should always check the property isAuthenticated rather than checking to see if there is an error. If there is an error, Game Kit may be able to authenticate the user with cached data and authenticate anyway. You shouldn't display the errors to the user—Game Center will do that for you. The errors are mostly for debugging.

That's pretty much all there is to authenticating a user. When you run your app, it will prompt you to log in or create a new account, as in Figure 16–9. Authenticated users can be passed between apps, so if you or a user have authenticated to Game Center in another app, this can be passed to your app without prompting you to log in again.

Image

Figure 16–9. Your application prompting for a Game Center account

Recipe 16–2: Leaderboards

You are now able to authenticate your user. Let's start storing some high scores. Leaderboards, a.k.a. high scores, are a great way to increase replay value of your game and encourage competition among friends. You first need to create a leaderboard in iTunes Connect, so head over to itunesconnect.apple.com.

Setting Up iTunes Connect

Click Manage Your Applications, and then click your app that has Game Center enabled. On the App Information page, click Manage Game Center, highlighted in Figure 16–10.

Image

Figure 16–10. Accessing the Manage Game Center screen

Click Setup under the Leaderboard heading, shown in Figure 16–11.

Image

Figure 16–11. The Game Center screen, from which you can set up a leaderboard

Once a leaderboard has gone live for an app, it cannot be deleted, so create them with some thought. You can have up to 25 leaderboards per app. This allows you to create multiple leaderboards for different difficulties or even one for each level of your game, whatever makes the most sense. In your simple app, you are going to create only one leaderboard.

Click the Add Leaderboard button, marked in Figure 16–12, to begin.

Image

Figure 16–12. Your app's list of leaderboards to be added to

Click the Choose button under Single Leaderboard, marked in Figure 16–13, to continue with a stand-alone leaderboard.

Image

Figure 16–13. Choosing leaderboard type

Fill in a name for the leaderboard and an identifier. The name is an internal name for tracking purposes and will not be displayed to the player. (The display name is configured in the next step when adding a language.) Now select the score format type; in this case, I'm going to use a simple integer, but you can also use time-based, floats, and currency. Select the sort order for your leaderboard. If you want high scores at the top (typical), then sort “High to Low”; if you want your low scores at the top (for instance in a golf game), then sort “Low to High”.

You will need to set up at least one language for the leaderboard. To do that, click the Add Language button, shown in Figure 16–14.

Image

Figure 16–14. Configuring a leaderboard

Now configure the localized language for that score as shown in Figure 16–15. Select the language, and then enter a display name for the leaderboard; this is the name that will be visible to the player in the game. You can set the formatting of the score and what unit to call them (singular and plural); in this case, they are “point” and “points.”

Image

Figure 16–15. Language display settings for a leaderboard

Once complete, click Save and then click Save on the leaderboards page as well.

Now, let's dive into some code.

Setting Up Your Code

Open the HangmanMP project, and go to the GameScene.m file. Add the import statement at the top of the file: #import <GameKit/GameKit.h>.

Let's add a method to report the score under the @synthesize and instance variables:

- (void) reportScore: (int64_t) score forCategory: (NSString*) category
{
    GKScore *scoreReporter = [[GKScore alloc] initWithCategory:category];
    scoreReporter.value = score;
    [scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {

        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
        NSString *scoreFilePath = [NSString stringWithFormat:@"%@/scores.plist",[paths
objectAtIndex:0]];
        NSMutableDictionary *scoreDictionary=[NSMutableDictionary
dictionaryWithContentsOfFile:scoreFilePath];

        if (error != nil)
        {
            //There was an error so we need to save the score locally and resubmit later
            NSLog(@"Saving score for later");
            [scoreDictionary setValue:scoreReporter forKey:[NSDate date]];
            [scoreDictionary writeToFile:scoreFilePath atomically:YES];
        }
    }];
}

This method will try to report the score, but if it fails, it will save the score to a dictionary and write that dictionary to a file so you can load the scores later. You can call this method anywhere that you want to report the score to Game Center with a call similar to this:

[self reportScore:playerScore forCategory:@"default_high_scores"];

You should also try to report any saved scores when you start the app, so switch over to your app delegate implementation file (.m).

Add the following to the end of didFinishLaunchingWithOptions, before the line that reads “return YES;”.

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
    NSString *scoreFilePath = [NSString stringWithFormat:@"%@/scores.plist",[paths
objectAtIndex:0]];
    NSMutableDictionary *scoreDictionary=[NSMutableDictionary
dictionaryWithContentsOfFile:scoreFilePath];

    for (NSDate *dateID in [scoreDictionary allKeys]) {
        NSLog(@"Reporting old score: %@", dateID);
        GKScore *scoreToReport=(GKScore *)[scoreDictionary objectForKey:dateID];
        [scoreToReport reportScoreWithCompletionHandler:^(NSError *error) {
            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
            NSString *scoreFilePath = [NSString
stringWithFormat:@"%@/scores.plist",[paths objectAtIndex:0]];
            NSMutableDictionary *scoreDictionary=[NSMutableDictionary
dictionaryWithContentsOfFile:scoreFilePath];

            if (error != nil)
            {
                //There was an error so we need to save the score locally and resubmit
later
                [scoreDictionary setValue:scoreToReport forKey:scoreToReport.playerID];
                [scoreDictionary writeToFile:scoreFilePath atomically:YES];
            }
        }];

    }

This will take a look at the old scores written to file and try to send them to Game Center.

Showing High Scores

High scores are no fun unless people see them. Your users can see the High Scores for your game from within the Game Center app, but you can also give them a direct link to the high scores very easily. Switch over to MainMenuScene.h.

You'll want to import GameKit.h and also set MainMenuScene as a GKLeaderboardViewControllerDelegate.

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

@interface MainMenuScene : UIViewController <GKLeaderboardViewControllerDelegate>

@end

Now go to the implementation file (.m), and add two new methods to the top. The first method will dismiss the GKLeaderboardViewController when you click Done:

- (void)leaderboardViewControllerDidFinish:(GKLeaderboardViewController*)viewController
{
    [self dismissModalViewControllerAnimated:YES];
}

The other method you need to add will show the actual GKLeaderboardViewController view:

- (void) showLeaderboard
{
    GKLeaderboardViewController *leaderboardController = [[GKLeaderboardViewController
alloc] init];
    if (leaderboardController != nil)
    {
        leaderboardController.leaderboardDelegate = self;
        [self presentModalViewController: leaderboardController animated: YES];
    }
}

Now you can create a UIButton on the main menu scene and connect it to the showLeaderboard method to display the leaderboards for your game. You can create the button in the -viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];
    UIButton *buttonShowHighScores=[UIButton buttonWithType:UIButtonTypeRoundedRect];
    [buttonShowHighScores addTarget:self action:@selector(showLeaderboard)
forControlEvents:UIControlEventTouchUpInside];
    buttonShowHighScores.frame=CGRectMake(104, 302, 112, 44);
    [buttonShowHighScores setTitle:@"High Scores" forState:UIControlStateNormal];

    [self.view addSubview:buttonShowHighScores];
}

Recipe 16–3: Achievements

Achievements in games are similar to badges and other unlockables in other apps and games. You provide your players with a notification when they reach certain milestones. In the HangmanMP game, a good achievement might be if they get all the letters right in a word without any mistakes. Let's take a look at how you would implement that.

Setting Up iTunes Connect

As with other things you've done with Game Center, it all starts in iTunes Connect, so head over there: itunesconnect.apple.com. Click Manage Your Applications, and then click the app you have set up for Game Center. Now click Manage Game Center.

In the Game Center management page, click Set Up under Achievements, shown in Figure 16–16.

Image

Figure 16–16. Accessing the Achievements section from the Game Center screen

Each game can have up to 1,000 achievement points. These points can be assigned to different achievements as you see fit, but each achievement can have a max of only 100 achievement points.

For now you are going to create a 50-point achievement, so go ahead and click Add New Achievement, as in Figure 16–17.

Image

Figure 16–17. Adding a new achievement for your app

This achievement will be for getting all the letters and no mistakes in a small word (six letters or less). Your configuration is shown in Figure 16–18.

Image

Figure 16–18. Configuring an achievement's details

You can't save an achievement until you add at least one language to it, so click Add Language now. The resulting view will resemble Figure 16–19. Here, you can set the achievement title as well as the “pre-earned” description. The pre-earned description should detail how the achievement is earned. There is also an earned description, which is the description shown after the achievement is earned. Finally, you will need an image to represent the achievement. This should be a 512×512 72 ppi .png file.

Image

Figure 16–19. Configuring achievement details in a specific language

Click Save and then Save again on the Achievement info page. And let's write some code…

Setting Up Your Code

This is going to be very similar to setting up leaderboards. Open up GameScene.m, and add the following method:

- (void) reportAchievementIdentifier:(NSString*)identifier percentComplete:(float)
percent
{
    GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:identifier];
    if (achievement)
    {
        achievement.percentComplete = percent;
        [achievement reportAchievementWithCompletionHandler:^(NSError *error)
        {
            if (error != nil)
            {
                //There was an error so we need to save the achievement locally and
resubmit later
                NSLog(@"Saving achievement for later");
                NSArray *paths =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
                NSString *achievementFilePath = [NSString
stringWithFormat:@"%@/achievements.plist",[paths objectAtIndex:0]];
                NSMutableDictionary *achievementDictionary=[NSMutableDictionary
dictionaryWithContentsOfFile:achievementFilePath];

                [achievementDictionary setValue:achievement
forKey:achievement.identifier];
                [achievementDictionary writeToFile:achievementFilePath atomically:YES];
            }
        }];
    }
}

Now you just need to call this method with your achievement and the percentage complete. Since in this case, you either have the achievement or you don't, the percentage complete will be 100 or 0, but you can use this snippet to report on the completion of any achievement. This bit of code goes just after you report the score in the -processGuess method.

if(unfoundLetters.location==NSNotFound){
            [self reportScore:playerScore forCategory:@"default_high_scores"];
            if(self.badGuessCount==0 && self.stringHiddenWord.length<=6){
                NSLog(@"Reporting achievement");
                [self reportAchievementIdentifier:@"no_mistakes_small_word"
percentComplete:100];
            }
….

Similar to how you reported saved scores in the app delegate, you should do the same with achievements. So go to HMAppDelegate.m, modify your friend the didFinishLaunchingWithOptions method, and add the following code before your return statement.

    //Report saved achievements
    NSString *achievementFilePath = [NSString
stringWithFormat:@"%@/achievements.plist",[paths objectAtIndex:0]];
    NSMutableDictionary *achievementDictionary=[NSMutableDictionary
dictionaryWithContentsOfFile:achievementFilePath];
    for (id achievement in [achievementDictionary allKeys]){
        GKAchievement *achievementToReport=(GKAchievement *)[achievementDictionary
objectForKey:achievement];
        [achievementToReport reportAchievementWithCompletionHandler:^(NSError *error)
         {
             if (error != nil)
             {
                 //There was an error so we need to save the achievement locally and
resubmit later
                 NSLog(@"Saving achievement for later");
                 NSArray *paths =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
                 NSString *achievementFilePath = [NSString
stringWithFormat:@"%@/achievements.plist",[paths objectAtIndex:0]];
                 NSMutableDictionary *achievementDictionary=[NSMutableDictionary
dictionaryWithContentsOfFile:achievementFilePath];

                 [achievementDictionary setValue:achievementToReport
forKey:achievement];
                 [achievementDictionary writeToFile:achievementFilePath atomically:YES];
             }else{
                 NSLog(@"Achievement reported");
             }
         }];
    }

Showing Achievements

This section is going to seem like déjà vu if you followed the “Leaderboard” section. You'll want to show players the achievements they've earned in your game, and the easiest way to do that is to tie a button in your app to the GKAchievementsViewController. Head over to MainMenuScene.m, and add the following method:

-(void)achievementViewControllerDidFinish:(GKAchievementViewController *)viewController{
    [self dismissModalViewControllerAnimated:YES];
}

This will dismiss your GKAchievementViewController. To display it, you'll add the following function into MainMenuScene.m:

- (void) showAchievements
{
    GKAchievementViewController *achievements = [[GKAchievementViewController alloc]
init];
    if (achievements != nil)
    {
        achievements.achievementDelegate = self;
        [self presentModalViewController:achievements animated: YES];
    }
}

Connect a UIButton action call to that method, and then don't forget to add GKAchievementViewControllerDelegate to the MainMenuScene.h file:

//
//  MainMenuScene.h
//  HangmanMP

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

@interface MainMenuScene : UIViewController <GKLeaderboardViewControllerDelegate
GKAchievementViewControllerDelegate>

- (IBAction)actionShowHighScores:(id)sender;

@end

Now test the application by launching it in the simulator. You will be able to view your newly created achievement, as in Figure 16–20.

Image

Figure 16–20. Your application displaying your earned achievement

Recipe 16–4: Multiplayer

Adding a multiplayer option to your game greatly increases the replay factor of your game because now the player can go outside the bounds of the computer and play against real-life players on the Internet.

NOTE: You can't test multiplayer in the simulator. Instead you'll need two devices to test with, so borrow a friend's or pick up an old iPod touch somewhere.

Setting Up Your Code

For this recipe, you can skip right over the iTunes Connect portion because if you've enabled Game Center, multiplayer is automatically made available as long as your game supports it. So let's jump right into some code.

The first thing you are going to do is update your MainMenuScene.h file. You'll need to set it as a GKMatchmakerViewControllerDelegate and also add a new action to host a multiplayer game. It should look like this:

//  MainMenuScene.h
//  HangmanMP
//
#import <UIKit/UIKit.h>
#import <GameKit/GameKit.h>

@interface MainMenuScene : UIViewController <GKLeaderboardViewControllerDelegate,
GKAchievementViewControllerDelegate,GKMatchmakerViewControllerDelegate>

- (IBAction)actionShowHighScores:(id)sender;
- (IBAction)actionShowAchievements:(id)sender;
- (IBAction)actionHostMatch:(id)sender;

@end

Now switch over to the MainMenuScene.m file. You'll create two standard methods required by the GKMatchmakerViewControllerDelegate protocol, matchmakerViewControllerWasCancelled and matchmakerViewController:didFailWithError:

- (void)matchmakerViewControllerWasCancelled:(GKMatchmakerViewController*)viewController
{
    [self dismissModalViewControllerAnimated:YES];
}

- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController
                didFailWithError:(NSError *)error
{
    [self dismissModalViewControllerAnimated:YES];
    [[[UIAlertView alloc] initWithTitle:@"Error" message:error.description delegate:nil
cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
}

One final delegate protocol method you'll need to add is matchmakerViewController:didFindMatch:, and this is how you start to load a multiplayer game.

- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController
                    didFindMatch:(GKMatch *)match
{
    [self dismissModalViewControllerAnimated:YES];
//Set up our game scene from the story board
    GameScene *gameSceneVC=[self.storyboard
instantiateViewControllerWithIdentifier:@"GameScene"];
//Set the match property on GameScene to the matchmaker match
    gameSceneVC.match=match;
//Set the delegate of the match to GameScene, more on this to come…
    match.delegate = gameSceneVC;
[self.navigationController pushViewController:gameSceneVC animated:YES];
}

Now you will create the action to actually start looking for matches with the matchmakerViewController:

- (IBAction)actionHostMatch:(id)sender {
    if([GKLocalPlayer localPlayer].isAuthenticated){
        GKMatchRequest *request = [[GKMatchRequest alloc] init] ;
        request.minPlayers = 2;
        request.maxPlayers = 2;
        GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc]
initWithMatchRequest:request];
        mmvc.matchmakerDelegate = self;
        [self presentModalViewController:mmvc animated:YES];
    }
}

This will get us to start looking for matches, but if you want to be able to respond to invitations, you'll need to add an invitation handler. This should be added as soon as the player is authenticated so that it can handle invitation requests as soon as possible. This block of code will handle invites you send out and invites that you receive. Add the following code in the -viewDidLoad method of MainMenuScene.m:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite,
                                                      NSArray *playersToInvite) {
        // Insert application-specific code here to clean up any games in progress.
        if (acceptedInvite)
        {
            GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc]
initWithInvite:acceptedInvite];
            mmvc.matchmakerDelegate = self;
            [self presentModalViewController:mmvc animated:YES];
        }
        else if (playersToInvite)
        {
            GKMatchRequest *request = [[GKMatchRequest alloc] init];
            request.minPlayers = 2;
            request.maxPlayers = 2;
            request.playersToInvite = playersToInvite;
            GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc]
initWithMatchRequest:request];
            mmvc.matchmakerDelegate = self;
            [self presentModalViewController:mmvc animated:YES];
        }
    };
}

Now you'll want to implement multiplayer game controls into your GameScene. Open GameScene.h, and set it as a GKMatchDelegate. You can also set it as a UIAlertViewDelegate (for later). Finally, you will add two new properties, GKMatch *match and BOOL matchStarted. GameScene.h should now look like this:

//  GameScene.h
//  HangmanMP

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

@interface GameScene : UIViewController <UITextFieldDelegate, GKMatchDelegate, UIAlertViewDelegate>

@property (weak, nonatomic) IBOutlet UITextField *textFieldGuess;
@property (weak, nonatomic) IBOutlet UITextView *textViewGuesses;
@property (weak, nonatomic) IBOutlet UILabel *labelGuessedLetters;
@property (weak, nonatomic) IBOutlet UIImageView *imageViewHanger;
@property (weak, nonatomic) IBOutlet UILabel *labelLettersInWord;
@property (weak, nonatomic) IBOutlet UIScrollView *scrollViewContent;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;


@property(strong, nonatomic) NSMutableArray *arrayGuesses;
@property(strong, nonatomic) NSString *stringDifficulty;
@property(strong, nonatomic) NSString *stringHiddenWord;
@property(nonatomic) int badGuessCount;
@property(strong, nonatomic) GKMatch *match;
@property(nonatomic) BOOL matchStarted;

-(NSString *) getMagicWord;

@end

Switch to GameScene.m, and you have a few more changes to make to ensure that your game can handle multiplayer sessions. First, you need to synthesize the two new properties you created, so at the bottom of the @synthesize list, add this:

@synthesize match;
@synthesize matchStarted;

Now, you'll create a method in GameScene.m for sending data to all users in your game. This is just a generic function that will encode an NSDictionary and send it as an NSData object.

- (void) sendData:(NSDictionary *)dictionaryToSend
{
    NSError *error;

    NSMutableData *dataToSend = [[NSMutableData alloc] init];
        NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]
initForWritingWithMutableData:dataToSend];
        [archiver encodeObject:dictionaryToSend forKey:@"DataDictionary"];
        [archiver finishEncoding];

    [match sendDataToAllPlayers:dataToSend withDataMode:GKMatchSendDataReliable
error:&error];
    if (error != nil)
    {
        NSLog(@"Error sending data: %@", error.description);
    }
}

Note this line:

[match sendDataToAllPlayers:dataToSend withDataMode:GKMatchSendDataReliable
error:&error];

You could also send the data withDataMode:GKMatchSendDataUnreliable if you didn't need to know every update of the player (for instance, if the player were moving on the screen, you need to know only the current position, not where the player has been). In this case, you do need to know that players receive your data (the word they are trying to guess), so you will send with GKMatchSendDataReliable.

Before I show how to receive data, let's take a look at setting up the match. Add the following delegate method to notify your game whenever a player's state changes. Once all the players are in the game, the expectedPlayerCount will equal 0 and you are ready to start your game. You need to know who is going to go first, so have each player generate a random number with arc4random() between 0 and 999. Then send this random number using your previous sendData method.

- (void)match:(GKMatch *)match player:(NSString *)playerID
didChangeState:(GKPlayerConnectionState)state
{
    switch (state)
    {
        case GKPlayerStateConnected:
            // handle a new player connection.
            break;
        case GKPlayerStateDisconnected:
            [[[UIAlertView alloc] initWithTitle:@"Warning" message@"Opponent left the
game" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
            break;
    }
    if (!self.matchStarted && match.expectedPlayerCount == 0)
    {
        self.matchStarted = YES;
        // handle initial match negotiation.
        randomPlayerStartKey=arc4random() % 1000;
        NSDictionary *dictionaryRandomStart=[NSDictionary
                                             dictionaryWithObject:[NSNumber
numberWithInt:randomPlayerStartKey]
                                             forKey:@"randomStartKey"];
        [self sendData:dictionaryRandomStart];

    }
}

So now you have your first piece of data to receive, the random number that was generated. You'll start by decoding the NSData received back into a dictionary. Then you'll check for the key randomStartKey; if it exists, you'll check if it is larger than the random number you generated. If your number is greater, then you will prompt for a word that you will send to the opponent to guess. There are a few other handlers added to complete the implementation: WordToGuess, gameWon, and gameLost.

- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString
*)playerID
{
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]
initForReadingWithData:data];
    NSDictionary *myDictionary = [unarchiver decodeObjectForKey:@"DataDictionary"];
    [unarchiver finishDecoding];
    NSLog(@"Received Dict: %@", myDictionary);

    if([myDictionary valueForKey:@"randomStartKey"]!=nil){
        NSNumber *otherRandomStartKey=[myDictionary valueForKey:@"randomStartKey"];
        if([otherRandomStartKey integerValue]>randomPlayerStartKey){
            //If their random key is larger than mine, then they will send the word

        }else{
            //My random key is larger so I will send the word
            UIAlertView *wordPrompt=[[UIAlertView alloc] initWithTitle:@"Enter Word:"
message:@"Type the word they must decode" delegate:self cancelButtonTitle:@"Cancel"
otherButtonTitles:@"OK", nil];
            wordPrompt.alertViewStyle=UIAlertViewStylePlainTextInput;
            [wordPrompt show];
        }
    }else if([myDictionary valueForKey:@"WordToGuess"]!=nil){
//if they sent the word to guess
        [self setWord:[myDictionary valueForKey:@"WordToGuess"]];
        [self.activityIndicator stopAnimating];
    }else if([myDictionary valueForKey:@"gameWon"]!=nil){
//if they won the game
        int guessCount=[[myDictionary valueForKey:@"gameWon"] integerValue];
        [[[UIAlertView alloc] initWithTitle:@"Your Opponent Won!" message:[NSString
stringWithFormat:@"Better luck next time.  %i bad guesses", guessCount] delegate:nil
cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
    }else if([myDictionary valueForKey:@"gameLost"]!=nil){
        [[[UIAlertView alloc] initWithTitle:@"You Win!" message:@"They didn't guess your
word" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
    }
}

Since you prompt for the word to send, you should check your dictionary and make sure it is a legitimate word. Since you set your class as a UIAlertViewDelegate in the .h file and you set the delegate of the alert view prompt to self, you can add the following method to process the submitted word before you send it to your opponent:

- (void)alertView:(UIAlertView *)alertView
didDismissWithButtonIndex:(NSInteger)buttonIndex{
    if(buttonIndex==1){
        NSLog(@"Alert View Text: %@", [alertView textFieldAtIndex:0].text);
        NSString *potentialWord=[alertView textFieldAtIndex:0].text;

        NSString *path = [[NSBundle mainBundle] pathForResource:@"wordlist"
                                                         ofType:@"txt"];
        NSString *content = [NSString stringWithContentsOfFile:path
                                                      encoding:NSUTF8StringEncoding
                                                         error:NULL];

        NSArray *lines = [content componentsSeparatedByString:@" "];

        BOOL wordMatch=NO;
        while(wordMatch==NO){
            for (NSString *word in lines) {
                if([word isEqualToString:potentialWord]){
                    NSDictionary *myDictionary = [NSDictionary
bdictionaryWithObject:[alertView textFieldAtIndex:0].text forKey:@"WordToGuess"];
                    [self sendData:myDictionary];
                    wordMatch=YES;
                    break;
                }
            }
            if(wordMatch==NO){
                UIAlertView *wordPrompt=[[UIAlertView alloc] initWithTitle:@"Word Not
Found" message:@"Your word was not found in the dictionary, please enter a new word for
your opponent to decode:" delegate:self cancelButtonTitle:@"Cancel"
otherButtonTitles:@"OK", nil];
                wordPrompt.alertViewStyle=UIAlertViewStylePlainTextInput;
                [wordPrompt show];
            }
        }
    }
}

Now that you have data going back and forth, you can send the game notifications (e.g., winning and losing) using the sendData method, but you should first see if you are in a match. The following sample code shows how to check if you are in a match:

if(self.match){

                NSDictionary *dictionaryGameWon=[NSDictionary
                                                     dictionaryWithObject:[NSNumber
numberWithInt:self.badGuessCount]
                                                     forKey:@"gameWon"];
                [self sendData:dictionaryGameWon];

            }

The foregoing code would be placed toward the end of the -processGuess method in GameScene.m.

-(void) processGuess:(NSString *)guessedLetter{

        if(unfoundLetters.location==NSNotFound){
            [self reportScore:playerScore forCategory:@"default_high_scores"];
            if(self.badGuessCount==0 && self.stringHiddenWord.length<=6){
                NSLog(@"Reporting achievement");
                [self reportAchievementIdentifier:@"no_mistakes_small_word"
percentComplete:100];
            }

            [[[UIAlertView alloc] initWithTitle:@"WINNER!" message:[NSString
stringWithFormat:@"You Win! Score:%i", playerScore] delegate:nil
cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
            if(self.match){

                NSDictionary *dictionaryGameWon=[NSDictionary
                                                     dictionaryWithObject:[NSNumber
numberWithInt:self.badGuessCount]
                                                     forKey:@"gameWon"];
                [self sendData:dictionaryGameWon];

            }
        }
    }
}

This is the start of a multiplayer game and shows the sending of player information, moves, and other data back and forth. If you have trouble with any of the foregoing code, you can find the completed project at https://github.com/shawngrimes/HangmanMP-Complete.

Summary

In this chapter, you've learned how to extend your game with Game Center and Game Kit. You should be able to include high scores in your games to encourage competition among players and establish bragging rights. You should also be able to implement achievements that can give your players a feeling of accomplishment during long levels or even easily provide mini-games within a game. Finally, you implemented basic multiplayer functionality into the game to encourage even more social game play against live opponents.

Developing iOS applications is a multifarious process: a combination of visual design and programmatic functionality that requires a versatile skillset, as well as significant dedication. Thankfully, Apple provides an excellent development toolset and programming language to work with, both of which are constantly updated and improved upon. With such a flexible language, tasks ranging from organizing massive data stores, to complex web requests, to image filtering can be simplified, designed, and implemented for some of the most widely used and powerful devices of our generation. Whether you use this book as a simple reference or a full guide, we hope that you are able to use these recipes to build stronger applications to help improve and contribute to the world of iOS technology.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset