How To Make a Simple Playing Card Game with Multiplayer and Bluetooth, Part 1
This is a post by iOS Tutorial Team member Matthijs Hollemans, an experienced iOS developer and designer. You can find him on Google+ and Twitter. Card games are quite popular on the App Store – over 2,500 apps and counting – so it’s about time that raywenderlich.com shows you how to make one! In addition, […] By Matthijs Hollemans.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Make a Simple Playing Card Game with Multiplayer and Bluetooth, Part 1
50 mins
Matchmaking
Now that you have the basic Host Game and Join Game screens working, you can add the matchmaking logic. When a player taps the Host Game button, her device should broadcast the availability of the Snap! service. When a player goes into the Join Game screen, his device should start looking for any servers that are making the Snap! service available.
Game Kit’s GKSession class takes care of the hard work for this, but you still need to do some work yourself. Rather than putting all this logic into the view controllers, you will be making two new classes, MatchmakingServer and MatchmakingClient. Source code can get a bit messy if the view controllers assume too many responsibilities, so that’s why you have these two new objects for setting up the connections between the devices.
Before you create these new classes, first add Game Kit to the project. In the Target Summary screen, under Linked Frameworks on Libraries, tap the + button and pick GameKit.framework from the list to add it to the project.
Rather than putting an #import for the Game Kit headers in every source file that requires them, I prefer to import frameworks in the project’s Pre-Compiled Headers file. Open Snap-Prefix.pch (under Supporting Files) and add the following line inside the #ifdef __OBJC__ section:
#import <GameKit/GameKit.h>
Now the Game Kit headers will be immediately available to all source files.
There’s one more thing you need to do, and that’s adding a special symbol in your Info.plist file to indicate that this app needs peer-to-peer functionality, because not all devices (notably first generation iPhone and iPod Touch) support peer-to-peer.
Open Snap-Info.plist and add a new row under the “Required device capabilities” entry. Give it the value “peer-peer”:
The MatchmakingServer
Add a new Objective-C class to the project, subclass of NSObject, and name it MatchmakingServer. I suggest putting it in a new group named “Networking.” Replace the contents of MatchmakingServer.h with the following:
@interface MatchmakingServer : NSObject <GKSessionDelegate>
@property (nonatomic, assign) int maxClients;
@property (nonatomic, strong, readonly) NSArray *connectedClients;
@property (nonatomic, strong, readonly) GKSession *session;
- (void)startAcceptingConnectionsForSessionID:(NSString *)sessionID;
@end
The server has a list of connected clients and a variable that limits how many clients may connect at a time. Snap! has a maximum of four players, so you don’t want to allow more than three clients to connect (the server itself counts as one of the players).
The server also has a GKSession object that will take care of the network communication between the devices, and it conforms to the GKSessionDelegate protocol because that’s how GKSession lets it know about important events.
Currently, MatchmakingServer has only one method, to start broadcasting the service and accepting connections from clients. Soon, you’ll be adding a lot more to it.
Replace MatchmakingServer.m with the following:
#import "MatchmakingServer.h"
@implementation MatchmakingServer
{
NSMutableArray *_connectedClients;
}
@synthesize maxClients = _maxClients;
@synthesize session = _session;
- (void)startAcceptingConnectionsForSessionID:(NSString *)sessionID
{
_connectedClients = [NSMutableArray arrayWithCapacity:self.maxClients];
_session = [[GKSession alloc] initWithSessionID:sessionID displayName:nil sessionMode:GKSessionModeServer];
_session.delegate = self;
_session.available = YES;
}
- (NSArray *)connectedClients
{
return _connectedClients;
}
#pragma mark - GKSessionDelegate
- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state
{
#ifdef DEBUG
NSLog(@"MatchmakingServer: peer %@ changed state %d", peerID, state);
#endif
}
- (void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID
{
#ifdef DEBUG
NSLog(@"MatchmakingServer: connection request from peer %@", peerID);
#endif
}
- (void)session:(GKSession *)session connectionWithPeerFailed:(NSString *)peerID withError:(NSError *)error
{
#ifdef DEBUG
NSLog(@"MatchmakingServer: connection with peer %@ failed %@", peerID, error);
#endif
}
- (void)session:(GKSession *)session didFailWithError:(NSError *)error
{
#ifdef DEBUG
NSLog(@"MatchmakingServer: session failed %@", error);
#endif
}
@end
This is mostly boilerplate stuff. The GKSessionDelegate methods don’t do anything yet, except log the results to the Xcode Debug Pane. The interesting stuff happens in startAcceptingConnectionsForSessionID:
_session = [[GKSession alloc] initWithSessionID:sessionID displayName:nil sessionMode:GKSessionModeServer];
_session.delegate = self;
_session.available = YES;
Here is where you create the GKSession object and tell it to operate in server mode. This means it will only broadcast the availability of the service – named by the sessionID parameter – but it won’t look for any other devices that may be broadcasting the same service. Then you tell the session that MatchmakingServer is its delegate, and set the “available” property to YES, which starts the broadcasting. And that’s all you need to do to get a Game Kit session going.
Now you’ll put the MatchmakingServer into the Host Game screen. Add an import in HostViewController.h:
#import "MatchmakingServer.h"
Add the MatchmakingServer object as an instance variable to the Host View Controller in HostViewController.m:
@implementation HostViewController
{
MatchmakingServer *_matchmakingServer;
}
Also add the following method to HostViewController.m:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (_matchmakingServer == nil)
{
_matchmakingServer = [[MatchmakingServer alloc] init];
_matchmakingServer.maxClients = 3;
[_matchmakingServer startAcceptingConnectionsForSessionID:SESSION_ID];
self.nameTextField.placeholder = _matchmakingServer.session.displayName;
[self.tableView reloadData];
}
}
This creates the MatchmakingServer object as soon as the Host Game screen appears, and tells it to start accepting connections. It also places the name of the device (from session.displayName) as placeholder text into the “Your Name:” text field. If a player doesn’t enter her own name, she’s identified in the game using that placeholder text.
This new code won’t work until you define the SESSION_ID symbol somewhere. It doesn’t really matter what this ID is, as long as both the server and client agree on the same value. Behind the scenes, Game Kit will garble this into a unique Bonjour identifier. Because both MatchmakingServer and MatchmakingClient will need to use this symbol, you’ll simply add it to the prefix file. Open Snap-Prefix.pch and paste the following at the bottom:
// The name of the GameKit session.
#define SESSION_ID @"Snap!"
Run the app and tap the Host Game button. If you’re running on the Simulator, you may see something like this:
The displayName property from GKSession contains a string such as “com.hollance.Snap355561232…”, and so on. If you run the app on your device, it will say “Joe’s iPhone” or whatever you called your device when you first set it up.
You have an up-and-running Game Kit server that’s broadcasting the “Snap!” service, but no clients yet to connect to it. Now you’ll change that by building the MatchmakingClient class.