How To Make a Simple Playing Card Game with Multiplayer and Bluetooth, Part 7
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.
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 7
55 mins
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 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, second, third, fourth, fifth, and sixth parts of the series, you created most of the game, including the networking infrastructure, card animations, and gameplay.
In this seventh and final part of the series, you will finally wrap up the game! We will finish up the “Snap!” logic, fix some edge cases, add win/lose detection, and even implement a single player mode with a computer player!
Keep reading to finally finish this epic series!
Reliable vs unreliable
So far all the network transmissions you’ve sent were in reliable mode, which guarantees messages will be delivered with their contents 100% intact.
The problem with reliable mode is that on a bad network connection that drops a lot of packets, message sending can be very slow. If a message couldn’t be delivered, the networking stack will try again and again and again, until it either gives up completely and disconnects or the message is successfully delivered.
Also, even though the packets are guaranteed to arrive, the problem is the order in which they arrive is not guaranteed. You’ve seen the problems this can cause and how to work around them in the previous part of the series.
How to deal with these issues? Well, there is another method of sending data over the network and that is in unreliable mode.
With unreliable mode the only guarantee is that if the message arrives it will be 100% complete, but it’s sent only once and if something goes wrong along the way, the entire message is dropped. The recipient will never know about it.
When you’re writing a multiplayer network game that needs to be real-time, you’d usually send unreliable messages, because you don’t need the retry mechanism. By the time the retried message arrives at its destination it is likely no longer relevant. Therefore it’s better to send everything unreliably and hope that it arrives, and have some mechanism in place to deal with messages that get lost.
Tip: In a real-time game that sends position updates using unreliable packets, you’d use a technique called “dead reckoning” to estimate where the game objects will be next, and then adjust when you receive the next network message. Every second or so a larger packet with a full status update is sent, just so that clients are able to bring themselves up-to-date and get the complete picture again.
Tip: In a real-time game that sends position updates using unreliable packets, you’d use a technique called “dead reckoning” to estimate where the game objects will be next, and then adjust when you receive the next network message. Every second or so a larger packet with a full status update is sent, just so that clients are able to bring themselves up-to-date and get the complete picture again.
In Snap! you want the PlayerShouldSnap packets to arrive at the server as quickly as possible. Therefore you will now send them unreliably in order to lose as little time as possible.
This means that sometimes when a player taps the Snap! button their packets will get lost along the way. C’est la vie. Most of the time the packets will arrive properly anyway.
You’ll add a new property to Packet that indicates whether this packet should be sent reliably or unreliably. In Packet.h:
@property (nonatomic, assign) BOOL sendReliably;
Synthesize this property in Packet.m:
@synthesize sendReliably = _sendReliably;
Set its value to YES in the init method because by default you want all packets to be sent reliably:
- (id)initWithType:(PacketType)packetType
{
if ((self = [super init]))
{
self.packetNumber = -1;
self.packetType = packetType;
self.sendReliably = YES;
}
return self;
}
The only packet you will send unreliably in our app is PacketPlayerShouldSnap. Change its init method to:
- (id)initWithPeerID:(NSString *)peerID
{
if ((self = [super initWithType:PacketTypePlayerShouldSnap]))
{
self.peerID = peerID;
self.sendReliably = NO;
}
return self;
}
The actual sending of the packets happens in two places in Game.m, in the sendPacketToAllClients: and sendPacketToServer: methods. In both methods change the line that initializes the dataMode variable to:
GKSendDataMode dataMode = packet.sendReliably ? GKSendDataReliable : GKSendDataUnreliable;
And that’s it. You should test the app to see that tapping the Snap! button on the client still shows the red X on the server, but you won’t notice any differences in how the app behaves unless you’re on a really crappy network.
“Too late”
The first player to yell “Snap!” wins (or has to pay up if there are no matching cards). For any other players who yell “Snap!” too late, you simply show the speech bubble. To keep track of this you use a new boolean instance variable:
@implementation Game
{
. . .
BOOL _haveSnap;
}
You reset this value to NO at the top of beginRound:
- (void)beginRound
{
_haveSnap = NO;
. . .
}
And in turnCardForPlayer:
- (void)turnCardForPlayer:(Player *)player
{
_haveSnap = NO;
. . .
}
That means after the active player has turned over his card, everyone can yell “Snap!” again.
Finally, playerCalledSnap: becomes:
- (void)playerCalledSnap:(Player *)player
{
if (self.isServer)
{
if (_haveSnap)
{
[self.delegate game:self playerCalledSnapTooLate:player];
}
else
{
_haveSnap = YES;
[self.delegate game:self playerCalledSnapWithNoMatch:player];
}
}
else
{
Packet *packet = [PacketPlayerShouldSnap packetWithPeerID:_session.peerID];
[self sendPacketToServer:packet];
}
}
There is now a new delegate method, so add it to the protocol in Game.h:
- (void)game:(Game *)game playerCalledSnapTooLate:(Player *)player;
Add the implementation to GameViewController.m:
- (void)game:(Game *)game playerCalledSnapTooLate:(Player *)player
{
[self showSnapIndicatorForPlayer:player];
[self performSelector:@selector(hideSnapIndicatorForPlayer:) withObject:player afterDelay:1.0f];
}
Here you just show the speech bubble and then hide it again after one second. Try it out, for the second player who taps the Snap! button you only show the speech bubble but no red X. Again, for now the speech bubbles will only appear on the server but you’ll remedy that next.