Mobile Zone is brought to you in partnership with:

Kristina Chodorow is a core contributor to MongoDB. She has written several O'Reilly books (MongoDB: The Definitive Guide, Scaling MongoDB, and 50 Tips and Tricks for MongoDB Developers) and has given talks at conferences around the world, including OSCON, FOSDEM, Latinoware, TEK·X, and YAPC. Her Twitter handle is @kchodorow. Kristina is a DZone MVB and is not an employee of DZone and has posted 52 posts at DZone. You can read more from them at their website. View Full User Profile

Programming a State Machine

04.02.2013
| 7504 views |
  • submit to reddit

My attempts at game programming usually turn into impenetrable spaghetti code: “If the player walks through this door, then have him talk to the princess, unless he’s killed a guard, in which case the guards attack, or if he comes out of the secret passage…”

The game I’m working on now is pretty simple, but I’ve kept it really clean (so far) by using a state machine to keep track of what should happen when. Basically, each scene in the game is a state. There’s an overarching state machine which runs the current state on each tap. A state can either return itself or choose a new state to run next time.

In Objective C (+cocos2d), a state looks like this:

@interface State : NSObject {
    CCLayer *ui;
}
 
-(id) init:(CCLayer*)layer;
-(Class) processTouch:(UITouch*)touch withEvent:(UIEvent*)event;
 
@end

The processTouch function either returns nil, which means “run me again next time” or the next state to run. The other half is a “machine” to run the states:

// -----------------
// Interface
// -----------------
 
@interface StateMachine : NSObject {
    State *currentState;
    CCLayer *ui;
}
 
-(id) init:(CCLayer*)layer;
-(BOOL) processTouch:(UITouch*)touch withEvent:(UIEvent*)event;
 
@end
 
// -----------------
// Implementation
// -----------------
 
@implementation StateMachine
 
// Initialize the state machine by setting currentState to the first state
-(id) init:(CCLayer*)layer {
    self = [super init];
 
    if (self) {
        ui = layer;
        currentState = [[FirstState alloc] init:ui];
    }
 
    return self;
}
 
// Run this from the UI's touch dispatcher: it runs the current state's processing code
-(BOOL) processTouch:(UITouch*)touch withEvent:(UIEvent*)event {
    Class nextState = [currentState processTouch:touch withEvent:event];
 
    if (nextState != nil) {
        currentState = [(State*)[nextState alloc] init:ui];
    }
}
 
@end

Then, you might have an implementation like this for a swords & sorcery RPG:

@interface PalaceDungeonState : State {
    Guard *guard;
}
 
@implementation PalaceDungeonState
 
-(id) init:(CCLayer*)layer {
    // Use ui to render a dungeon
}
 
-(State*) processTouch:(UITouch*)touch withEvent:(UIEvent*)event {
    if (guard.alive) {
        [guard updatePosition];
    }
 
    CGPoint touched = [ui convertTouchToNodeSpace:touch];
 
    switch (touched) {
    case GUARD:
         [guard dies];
         break;
    case STAIRWAY:
         return PalaceStairwayState;
    case SECRET_PASSAGE:
         return SecretPassageState;
    }
 
    return nil;
}
 
@end

I’m not thrilled with doing so much work in the init, so for this type of game I’d probably move that to a start method that would be called by StateMachine on state changes.

Regardless, I’ve found this makes it a lot easier to make a complicated sequence of events while keeping my code readable.

Published at DZone with permission of Kristina Chodorow, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Tom Poindexter replied on Tue, 2013/04/02 - 9:59am

Perhaps you will want to take a look at State Machine Compiler  http://smc.sourceforge.net/

It can generate state machine code for 15 languages (including Objective-C) based on a fairly simple description language.  SMC can also output Graphviz DOT files, so you can visualize your state machine http://smc.sourceforge.net/SmcGallery.htm



Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.