How To Make a Simple Playing Card Game with Multiplayer and Bluetooth, Part 2

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. Welcome back to our monster 7-part tutorial series on creating a multiplayer card game over Bluetooth or Wi-Fi using UIKit! If you are new to this series, check out the […] By Matthijs Hollemans.

Leave a rating/review
Save for later
Share

Create a networked multiplayer card game!

Create a networked multiplayer card game!

Create a networked multiplayer card game!

This is a post by iOS Tutorial Team member Matthijs Hollemans, an experienced iOS developer and designer. You can find him on and Twitter.

Welcome back to our monster 7-part tutorial series on creating a multiplayer card game over Bluetooth or Wi-Fi using UIKit!

If you are new to this series, check out the introduction first. There you can see a video of the game, and we’ll invite you to our special Reader’s Challenge!

In the first part of the series, you created the main menu and the basics for the host and join game screens.

You also created a game server that broadcasts the “Snap!” service and a client that detects the server, but the only evidence of this so far is some NSLog-lines in the Xcode debug output pane.

Now in Part 2, you’ll show the available servers and connected clients on the screen properly, and finish up the matchmaking. Ante up!

Getting Started: Showing the Servers to the User

The MatchmakingClient class has an _availableServers variable, an NSMutableArray, that is supposed to hold the list of servers that this client has detected on the network. You’ll add the peer ID of the server to this array whenever GKSession detects a new server.

How do you know when that happens? Well, MatchmakingClient is the delegate of GKSession, and you can use the session:peer:didChangeState: delegate method for this. Replace that method in MatchmakingClient.m with:

- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state
{
	#ifdef DEBUG
	NSLog(@"MatchmakingClient: peer %@ changed state %d", peerID, state);
	#endif
	
	switch (state)
	{
		// The client has discovered a new server.
		case GKPeerStateAvailable:
			if (![_availableServers containsObject:peerID])
			{
				[_availableServers addObject:peerID];
				[self.delegate matchmakingClient:self serverBecameAvailable:peerID];
			}
			break;

		// The client sees that a server goes away.
		case GKPeerStateUnavailable:
			if ([_availableServers containsObject:peerID])
			{
				[_availableServers removeObject:peerID];
				[self.delegate matchmakingClient:self serverBecameUnavailable:peerID];
			}
			break;

		case GKPeerStateConnected:
			break;

		case GKPeerStateDisconnected:
			break;

		case GKPeerStateConnecting:
			break;
	}	
}

The newly discovered server is identified by the peerID parameter. This is a string that contains a number, such as @”663723729.” This number is only important in that it identifies the server.

The third parameter is “state,” and it tells you what is going on with that peer. Currently you deal only with the states GKPeerStateAvailable and GKPeerStateUnavailable. As you can tell from their names, these states indicate that a new server has been discovered, or that a server went away (possibly because that user exited the app or he wandered out of range). Depending on the circumstance, you either add the server’s peer ID to the list of _availableServers, or you remove it from that list.

This code won’t compile as-is, because it also makes calls to self.delegate, which is a property you haven’t defined yet. The MatchmakingClient needs to let the JoinViewController know that a new server has become available (or a previously known server has become unavailable), and you do this through delegate methods. Add the following to the top of MatchmakingClient.h:

@class MatchmakingClient;

@protocol MatchmakingClientDelegate <NSObject>

- (void)matchmakingClient:(MatchmakingClient *)client serverBecameAvailable:(NSString *)peerID;
- (void)matchmakingClient:(MatchmakingClient *)client serverBecameUnavailable:(NSString *)peerID;

@end

Also add a new property to the @interface:

@property (nonatomic, weak) id <MatchmakingClientDelegate> delegate;

And synthesize it in the .m file:

@synthesize delegate = _delegate;

The JoinViewController now needs to become the delegate for MatchmakingClient, so add this protocol to the @interface line in JoinViewController.h:

@interface JoinViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate, MatchmakingClientDelegate>

In JoinViewController.m’s viewDidAppear method, add the following line right after where the MatchmakingClient object is allocated:

		_matchmakingClient.delegate = self;

And finally, implement the new delegate methods:

#pragma mark - MatchmakingClientDelegate

- (void)matchmakingClient:(MatchmakingClient *)client serverBecameAvailable:(NSString *)peerID
{
	[self.tableView reloadData];
}

- (void)matchmakingClient:(MatchmakingClient *)client serverBecameUnavailable:(NSString *)peerID
{
	[self.tableView reloadData];
}

You simply tell the table view to reload itself. That means you should also replace the current table data source methods to make all of this work:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	if (_matchmakingClient != nil)
		return [_matchmakingClient availableServerCount];
	else
		return 0;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	static NSString *CellIdentifier = @"CellIdentifier";

	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
	if (cell == nil)
		cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];

	NSString *peerID = [_matchmakingClient peerIDForAvailableServerAtIndex:indexPath.row];
	cell.textLabel.text = [_matchmakingClient displayNameForPeerID:peerID];

	return cell;
}

This is very basic table view code. You simply ask the MatchmakingClient object for the rows that the table view should display, using a few new methods that you should add to MatchmakingClient. Add their signatures to MatchmakingClient.h:

- (NSUInteger)availableServerCount;
- (NSString *)peerIDForAvailableServerAtIndex:(NSUInteger)index;
- (NSString *)displayNameForPeerID:(NSString *)peerID;

And their implementations to MatchmakingClient.m:

- (NSUInteger)availableServerCount
{
	return [_availableServers count];
}

- (NSString *)peerIDForAvailableServerAtIndex:(NSUInteger)index
{
	return [_availableServers objectAtIndex:index];
}

- (NSString *)displayNameForPeerID:(NSString *)peerID
{
	return [_session displayNameForPeer:peerID];
}

These methods are pretty simple and serve as convenient wrappers around the _availableServers and _session objects. That should do it, so restart the app on the device that serves as your client. You should see something like this:

Join Game screen shows available servers (ugly)

It works! The client shows the name of the server (in the screenshot above, I used my iPod as the server).

Unfortunately, it doesn’t look very pretty. That’s easily solved. Add a new Objective-C class to the project, subclass of UITableViewCell, named PeerCell. (I suggest placing the PeerCell source files in a new group named “Views.”)

You can leave PeerCell.h as-is, but replace the contents of PeerCell.m with the following:

#import "PeerCell.h"
#import "UIFont+SnapAdditions.h"

@implementation PeerCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
	if ((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]))
	{
		self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"CellBackground"]];
		self.selectedBackgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"CellBackgroundSelected"]];

		self.textLabel.font = [UIFont rw_snapFontWithSize:24.0f];
		self.textLabel.textColor = [UIColor colorWithRed:116/255.0f green:192/255.0f blue:97/255.0f alpha:1.0f];
		self.textLabel.highlightedTextColor = self.textLabel.textColor;
	}
	return self;
}

@end

PeerCell is a regular UITableViewCell, but it changes the font and colors of the main textLabel, and it also gives the cell a new background. In JoinViewController’s cellForRowAtIndexPath method, replace the line that allocates the table view cell with:

		cell = [[PeerCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];

Don’t forget to add an #import for PeerCell.h as well. Now the table view cell fits a lot better with the rest of the graphics:

Join Game screen shows available servers (pretty)

Try the following: exit the app on the device that acts as the server. The client should now remove the name of that server from its table view. If you have enough devices, try the app with more than one device acting as a server. The client should find all the servers and show their names in the table view.

Note: It may take a few seconds for the client to recognize that a server has appeared or disappeared, so don’t panic if the table view doesn’t immediately reload!

Note: It may take a few seconds for the client to recognize that a server has appeared or disappeared, so don’t panic if the table view doesn’t immediately reload!

Contributors

Over 300 content creators. Join our team.