Creating a State Machine Class
The design can be made a lot cleaner by encapsulating all the state related
data and methods into a state machine class. This way an agent can own an
instance of a state machine and delegate the management of current states,
global states, and previous states to it.
With this in mind take a look at the following
StateMachine class
template.
template <class entity_type>
class StateMachine
{
private:
//a pointer to the agent that owns this instance
entity_type* m_pOwner;
State<entity_type>* m_pCurrentState;
//a record of the last state the agent was in
State<entity_type>* m_pPreviousState;
//this state logic is called every time the FSM is updated
State<entity_type>* m_pGlobalState;
public:
StateMachine(entity_type* owner):m_pOwner(owner),
m_pCurrentState(NULL),
m_pPreviousState(NULL),
m_pGlobalState(NULL)
{}
//use these methods to initialize the FSM
void SetCurrentState(State<entity_type>* s){m_pCurrentState = s;}
void SetGlobalState(State<entity_type>* s) {m_pGlobalState = s;}
void SetPreviousState(State<entity_type>* s){m_pPreviousState = s;}
//call this to update the FSM
void Update()const
{
//if a global state exists, call its execute method
if (m_pGlobalState) m_pGlobalState->Execute(m_pOwner);
//same for the current state
if (m_pCurrentState) m_pCurrentState->Execute(m_pOwner);
}
//change to a new state
void ChangeState(State<entity_type>* pNewState)
{
assert(pNewState &&
"<StateMachine::ChangeState>: trying to change to a null state");
64 | Chapter 2
Creating a State Machine Class
//keep a record of the previous state
m_pPreviousState = m_pCurrentState;
//call the exit method of the existing state
m_pCurrentState->Exit(m_pOwner);
//change state to the new state
m_pCurrentState = pNewState;
//call the entry method of the new state
m_pCurrentState->Enter(m_pOwner);
}
//change state back to the previous state
void RevertToPreviousState()
{
ChangeState(m_pPreviousState);
}
//accessors
State<entity_type>* CurrentState() const{return m_pCurrentState;}
State<entity_type>* GlobalState() const{return m_pGlobalState;}
State<entity_type>* PreviousState() const{return m_pPreviousState;}
//returns true if the current state’s type is equal to the type of the
//class passed as a parameter.
bool isInState(const State<entity_type>& st)const;
};
Now all an agent has to do is to own an instance of a StateMachine and
implement a method to update the state machine to get full FSM
functionality.
State-Driven Agent Design | 65
Creating a State Machine Class
The improved Miner class now looks like this:
class Miner : public BaseGameEntity
{
private:
//an instance of the state machine class
StateMachine<Miner>* m_pStateMachine;
/* EXTRANEOUS DETAIL OMITTED */
public:
Miner(int id):m_Location(shack),
m_iGoldCarried(0),
m_iMoneyInBank(0),
m_iThirst(0),
m_iFatigue(0),
BaseGameEntity(id)
{
//set up state machine
m_pStateMachine = new StateMachine<Miner>(this);
m_pStateMachine->SetCurrentState(GoHomeAndSleepTilRested::Instance());
m_pStateMachine->SetGlobalState(MinerGlobalState::Instance());
}
~Miner(){delete m_pStateMachine;}
void Update()
{
++m_iThirst;
m_pStateMachine->Update();
}
StateMachine<Miner>* GetFSM()const{return m_pStateMachine;}
/* EXTRANEOUS DETAIL OMITTED */
};
Notice how the current and global states must be set explicitly when a
StateMachine is instantiated.
The class hierarchy is now like that shown in Figure 2.4.
66 | Chapter 2
Creating a State Machine Class

Get Programming Game AI by Example now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.