Eradicate Week 7: Turn Based Gaming

This week I want to talk about how two issues converged: saving game state and turn based match play. Saving the state of a strategy game is an obvious feature. If every time you turn off the game you have to restart the game, people stop playing.

As a selling point, we are also going to implement Turn Based Matches (Apple API not quite updated, check this tutorial to get started), which is also a prefect fit for turn based strategy games.

The reason these two issues converge is because the current packet size limit for the turn based game, see http://www.idownloadblog.com/2011/08/19/new-in-beta-6/, check the Game Kit section. Hopefully Apple increases the limit later, but until then, I had to work with the 4KB limitations.

Basically (and this is a non-technical post), you need to implement NSCoding Protocol on all classes that you want to save. To do this, you need add two methods to your class (and to your definition):

- (void)encodeWithCoder:(NSCoder *)encoder;

This method will is used to store/serialize your data.

- (id)initWithCoder:(NSCoder *)coder;

This method is used to unencode/unserialize your data.

Here is a very simple example from our game:

- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeInt:roleType forKey:@"roleType"];
    [encoder encodeInt:rolePosition forKey:@"rolePosition"];
    [encoder encodeObject:currentLocation forKey:@"currentLocation"];
}

- (id)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        
        [self updateToRole:[coder decodeIntForKey:@"roleType"]];
        rolePosition = [coder decodeIntForKey:@"rolePosition"];
        currentLocation = [coder decodeObjectForKey:@"currentLocation"];
    }  

We are basically saving the type, data position and current location for each role in the game (between 2 and 4 of them).

You never directly call these methods though, they are called indirectly when you try to encode this object from elsewhere, using code like this:


-(void) updateGameDataWithID: (NSString *) stringID{  //This is called by our game engine after each turn.
    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *docPath = [NSString stringWithFormat:@"%@/%@", [paths objectAtIndex:0], stringID];

    NSMutableData *data = [[NSMutableData alloc] init];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    [archiver encodeObject:self forKey:@"gamePlayLayer"];
    [archiver finishEncoding];
    
    [data writeToFile:docPath atomically:YES];
}

The [archiver encodeObject:self ....] line is where all the serialization starts. The current object finds its Encoder method and starts writing data, calling other objects' Encoder methods, like our Role class.

Check this tutorial for a bit more in-depth explanation, or this article for a super quick intro.

Back to the point

So the main issue, is that after this is all done, we needed to make sure this was under 4Kb, for the match play. My first pass we were well over 7Kb of data (use a NSLog(@"%@", [data length]); to check the size).

So to get under the limit, instead of just saving all the data, we had to save the 'right' data, and then reconstruct the rest upon loading. So we didn't save the role name and description, since they can both be recreated from the role type. We just saved the type. Also, integers representations for a city, instead of the city name, were much smaller. We created an array upon loading the game with all the city information with an integer index, and then just save that index to an array and save that array (an array of five integers is much smaller than an array of five strings).

Thus laboring for a few hours, I was able to get the save game data to about 3Kb, which gives us plenty of wiggle room to add new features or expand the game world a bit w/o going over the 4Kb limit.

This is a dev diary about our Eradicate project (view all posts here). Also visit Skejo Studios to see all we are doing there.