• Recent Tutorials

  • Tripping The Class Fantastic: State Machines

    Designing a State Machine

    I'm not going to proclaim to be an expert on this subject. There is a lot of information on-line about how to do it, but this is how I would go about it (this approach has served me well in the past and it's the approach I still use today, because I find it clean and simple).

    1. Identify the states - Think logically about the states you will need. Don't be afraid of very simple states (i.e. don't have one state that does lots of things), you can always optimise later on
    2. Identify the transitions - Think about how you move from state to state (not when at this point, just how). In other words, if I'm in state X, which states can I move to?
    3. Identify the triggers - Once you have a list of states and the transitions, think about when you move from state X to state Y

    Let's consider a simple example.

    We're going to use a modem to listen for an incoming call, answer the call, wait for a command, process the command, close the connection when instructed by the remote caller and tidy up and wait for the next call if the line is dropped.

    So, caveat time... I'm not going to concern myself with the complexities of dealing with the modem, so for the purposes of this tutorial the modem will be represented by an object with the following methods and properties.

    • Method 'initialise' - Setup the modem
    • Method 'waitForCall' - Begin waiting for a call (when a call comes in ring indicator will be signalled)
    • Method 'cancelWaitForCall' - Cancel waiting for a call
    • Method 'sendString' - Sends a string (the string will be terminated by #13 - CR)
    • Method 'readString' - Reads a string (the string is terminated by #13 - CR)
    • Method 'pickup' - Answer an incoming call
    • Method 'hangup' - Hangup on a connected call
    • Property 'signalRingIndicator' - Returns true if there is an incoming call
    • Property 'signalCarrierDetect' - Returns true if the carrier is present
    • Property 'dataReady' - Returns true if there is one or more string available to be read

    All commands will be terminated by a CR, so it's safe to simply read the command using r'. And finally one biggie... for the purposes of this tutorial we are not going to concern ourselves with success or failure. If I tell the modem to hangup, it will do it without question and the command will succeed every time. I'm also going to assume that all actions performed by the modem will take no time.

    Caveats out of the way, onto design.

    Stage 1... identifying the states

    First things first, think about what you are going to do... initialise the modem, set it up to wait for a call, check to see if a call is coming in, answer it, wait for a command, process it, hang up if instructed and then reset and start again. If a call is in progress, check the state of the carrier detect line, if it drops, reset and start again. Turning this into a rough state list we get:-

    1. InitialiseModem
    2. BeginWaiting
    3. WaitingForCall
    4. CallDetected
    5. CallInProgress
    6. EndCall
    7. CallDisconnected

    That's a fairly short list, but it's also fairly complete.

    Stage 2... identifying transitions

    For each state, you need to work out to which other states it's possible to move.

    1. InitialiseModem - From here we can move to 2. BeginWaiting
    2. BeingWaiting - From here we can move to 3. WaitingForCall
    3. WaitingForCall - From here we can move to 4. CallDetected
    4. CallDetected - From here we can move to 5. CallInProgress
    5. CallInProgress - From here we can move to 6. EndCall and 7. CallDisconnected
    6. EndCall - From here we can move to 1. InitialiseModem
    7. CallDisconnected - From here we can move to 1. InitialiseModem

    Now we know what states we can move to, we need to decided what triggers each transition.

    Stage 3... identifying triggers

    For this, list each transition and then next to it, list the trigger for that transition.
    • 1. InitialiseModem -> 2. BeginWaiting - No trigger
    • 2. BeginWaiting -> 3. WaitingForCall - No trigger
    • 3. WaitingForCall -> 4. CallDetected - Ring Indicator is signalled
    • 4. CallDetected -> 5. CallInProgress - No trigger
    • 5. CallInProgress -> 6. EndCall - User issues the QUIT command
    • 5. CallInProgress -> 7. CallDisconnected - Carrier Detect is not signalled
    • 6. EndCall -> 1. InitialiseModem - No Trigger
    • 7. CallDisconnected -> 1. InitialiseModem - No Trigger

    Comments 3 Comments
    1. Ñuño Martínez's Avatar
      Ñuño Martínez -
      Just did a very fast reading (titles and some of the code, not much more). Looks very interesting.

      I did my own state-machine library translating the one described in"Programming Game AI by Example". Your approach seems slightly different. I'll tell you once I read your tutorial in-deep and compare both ways.
    1. AthenaOfDelphi's Avatar
      AthenaOfDelphi -
      If anyone has downloaded the attachment, the file classBaseStateMachine.pas was missing. Sorry about that, thanks to d.spinetti for pointing out my mistake.
    1. Ñuño Martínez's Avatar
      Ñuño Martínez -
      Read. Your implementation is way different than the one described in"Programming Game AI by Example" but it's a nice approach.