Finite State Machine Fun

If you think you’ve never used a state machine, you probably have and didn’t know it. That ‘MovieClip’ time line with bits of code attached to it is a state machine.

A lot of complicated and messy game state can be encapsulated in one or more state machines and be made very simple and sensible.

Let’s look at a very simple one written in AS2, and attached to the first frame of a blank Flash file. This waits for two mouse clicks, to draw a line. It’s part of a unit test for a little piece of code I’m working on, but I thought I’d share.


// How big the grid is
var gridSize : Number = 16;
// Line state we're filling in
var xStart : Number = 0;
var yStart : Number = 0;
var xEnd : Number = 0;
var yEnd : Number = 0;
// State machine mouse click event
var bClicked : Boolean = false;
var xMouse : Number = 0;
var yMouse : Number = 0;

/*
 * Big, dumb state machine
 */
var stateCurr:Number = 0;  /* Current state index */
var loop : Number = 0;     /* A place to set a 'loop' and repeat state */
var states : Array = new Array /* function */
(
    function()    // Initialize
    {
        var line : Number;
        lineStyle( 0, 0xe0e0e0, 100, true, "none", "none", "none", 1);
        for( line = 0; line < 640; line += gridSize )
        {
            moveTo(line,0);
            lineTo(line,479);
        }
        for( line = 0; line < 480; line += gridSize )
        {
            moveTo(0,line);
            lineTo(639,line);
        }
        stateCurr++;
        Mouse.addListener(this);
    },
    function()    // Wait for first point
    {

        if( bClicked )
        {
            xStart = xMouse;
            yStart = yMouse;
            bClicked = false;
            // Show Start
            drawLittleX( xStart, yStart, 0x004000 );
            loop = stateCurr++;
        }
    },
    function()    // Wait for second point
    {
        if( bClicked )
        {
            bClicked = false;
            xEnd = xMouse;
            yEnd = yMouse;
            drawLittleX( xEnd, yEnd, 0xff0000 );
            // Draw the line
            lineStyle( 0, 0x0000ff, 100, true, "none", "none", "none", 1);
            moveTo(xStart,yStart);
            lineTo(xEnd,yEnd);
            stateCurr++;
        }
    },
    function()
    {
        stateCurr = loop;
    }
);

// Cycle the state machine
function onEnterFrame():Void
{
    // More complicated state machines can have flags telling it NOT to cycle pending
    // some generic event or notification, or be added/removed from lists to do the 
    // same, but this is all 'Top Level' code, not inner-loop code, so any performance 
    // gain might be negligible, especially considering the added complexity.

    // Operates ONE state at a time.
    states[stateCurr]();
}

// Mouse button event
function onMouseDown():Void
{
    // Let the state machine know the mouse was clicked.
    xMouse = _xmouse;
    yMouse = _ymouse;
    bClicked = true;
}

// Draw a little colored X
function drawLittleX( x:Number, y:Number, rgb:Number ) : Void
{
    lineStyle( 0, rgb, 100, true, "none", "none", "none", 1);
    moveTo(x-2,y-2);
    lineTo(x+2,y+2);
    moveTo(x+2,y-2);
    lineTo(x-2,y+2);
}

It’s super-easy to add and remove pieces of state from the ‘to-do’ list. You could wait for a third point to make a triangle very easily with trivial additions, put up prompts, etc.

Your first impulse you might be “I could put that in several frames, and attach a piece of AS code to each!” Yes, you could do that, and you would also have a state machine… but then you’d have a whole bunch of disconnected little nonsensical snippets of code to deal with, and sometimes that time line ain’t all that it’s cracked up to be for more complex character animation under application control.

A single state machine could follow a movie and wait for a frame, or be ‘cued’ by a single-line piece of AS code attached to a frame. So the ‘flow’ of a sequence of events can be seen all in one file, rather than groping around with a time line in that awful user interface.

One could also add labeled ‘events’ to the list fairly easily (wrap each state in a {label:String, call:function} Object and iterate to find state, or write the state machine in a switch, or combine switched and anonymous state), to give it points to ‘Jump’ to based on other state. You could wrap it all up and derive from an Object to add all manner of stuff to it.

State machines can made to communicate among each other and ‘multitask’ fairly trivially. One might be waiting for animation, and another waiting for user input, or a real-time clock, or an incoming message, or all of these things at different times, while a further machine waits for combinations of other state.

Anyway, the whole game becomes simple state machines to do tasks over time, instead of, well, whatever it is you were doing.

A card game pseudo-state machine[LIST]
[]Initialize Card Game
* Shuffle Cards
[
]Cut deck
[]Deal Cards
* Wait for Player Bet
[
]Show Bet
[]Next Player
[
]Deal Card…
*
[]Determine winner(s)
[
]Split pot
[]Eliminate broke players
[
]Go back to shuffle cards[/LIST]A turn-based combat pseudo-state machine[LIST]
[]Countdown timer
* Player turn
[
]Do player moves
[]Do player combat turns
[
]Monster turn
[]Do monster moves
[
]Do monster combat turns
[*]Jump to loop[/LIST]As you can see, the code and the ‘To Do’ list for what the code needs to do become much the same.

Naturally, this sort of thing is just MADE for real-time AI. A combination switched/anonymous state machine could have patrol, reaction, pursuit, etc. behaviors all wrapped into one or more simple machines. Scripting movement and animation can be trivially waypoint-based, ‘events’ from the environment can trigger AI behavior, that sort of thing.

Debugging the state machine is pretty straight forward, too. Slap a breakpoint on ‘onEnterFrame()’ (or whatever you call it from), or in certain states, and wait for it to hit. You can then step in and see exactly what state it’s stuck on.