TicTacToe: Multi-device Syncing part #2

In the first part of our tutorial, we discussed the data model we’ll use to power a multi-device TicTacToe game.

We also learned how to integrate Simperium into an iOS app, and how to authenticate our clients by means of a pre-generated token.

One of the biggest technical challenges of this app is: suppose our users have a Match initiated between two players. In our current data model, this is directly translated into: the two clients each have a Player object in the Player bucket, and there is also a Match object linking both players.

Due to the nature of mobile devices, any of the clients can suffer a network disruption at any-time (the device itself might even shutdown due to low battery!)

So… how do we notify our users that their opponents went offline?

1. Simperium’s Presence

Presence is one of the coolest features offered by Simperium. What does it do, exactly?

The presence feature turns a bucket into an ephemeral store. There are two options:

  • User Presence
    Whenever a given user gets disconnected from Simperium’s backend, all of the objects he inserted into buckets with user presence enabled will be automatically deleted. No client interaction is needed, other than the disconnection event itself!.
  • Client Presence
    It works exactly the same way as User Presence, but it is client (or connection) based instead. In Simperium presence’s jargon, a client is represented by a single connection to Simperium. Let’s suppose that someone has both an iPad and an iPhone running the app, these would be two separate clients. Objects created by the iPhone then, would be removed when the iPhone disconnected, but objects created by the iPad would remain as long as the iPad stayed connected.

In our particular scenario, due to our decision to automatically authenticate every device with the same pre-generated auth-token, we’ll need to enable Client Presence in both the Match and Player buckets.

By toggling client presence on, whenever a device running our game goes offline, Simperium will automatically remove all of the Player and Match entities that were created by that device. 

In order to toggle presence for a given bucket, we’ll need to run a simple curl request:

curl -H 'X-Simperium-Token:APP_ADMIN_TOKEN' \
    https://api.simperium.com/1/APP_ID/__options__/i/BUCKET_NAME \
    -d '{"presence":"client"}'

The APP_ID and APP_ADMIN_TOKEN are both available in your Simperium application’s dashboard, as seen here:

SimperiumAdminKey

Since we require client presence to be enabled in both, Player and Match buckets, we’ll need to run two curl requests.

Once that’s ready, Simperium’s backend will make sure that whenever a client goes offline, all of the objects that were inserted by that client are removed.

2. Signaling Player’s Presence

In order to signal a Player’s presence, let’s insert a new Player entity into CoreData, whenever the app becomes active:

Simperium* simperium	= [[TTAppDelegate sharedDelegate] simperium]
Player *player			= [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Player class]) inManagedObjectContext:simperium.managedObjectContext];
player.playerID			= simperium.clientID;
[simperium save];

// Keep a reference to the player
self.player = player;

We’ve already written the code that will disconnect the WebSocket when the app becomes inactive. When that happens, we can expect the Player entity to be deleted (due to our presence settings): all we need to do is to clear the reference to the player instance!

3. Initiating a Match

We’ve discussed before that a match entity will be a simple object that carries the playerID of the involved parties, plus an object representing the TicTacToe board itself. It’s time for us to discuss some application logic…

A match should be initiated when:

  • The app is just launched.
  • Your opponent is removed from the Player’s bucket: maybe he closed the app, his device was shutdown… or he just lost connectivity!
  • The match in which you’re playing suddenly gets removed. This will happen if your opponent initiated the match, and signed off from Simperium’s backend.

An opponent is eligible if:

  • He is not already playing a match with someone else.
  • His playerID is not the same as our own playerID. Otherwise we’d be initiating a match with ourselves!.

All of these conditions can be easily checked, locally, by means of a couple CoreData queries. You don’t need to perform a single backend request: Simperium informs you of any changes to the data.

Code for the entire startMatch method implementation is available here.

4. Listening for new changes

Our application logic is based on two simple data structures. In order to react accordingly to the game state as new changes come in, we’ll need to setup our two Simperium Bucket’s delegates (Player and Match) as follows:

- (void)listenForBucketUpdates
{
	Simperium *simperium	= [[TTAppDelegate sharedDelegate] simperium];
	NSArray *buckets		= @[ NSStringFromClass([Player class]), NSStringFromClass([Match class]) ];

	for (NSString *name in buckets)
	{
		SPBucket *bucket	= [simperium bucketForName:name];
		NSAssert(bucket != nil, @"Bucket not initialized");

		bucket.delegate		= self;
	}
}

You can take a look at the entire delegate implementation here. The different events that are being handled are:

  • Player Inserted
    We will simply refresh the label that displays the number of active players.
  • Player Deleted
    When a player gets deleted, we’ll need to check if the playerID is the same as our opponent’s playerID: if our opponent was removed, we’ll need to move on and try to begin a new match with another player.
  • Match Inserted
    When a new match event gets triggered, we’ll need to react only if our own  playerID is equal to either the crossPlayerID or the circlePlayerID.If that condition is affirmative, and we’re not currently in a match with another player, we should proceed with accepting the new challenge, and just refresh the UI. However, if we were already in a match with another player, we’ll simply delete the new Match object, and let the opponent realize that we’re busy!.
  • Match Updated
    Our app will have, all the time, the ID of the match we’re currently playing (if any). Whenever our own match is updated, we’ll just refresh the interface. If it’s our turn, the user will get to place a cross (or a circle). If not, we’ll have to wait for our opponent.
  • Match Deleted
    A match can be deleted for a variety of reasons: maybe our opponent went offline, or we initiated a match with a player that was already busy.
    Whenever that happens, we’ll need to refresh the UI, and begin a new match with someone else.!

5. Sync’ing the GameBoard

The TicTacToe board itself is represented by a NSArray instance with 9 NSNumber’s in it.  We’ve defined a handy NS_ENUM to specify the allowed matrix values: empty, circle, and cross.

We’ll rely on our Match entity just as a Transport Object: whenever there is a change that requires the UI to be refreshed, we’ll feed our TTGameBoard instance with the updated match.matrix property, and simply request our TTBoardView object to get refreshed.

You can check out the code for refreshing the user interface here.

6. Full Source Available!

You’re welcome to take a look at the final project. Note that you should open TicTacToe-Final.xcodeproj to get the final version of the project.

Got any questions? we’d love to help! Please leave a comment, ask a question, or post an issue, and we’ll get back to you shortly.

TicTacToe: Multi-device syncing with Simperium!

Simperium is a cross platform framework that allows you to seamlessly sync data through multiple devices.

Tic Tac Toe is one of the simplest games you can possibly think of: two players take turns to place a piece (either a cross or a circle) on a 3×3 matrix. The first player that succeeds in placing three marks in either a horizontal, vertical, diagonal or antidiagonal wins the game.

We have built a single-device Tic Tac Toe game for iOS: source code available here. In this tutorial we’ll be learning how to integrate the Simperium framework, and how to enable multi-device data synchronization without writing a single line of backend code.

Our approach to implement the data model will be straight forward. We will maintain two collections:

Players:

Each player will have a unique ID, and will make sure that as long as the app is active, its uniqueID will be present in the Players collection. We will rely on this mechanism to initiate a match between two players.

Match:

The match object itself will have three attributes: the ID of both players, an array representing the status of the gameboard, and an extra flag we’ll use to indicate which player should perform the next move.

Let’s dive into the details!

1. Integrating the Simperium Framework

First of all, we’ll need to add drag the Simperium.xcproj file to our TicTacToe project, as seen below:

Drag Simperium Project

Once ready, let’s make sure we’re linking the required frameworks:

Required Frameworks

Since Simperium is an Objective C framework, we will need to instruct Xcode’s linker to work with Objective C libraries as well. In order to do so, let’s add the ‘-ObjC‘ string in the Other Linker Flags, under the project’s Build Settings, as seen here:

LinkerFlags

At last, we’ll need to add Simperium as a target dependency, in the Build Phases section of our project:

2. Updating our Data Model

Simperium is designed to listen to your CoreData Context, and efficiently sync delta’s through the network.

As a requirement, the classes you want to sync must subclass SPManagedObject -instead of NSManagedObject-.

We’ll need to also store two extra attributes per object, used internally by Simperium: simperiumKey and ghostData, both of NSString kind.

Let’s open our Data Model, and update both, Match and Player entities, accordingly:

1. MatchSchema

2. PlayerSchema

3. Adding a New Simperium App

We’ll need to signup for an account at Simperium’s website. Once there, let’s add a brand new app. We’ll need both, the AppId and AppKey, to initialize our instance of Simperium:

NewApp

There are several ways in which you can implement user authentication: the framework itself already provides a customizable User Interface to perform Signup and Sign-In.

However, in this particular project, we want to offer a signup-free experience: we want our users to be able to begin playing Tic Tac Toe, as soon as the app finishes launching.

In order to do so, let’s pre-generate a user token: every instance of this game will share the same user. You can find the Generate Token button in the Browse Data section of your app:

GenerateToken

4. Updating our CoreData stack

One of the benefits of using Simperium’s library is that Core Data disk operations are performed in a background thread, automatically, by the library. As a requirement for this to work, the Main ManagedObjectContext must not have its persistentStoreCoordinator set.

In our TicTacToe game, we’ve wrapped up all the CoreData initialization code in a single class, called TTCoreDataManager (Header | Implementation). The only difference with a regular CoreData stack initialization can be seen below:

_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

// persistentStoreCoordinator must be nil on the main context!:
// [_managedObjectContext setPersistentStoreCoordinator:coordinator];

5. Starting Simperium

We’ll be initializing and authenticating our Simperium instance in the AppDelegate itself, as soon as the application:didFinishLaunchingWithOptions: method is executed:

- (void)startSimperium
{
	TTCoreDataManager* manager = [TTCoreDataManager sharedInstance];

	self.simperium = [[Simperium alloc] initWithModel:manager.managedObjectModel
											  context:manager.managedObjectContext
										  coordinator:manager.persistentStoreCoordinator];
}

- (void)authenticateSimperiumIfNeeded
{
	if (self.simperium.user.authenticated) {
		return;
	}
	
	[self.simperium authenticateWithAppID:TTSimperiumAppId token:TTSimperiumToken];
}

After calling the two routines outlined above, we’ll have a Simperium instance initialized with manual authentication, and our CoreData stack will be ready to begin operating.

We must also call the method authenticateSimperiumIfNeeded everytime the app becomes active, and signout from Simperium’s backend when the app becomes inactive.

We’ll rely on the following lines of code to do so:

- (void)applicationDidBecomeActive:(UIApplication *)application
{
	[self authenticateSimperiumIfNeeded];
}

- (void)applicationWillResignActive:(UIApplication *)application
{
	[self.simperium signOutAndRemoveLocalData:YES completion:nil];
}


6. Next Up…

At this point we have a fully functioning TicTacToe game, we have successfully integrated the Simperium Framework, and we’re automatically authenticating the user when the app is brought to foreground (and logging off when the app is closed).

In the second part of this tutorial, we’ll learn how to signal our presence, initiate matches between two players, and how to sync our board… everything, just with Simperium Sync.

Stay tuned…!

Learn more about how to integrate Simperium into your iOS app.