1

I'm currently refactoring an in-game AI into a Finite State Machine based on this example, which I chose for its readable and highly visual definition format:

class FiniteStateMachine
{
    public enum States { Start, Standby, On };
    public States State { get; set; }

    public enum Events { PlugIn, TurnOn, TurnOff, RemovePower };

    private Action[,] fsm;

    public FiniteStateMachine()
    {
        this.fsm = new Action[3, 4] { 
        PlugIn,       TurnOn,                 TurnOff,            RemovePower
        {this.PowerOn,  null,                   null,               null},              //start
        {null,          this.StandbyWhenOff,    null,               this.PowerOff},     //standby
        {null,          null,                   this.StandbyWhenOn, this.PowerOff} };   //on
    }
    public void ProcessEvent(Events theEvent)
    {
        this.fsm[(int)this.State, (int)theEvent].Invoke();
    }

    private void PowerOn() { this.State = States.Standby; }
    private void PowerOff() { this.State = States.Start; }
    private void StandbyWhenOn() { this.State = States.Standby; }
    private void StandbyWhenOff() { this.State = States.On; }
}

This state machine class is instantiated by the parent object, which to match this simple example I'll call "Device".

What I'm trying to work out now is how best to extend this to implement the various State and Transition behaviors. Imagine the Device class has a counter, which we want to increment whenever we power on. How do we increment that counter?

The Action delegates used for Transition behaviors here do not take parameters from outside the FiniteStateMachine class, and I'm pretty sure it would be terrible practice to be making changes to the containing class from in here anyway.

But if I'm not going to be implementing any of the actual behavior in this class, I feel like I've missed the point of implementing a Finite State Machine in the first place. I understand the benefit of it throwing a null reference exception when I try to perform an illegal state change, but the plan was to have it encompass behavior as well.

If it doesn't, then I'm not actually changing any of the existing behavioral code: I'm just refactoring it to call ProcessEvent() instead of setting the State directly from the calling code. Safer, but not cleaner.

So my questions are:
1. What would be cleanest way to implement the behavioral elements of a finite state machine that affects it's containing class?
2. Should I forgo the above FSM model in favor of a different one to achieve this?

Community
  • 1
  • 1
Quasar
  • 172
  • 1
  • 13

1 Answers1

0

You should check out the state pattern (as suggest in zerkms' comment).

This will allow you to implement additional actions on state transitions easily.

When using the state pattern, you still have to decide how to allow individual states to share data. If you put shared data in the context class, then it is difficult to expose it to states without making it public. C++ implementations often use friend to allow states to access private data in the context. In C#, I have sometimes encapsulated shared data in a separate class which is passed from the context to the states, but not exposed publicly by the context or states.

If that isn't clear to you once you've grokked the state pattern, then add a comment and I can post some code.

Ergwun
  • 12,579
  • 7
  • 56
  • 83
  • I've looked into the state design pattern and I think I understand it now. The implementations I've seen are a bit more complex than expected, but I can see why. The state design pattern doesn't seem to allow for duplicated transition methods like "PowerOff" though, or for all the 'nulls'. Is there any way around this, or do I have to duplicate the code in each concrete state class? – Quasar Nov 22 '13 at 02:13
  • You can use inheritance amongst your state classes. Typically, you can have a base state class that has the default behaviour for each event, and override as required in child classes. The nulls in your example mean that that FSM cannot handle an event in a certain situation (in fact, it will throw if you try). When using the state pattern, the default response to an event will often be to just remain in the same state. – Ergwun Nov 22 '13 at 05:05