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

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
You are currently viewing page 3 of 6 of this article. Click here to view the first page.

The Data Model

This is a good time to talk a bit about the data model for this game. Because you’re using UIKit (as opposed to Cocos2D or OpenGL), it makes sense to structure the game using the Model-View-Controller (MVC) pattern.

A common way to make Cocos2D games is to subclass CCSprite, and put your game object logic into that class. Here you’ll do things a bit differently: you’ll make a strict separation between model, view, and view controller classes.

Note: It may not make sense to use MVC for all games, but it does for card and board games. You can capture the game rules in the model classes, separate from any presentation logic. This has the advantage of allowing you to easily unit-test these gameplay rules to ensure they’re always correct, although you’ll skip that in this tutorial.

Note: It may not make sense to use MVC for all games, but it does for card and board games. You can capture the game rules in the model classes, separate from any presentation logic. This has the advantage of allowing you to easily unit-test these gameplay rules to ensure they’re always correct, although you’ll skip that in this tutorial.

The Game object is part of the data model. It handles the game play rules, as well as the networking traffic between the clients and the server (it is both the delegate and data-receive-handler for GKSession). But Game is not the only data model object; there are several others:

The model, view, and controller objects

You’ve seen the Game and GameViewController classes, but the others are new and you’ll be adding them to the project in the course of the tutorial. The players participating in the game are represented by Player objects. Each player has two Stacks of Cards, which are drawn from a Deck. These are all model objects.

Cards are drawn on the screen by CardView objects. All the other views are regular UILabels, UIButtons and UIImageViews. For network communication, Game uses GKSession to send and receive Packet objects, which represent a message that is sent over the network between the different devices.

Begin by creating the Player object. Add a new Objective-C class to the project, subclass of NSObject, named Player. Since this is a data model class, add it to the Data Model group. Replace the contents of Player.h with:

typedef enum
{
	PlayerPositionBottom,  // the user
	PlayerPositionLeft,
	PlayerPositionTop,
	PlayerPositionRight
}
PlayerPosition;

@interface Player : NSObject

@property (nonatomic, assign) PlayerPosition position;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *peerID;

@end

And in Player.m:

#import "Player.h"

@implementation Player

@synthesize position = _position;
@synthesize name = _name;
@synthesize peerID = _peerID;

- (void)dealloc
{
	#ifdef DEBUG
	NSLog(@"dealloc %@", self);
	#endif
}

- (NSString *)description
{
	return [NSString stringWithFormat:@"%@ peerID = %@, name = %@, position = %d", [super description], self.peerID, self.name, self.position];
}

@end

You’re keeping it simple right now. The three properties of Player represent the different ways that each player can be identified:

  1. By their name. This is what you show to the user, but it’s not guaranteed to be unique. The name is what the player typed in on the Host Game or Join Game screens (if they didn’t type in anything, you’ll use the device name here).
  2. By the peer ID. This is what GKSession uses internally, and it’s how you will identify the players when you need to send them messages over the network.
  3. By their “position” on the screen. This one is interesting. Each player will see himself sitting at the bottom of the screen, so player positions are relative. As you can see from the typedef, you start at the bottom position and then go clockwise (left, up, right). You’ll use the position when you need to do things in a guaranteed order, such as dealing the cards.

This is how different players see themselves and the other players sitting around the table:

How players see themselves and the other players

Signing In

The Game object has now entered its initial state, GameStateWaitingForSignIn, on both the clients and the server. In the “waiting for sign-in” state, the server will send a message to all clients asking them to respond with their local player name.

So far the server only knows which clients are connected and what their peer IDs and device names are, but it does not know anything about the name that the user typed into the “Your Name” field. Once the server knows everyone’s name, it can tell all the clients about the other players.

Add the import for Player to Game.h:

#import "Player.h"

Add a new instance variable to Game, in Game.m:

@implementation Game
{
	. . .
	NSMutableDictionary *_players;
}

You’ll put the Player objects into a dictionary. To make it easy to look up players by their peer IDs, you’ll make the peer ID the key. The dictionary needs to be allocated right away, so add an init method to the Game class:

- (id)init
{
	if ((self = [super init]))
	{
		_players = [NSMutableDictionary dictionaryWithCapacity:4];
	}
	return self;
}

The method that starts the game on the server is startServerGameWithSession:playerName:clients:, and you already do some stuff in there to set up the game. Add the following code to the bottom of that method:

- (void)startServerGameWithSession:(GKSession *)session playerName:(NSString *)name clients:(NSArray *)clients
{
	. . .

	// Create the Player object for the server.
	Player *player = [[Player alloc] init];
	player.name = name;
	player.peerID = _session.peerID;
	player.position = PlayerPositionBottom;
	[_players setObject:player forKey:player.peerID];

	// Add a Player object for each client.
	int index = 0;
	for (NSString *peerID in clients)
	{
		Player *player = [[Player alloc] init];
		player.peerID = peerID;
		[_players setObject:player forKey:player.peerID];

		if (index == 0)
			player.position = ([clients count] == 1) ? PlayerPositionTop : PlayerPositionLeft;
		else if (index == 1)
			player.position = PlayerPositionTop;
		else
			player.position = PlayerPositionRight;

		index++;
	}
}

First you create the Player object for the server and place it in the “bottom” position on the screen. Then you loop through the array of peer IDs for all connected clients and make Player objects for them. You assign the positions for the client players, in clockwise order, depending on how many players there are in total.

Notice that you don’t set the “name” property of these Player objects yet, because at this point you do not yet know the names of the clients.

Contributors

Over 300 content creators. Join our team.