0 notes &
OpenFeint Multiplayer Integration Into Artifice
So, OpenFeint finally added turn based multiplayer into their SDK. I can now finally ditch Google App Engine and Urban Airship! WOO HOO!!! Don’t get me wrong, these are not bad services. I have actually found them to be quite nice. They can however both cost money, and the App Engine app is more code to maintain. Personally I would prefer to work on the client side over the server side any day. With the new OpenFeint multiplayer SDK I can do just that.
What follows is a quick rundown of the OpenFeint multiplayer system. This systems seems to be rather flexible and the way I used it is not the only way to do things.
More after the jump!
Where to start?
The first thing you have to do is initialize the OpenFeint multiplayer service. This is as simple as specifying a delegate. For my delegate I used one of the other OpenFeint delegates I was already using.
// Pass a reference to whatever object you want to house the // delegate methods you will need later [OFMultiplayerService setDelegate:openFDelegate];
I have decided to use the lobby approach for Artifice. This is used in the MPSampleApp that comes with the OpenFeint multiplayer SDK. I also decided that I would allow the user to have up to 10 games of Artifice running at one time. To do this we need to set the slot array size for the OFMultiplayerService.
[OFMultiplayerService setSlotArraySize:10];
These two setup method calls I have inside my app delegate right after my OpenFeint SDK initialization.
The delegate methods I needed to use are gameMoveReceived: and netWorkDidUpdateLobby. The first is called when an opponent makes a move. I will return to this later when I talk about making moves. The second method is called when a change has been made to one of your currently running games. In this method I simply post a notification to be caught by my lobby UI.
//Called when a move was received.
- (BOOL) gameMoveReceived:(OFMultiplayerMove *) move
{
if(move.code == OFMP_MC_DATA)
[Artifice postGotMove:[move data]];
return YES;
}
//Called when the games are updated from the network.
- (void) networkDidUpdateLobby
{
[Artifice postOFNetworkDidUpdateLobby];
}
Creating A Game
The next thing on our list is to start creating games. For this I pretty much followed the MPSampleApp example. I created a UIViewControler with a UITableView to hold the available game “slots”. When viewing and editing your game slots you need to “view” your games. You also need to stop viewing them when you are done. This is done in the sample app with viewDidLoad: and viewDidDisapear:. Viewing the games will have the networkDidUpdateLobby called which can be used to check the state of your game slots.
- (void) viewDidLoad
{
[OFMultiplayerService startViewingGames];
}
-(void)viewDidDisappear:(BOOL)animated
{
[OFMultiplayerService stopViewingGames];
}
Next some buttons can be created to execute the different actions on each slot. The Actions are as follows …
Create
The create command does just as it says, it will create a new game. There will be no opponent assigned when this is executed. This will allow a random person to join this game. When we create a new game we can simply use the OFMultiplayerService method getSlot:. We pass this an index from 0-9 in my case and it will return one of our 10 available OFMultiplayerGame objects. We then call the createGame:withOptions: method on the game object returned. The OF_GAME_DEFINITION_ID to my understanding is just an identifier for your game, in my case Artifice. As for the config, in my case there were only a few settings. Each game has a maximum and minimum of 2 players. The LOBBY_OPTION_CONFIG setting is used when finding a game.
OFMultiplayerGame* game = [OFMultiplayerService getSlot:slotIndex];
if(![game isActive])
{
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
@"GAME CONFIG", OFMultiplayer::LOBBY_OPTION_CONFIG,
[NSNumber numberWithUnsignedInt:2],
OFMultiplayer::LOBBY_OPTION_MAX_PLAYERS,
[NSNumber numberWithUnsignedInt:2],
OFMultiplayer::LOBBY_OPTION_MIN_PLAYERS,
nil];
[game createGame:OF_GAME_DEFINITION_ID withOptions:options];
}
Find
The find command will be used to find a game that has been created but has no opponent. Here we see the OF_GAME_DEFINITION_ID again, we use this to find a game of Artifice on the OpenFeint servers. We simply grab one of our OFMultiplayerGame objects with the OFMultiplayerService class. Then you call the findGame:withOptions: method and a game will be found and the client be assigned as an opponent.
OFMultiplayerGame* game = [OFMultiplayerService getSlot:slotIndex]; [game findGame:OF_GAME_DEFINITION_ID withOptions:nil];
Challenge
With this action you can challenge one of your OpenFeint friends to a game of Artifice. OpenFeint has finally added an easy way to show the friend picker. What took me about 5 lines of code before is now down to one. With this method you just specify a delegate, some text for the pickers title and an application id (to filter out people who don’t own the game).
[OFFriendPickerController launchPickerWithDelegate:self promptText:@"Choose an opponent" mustHaveApplicationId:@"910"];
With the exception of one setting, this is the same as our create action. The new setting is to take the chosen user from the picker and set them as the opponent.
OFMultiplayerGame* game = [OFMultiplayerService getSlot:slotIndex]; NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: @"GAME CONFIG", OFMultiplayer::LOBBY_OPTION_CONFIG, [NSNumber numberWithUnsignedInt:2], OFMultiplayer::LOBBY_OPTION_MAX_PLAYERS, [NSNumber numberWithUnsignedInt:2], OFMultiplayer::LOBBY_OPTION_MIN_PLAYERS, [NSArray arrayWithObject:[selectedUser resourceId]], OFMultiplayer::LOBBY_OPTION_CHALLENGE_OF_IDS, nil]; [game createGame:OF_GAME_DEFINITION_ID withOptions:options];
Play
Our play action will allow us to start sending moves for a particular game slot. We grab the OFMultiplayerGame and check if the game has started (the challenge has been accepted). If it has we load out game UI and start playing. If it has not been started we check if it currently holds a challenge from another player. If it does we accept the challenge and wait for them to go.
OFMultiplayerGame *game = [OFMultiplayerService getSlot:slotIndex];
if([game isStarted])
{
// Load game UI
}
else if([game hasBeenChallenged])
{
[game sendChallengeResponseWithAccept:YES];
}
Cancel
Finally, our cancel action will either refuse a challenge or cancel and delete the game.
OFMultiplayerGame *game = [OFMultiplayerService getSlot:slotIndex]; if([game hasBeenChallenged]) [game sendChallengeResponseWithAccept:NO]; else [[OFMultiplayerService getSlot:path.row] cancelGame];
Enter/Leave A Game
In order to start and stop receiving moves for a particular game you need to call the following methods. For instance, in Artifice I call enterGame: when I load the board UI and I call leaveGame when I close the board UI.
OFMultiplayerGame *game = [OFMultiplayerService getSlot:slotIndex]; [OFMultiplayerService enterGame:game]; [OFMultiplayerService leaveGame];
Taking a Turn
Now that we have a game created and a challenge has been accepted we need to start playing. For this we grab the current game we want to make a move for and call the method sendMove:. This method takes an instance of NSData, which can be used to pass the current users turn information to the opponent. In my case I simply created a game object that can be serialized and deserialize. This way I can just pass the current state back and forth via the sendMove: method. After sending the actual data I then call the sendEndTurnWithPushNotification: method. This will allow me to send a push notification with a specified message to the opponent.
OFMultiplayerGame *game = [OFMultiplayerService getSlot:activeSlot]; [game sendMove:[game gameData]]; [game sendEndTurnWithPushNotification:[NSString stringWithFormat:@"%@ went, it's your turn!", [OpenFeint lastLoggedInUserName]]];
OpenFeint recommends that you wrap your send move method calls inside an if statement checking the isReadyToSendMoves method of the OFMultiplayerService class. I had problems with that, more on this later.
[OFMultiplayerService isReadyToSendMoves];
Now, back to the delegate method we saw at the beginning. Since the sendEndTurnWithPushNotification: is technically counted as a move I wanted to filter them out. I just needed the move that contained the data for the game. To do this we can just check the code property of the OFMultiplayerMove object received. I then take that and dispatch a notification to be received by my game UI. Once the game UI has the data it can deserialize it and load it into view.
//Called when a move was received.
- (BOOL) gameMoveReceived:(OFMultiplayerMove *) move
{
if(move.code == OFMP_MC_DATA)
[Artifice postGotMove:[move data]];
return YES;
}
Ending A Game
When you want to end the game you need to know what order your players are in. You can use the game.playerOFUserIds of the OFMultiplayerGame object to do this. First I check to see if the current player is the first id in the playerOFUserIds array. The we do a test to see if the current player is the winner or loser. We then create an NSNumber with either a 0 (lose) or a 1 (win) for each of our players in the game. After we have our “ranks” we can add them to an array and pass them to OFMultiplayerService finishGameWithPlayerRanks: method. This will end the game.
NSMutableArray *players = game.playerOFUserIds; BOOL firstIsMe = [[players objectAtIndex:0] isEqualToString:[OpenFeint lastLoggedInUserId]]; // Test if the current player is the winner BOOL iWin = YES; NSNumber * me = iWin ? [NSNumber numberWithUnsignedInt:1] : [NSNumber numberWithUnsignedInt:0]; NSNumber * you = iWin ? [NSNumber numberWithUnsignedInt:0] : [NSNumber numberWithUnsignedInt:1]; if(firstIsMe) [OFMultiplayerService finishGameWithPlayerRanks:[NSArray arrayWithObjects:me, you, nil]]; else [OFMultiplayerService finishGameWithPlayerRanks:[NSArray arrayWithObjects:you, me, nil]];
Final Thoughts
All in all I like the OpenFeint multiplayer service. It still seems very flexible and nice to work with. There are some issues I have run into though. For instance sometimes when I accept a challenge it will empty the game slot on both clients (There is a fix for this in the comments, thanks Ron!). Another problem that I came across was in receiving moves. The gameMoveReceived: method returns a BOOL if the move has been processed. Although I process all the moves, deep in the OpenFeint code the mIncomingMoveQueue.count never drops to zero. This makes isReadyToSendMoves always return false. The third issue that I found is that sometimes a client will receive it’s own moves. This caused problems in my game since it makes the move happen twice (I was able to get OpenFeint the debug info needed to fix this issue, it should be coming soon).
This post is my attempt at better understanding the work I just did. There may be mistakes and please feel free to point them out if you see any.



