Open main menu

CDOT Wiki β

State

Revision as of 22:39, 12 April 2007 by Rmwang (talk | contribs) (References)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Description

What is the State Pattern?

  • Allow to sub-divide the behavior of an object depending on its current state.
  • Classified as a behavior pattern.
  • Also known as Objects for States.

When do you use the State Pattern?

Suppose that you are playing War Craft. Your units will behave according to your commands. For example, if the "Attack" command is given to your units, the state of the units will be changed to "Attack State" and they will attack the enemies around them. If you set them to hold their position, their state will be changed to "Hold Position" and your units will stop attacking and stand around. These behaviors such as "attack" behavior and "hold position" behavior can be also applied to other species – there are five species in this game; undeads, orcs, night elves, humans and corrupted night elves. By the use of the state pattern, it is possible to save time and gain reusability and maintainability since you do not have to code the similar unit behaviors all over again for other species when programming such game.

Advantages of using State Pattern

1. State pattern allows object to perform state-specific behaviors and operations and partitions behavior based on its state.

State pattern is beneficial since the object's state-specific behaviors are maintained in a single object. It is much easier to create and add new states and transitions than extending behaviors using if or switch statements. New state implements the state interface, and new state can extend other states.

2. State pattern makes state transition explicit.

If the state is defined with an internal data values, its state transition do not have explicit representation. If a variable is used to specify its current state, it will be difficult to extend and maintain since the states and transitions are handled using many if and switch statements.

3. State objects can be shared by other classes.

As mentioned above, state object can be used by other classes. For example, all five species of the War Craft have common behaviors such as "Attack" and "Hold Position" behaviors, and those state-specified behaviors can be used by all character classes. Click the link below to see the examples.

UML Diagram

State Pattern UML Diagram

 

Code Samples

Greeting Message Generator

Click here to see the UML diagram for this example

State class interface definition

class CBaseState
{
public:
    virtual CBaseState* GetNextState() = 0;
    virtual char* ToString() = 0;
};

Concrete class definitions


class CMorning : public CBaseState{
public:
    virtual CBaseState* GetNextState();
    virtual char* ToString();
};
class CEvening : public CBaseState{
public:
    virtual CBaseState* GetNextState();
    virtual char* ToString();
};

class CNight: public CBaseState{
public:
    virtual CBaseState* GetNextState();
    virtual char* ToString();
};

Context class structure

class CSun{
public:
    CSun();
    CSun(CBaseState* pContext);
    ~CSun();
    void StateChanged();
    char* GetStateName();
protected:
    void DoCleanUp();
    CBaseState* m_pState;
};

Function definition for state change

void CSun::StateChanged(){
    if (m_pState){
        CBaseState* pState = m_pState->GetNextState();
        delete m_pState;
        m_pState = pState;
    }
}

Code that changes state

CBaseState* CMorning::GetNextState(){return new CEvening;}
CBaseState* CEvening::GetNextState(){return new CNight;}
CBaseState* CNight::GetNextState(){return new CMorning;}

Simple example of actual implementation

CSun objSun(new CMorning);
printf("\n\nSun Says Good %s !!!",objSun.GetStateName());
objSun.StateChanged();
printf("\n\nSun Says Good %s !!!",objSun.GetStateName());
objSun.StateChanged();
printf("\n\nSun Says Good %s !!!",objSun.GetStateName());
objSun.StateChanged();	
printf("\n\nSun Says Good %s !!!",objSun.GetStateName());

Game programming: Character Status

Click here to see the UML diagram for this example


Header File Content

class IState;
class cDarkElf  : public cUnit { 
	IState* _pState; 
	void ChangeState(IState*); 
public: 
	cDarkElf(); 
	~cDarkElf();  
	void Attack(cUnit* Target); 
	void Move(int x, int y); 
	void Stop(); 
	void HoldPosition(); 
	void Slaughter(cUnit* Target); 
	void Update(); 
}; 

Class Definitions for State objects

class IState {  
	const ID_STATE _ID; 
public: 
	enum ID_STATE {STATE_ATTACK, STATE_MOVE, STATE_STOP, 
                       STATE_HOLDPOSITION, STATE_SLAUGHTER}; 
	IState(ID_STATE state) : _ID(state) {} 
	ID_STATE GetID() {return _ID;} 
	virtual void Operate() = 0; 
}; 

class cState_Attack : public IState { 
	cUnit* _pTarget; 
	cUnit* _pUnit; 
public: 
	cState_Attack(cUnit* target, cUnit* pUnit) : 
        IState(STATE_ATTACK), _pTarget(target), _pUnit(pUnit){} 
	void Operate();
}; 

class cState_Move : public IState { 
	int _x, _y; 
	cUnit* _pUnit; 
public: 
	cState_Move(int x, int y, cUnit* pUnit); 
	void Operate(); 
}; 

class cState_Stop : public IState { 
public: 
	void Operate(); 
}; 

class cState_HoldPosition : public IState { 
public: 
	void Operate(); 
}; 

class cState_SLAUGHTER : public IState { 
	cUnit* _pTarget; 
	cUnit* _pUnit; 
public: 
	cState_Slaughter(cUnit* target, cUnit* pUnit) : 
        IState(STATE_SLAUGHTER), _pTarget(target), 
        _pUnit(pUnit) {}
	void Operate(); 
}; 

Functions that changes character status

cDarkElf::cDarkElf() {_pState = new cState_Stop;} 

cDarkElf::~cDarkElf() {delete _pState;} 

void cDarkElf::ChangeState(IState* pState) { 
	delete _pState; 
	_pState = pState; 
}
 
void cDarkElf::Attack(cUnit* Target) {
        ChangeState(new cState_Attack(Target, this));
}
 
void cDarkElf::Move(int x, int y) {
        ChangeState(new cState_Move(x, y, this));
}
 
void cDarkElf::Stop() {
        ChangeState(new cState_Stop);
} 

void cDarkElf::HoldPosition() {
        ChangeState(new cState_HoldPosition);
} 

void cDarkElf::Patrol(int x1, int y1, int x2, int y2) {
        ChangeState(new cState_Patrol(x1, y1, x2, y2));
} 

void cDarkElf::Update() {
        _pState->Operate();
} 

State Pattern Example: C#

Abstract Class

using System;
using System.Drawing;

namespace StatePatternApp
{
    public abstract class State
    {
        public State()
        {
        }
        public State(State state)
        {
            this.CurrentLevel    = state.CurrentLevel;
            this.Deviation        = state.Deviation;
            this.MaxLevel        = state.MaxLevel;
            this.MinLevel        = state.MinLevel;
            this.Machine        = state.Machine;
            this.SetParams();
        }
        public State(Machine machine, int iCurrentLevel, 
                int iDeviation, int iMaxLevel, int iMinLevel)
        {
            this.CurrentLevel    = iCurrentLevel;
            this.Deviation        = iDeviation;
            this.MaxLevel        = iMaxLevel;
            this.MinLevel        = iMinLevel;
            this.Machine        = machine;
            this.SetParams();
        }
        private Machine machine;
        private int minLevel = 0;
        private int maxLevel = 0;
        private int currentLevel = 0;
        private int deviation = 0;
        private Color messageColor = Color.Green;
        private string messageText = "";
        public virtual void SetParams(){}
        
        public virtual void CheckEfficiency()
        {
            Transition t = Transition.GetInstance();
            t.Transform(this);
        }

        protected virtual void ChangeState(Machine m, State s)
        {
            m.ChangeState(s);
        }

        public Machine Machine
        {
            get
            {
                return this.machine;
            }
            set
            {
                this.machine = value;
            }
        }
        
        public int MinLevel
        {
            get
            {
                return this.minLevel;
            }
            set
            {
                this.minLevel = value;
            }
        }

        public int MaxLevel
        {
            get
            {
                return this.maxLevel;
            }
            set
            {
                this.maxLevel = value;
            }
        }

        public int CurrentLevel
        {
            get
            {
                return this.currentLevel;
            }
            set
            {
                this.currentLevel = value;
                // Is the machine value set?
                if(this.Machine != null)
                {
                    this.CheckEfficiency();
                }
            }
        }

        public int Deviation
        {
            get
            {
                return this.deviation;
            }
            set
            {
                this.deviation = value;
            }
        }

        public int MaxDeviaton
        {
            get
            {
                return this.MaxLevel - this.Deviation;
            }
        }

        public int MinDeviaton
        {
            get
            {
                return this.MinLevel + this.Deviation;
            }
        }

        public Color MessageColor
        {
            get
            {
                return this.messageColor;
            }
            set
            {
                this.messageColor = value;
            }
        }

        public string MessageText
        {
            get
            {
                return this.messageText;
            }
            set
            {
                this.messageText = value;
            }
        }
    }
}

Transition class

using System;

namespace StatePatternApp
{
    class Transition
    {
        private static Transition getInstance;
        protected Transition() {}

        
        public static Transition GetInstance()
        {
            if(getInstance == null)
            {
                getInstance = new Transition();
            }
            return getInstance;
        }

        public void Transform(State state)
        {
            if(state == null)
            {
                return;
            }

            // Get the type of state.
            string stateType = state.GetType().Name;

            // Are we in normal state?
            if(state.CurrentLevel < state.MaxDeviaton && 
                     state.CurrentLevel > state.MinDeviaton)
            {
                if(stateType.ToUpper() != "NORMALSTATE")
                {
                    state.ChangeState(state.Machine, 
                                 new NormalState(state));
                }
            }
            // Are we in warning?
            if(state.Deviation > 0)
            {
                if((state.CurrentLevel < state.MaxLevel && 
                      state.CurrentLevel >= state.MaxDeviaton) || 
                    state.CurrentLevel > state.MinLevel && 
                      state.CurrentLevel <= state.MinDeviaton)
                {
                    if(stateType.ToUpper() != "WARNINGSTATE")
                    {
                        state.ChangeState(state.Machine, 
                                     new WarningState(state));
                    }
                }
            }
            // Are we in alert state?
            if(state.CurrentLevel >= state.MaxLevel || 
                        state.CurrentLevel <= state.MinLevel)
            {
                if(stateType.ToUpper() != "ALERTSTATE")
                {
                    state.ChangeState(state.Machine, 
                                   new AlertState(state));
                }
            }
        }
    }
}

NormalState - derived state class

using System;
using System.Drawing;

namespace StatePatternApp
{
    public class NormalState : State
    {
        public NormalState(State state) : base(state)
        {
        }

        public NormalState(Machine machine, int iCurrentLevel, 
                   int iDeviation, int iMaxLevel, int iMinLevel) :
                   base(machine, iCurrentLevel, iDeviation, 
                   iMaxLevel, iMinLevel)
        {    
        }

        public override void SetParams()
        {
            this.MessageColor    = Color.Green;
            this.MessageText    = "Normal";
        }
    }
}

WarningState - derived state class

using System;
using System.Drawing;

namespace StatePatternApp
{
    public class WarningState : State
    {
        public WarningState(State state) : base(state)
        {
        }

        public WarningState(Machine machine, int iCurrentLevel, 
            int iDeviation, int iMaxLevel, int iMinLevel) :
            base(machine, iCurrentLevel, iDeviation, iMaxLevel, 
            iMinLevel)
        {
        }

        public override void SetParams()
        {
            this.MessageColor    = Color.Yellow;
            this.MessageText    = "Warning";
        }
    }
}

AlertState - derived state class

using System;
using System.Drawing;

namespace StatePatternApp
{
    public class AlertState : State
    {
        public AlertState(State state) : base(state)
        {
        }

        public AlertState(Machine machine, int iCurrentLevel, 
            int iDeviation, int iMaxLevel, int iMinLevel) :
            base(machine, iCurrentLevel, iDeviation, iMaxLevel, 
            iMinLevel)
        {
        }

        public override void SetParams()
        {
            this.MessageColor    = Color.Red;
            this.MessageText    = "Alert";
        }
    }
}

Machine class - This class maintains an instance of state

using System;

namespace StatePatternApp
{
    public class Machine
    {
        public State currentState;

        public Machine(int iCurrentLevel, int iDeviation, 
                               int iMaxLevel, int iMinLevel)
        {
            currentState = new NormalState(this, iCurrentLevel, 
                               iDeviation, iMaxLevel, iMinLevel);
            currentState.CheckEfficiency();
        }

        public void ChangeState(State setState)
        {
            currentState = setState;
        }

        public void SetCurrentLevel(int level)
        {
            currentState.CurrentLevel = level;
        }
    }
}

References