Finite State Machines and Various Implementations
Finite State Machine
A finite state machine (FSM) has a bunch of states that it can be in, and while in a certain state, it transitions to other states based on inputs (events/actions) received while in that state.
graph LR;
State1 -->|input| State2;
State2 -->|input| State3;
State2 -->|input2| State4;
In Software
In software, certain things can be modeled as FSMs, and when done so, makes their implementation/maintenance/extensibility significantly easier.
Examples include:
- User interface navigation (e.g. different screens/states)
- Game character behavior (e.g. idle, walking, jumping)
- Protocols (e.g. TCP connection states)
GoF State Pattern
The Gang of Four (GoF) State Pattern is one implementation of a FSM in software.
First, identify (methods) that depend on state. This is usually input/event handlers, and process()
/update()
type of methods. Some examples:
on_key_pressed()
on_mouse_clicked()
update(dt)
on_message_received()
Then create an interface with these methods.
from abc import ABC, abstractmethod
class HeroState(ABC):
@abstractmethod
def on_key_pressed(self):
pass
@abstractmethod
def update(self, dt):
pass
Then, have the main object hold a reference to its current state. It should delegate state-dependent methods to its current state.
class Hero:
def __init__(self):
self._state = None
def on_key_pressed(self):
self._state.on_key_pressed()
def update(self, dt):
self._state.update(dt)
Generally, you want each State to have an enter()
and exit()
method to initialize/cleanup when that state is entered or exited.
You also usually want the Context class (Hero) to have a set_state()
method. When a particular State, wants to change the state of the Context, it can call set_state()
on the context. Obviously, this means each State needs a reference to the Context.
class HeroState(ABC):
def __init__(self, context: Hero):
self._context = context
@abstractmethod
def enter(self):
pass
@abstractmethod
def exit(self):
pass
@abstractmethod
def on_key_pressed(self):
pass
@abstractmethod
def update(self, dt):
pass
class Hero:
def __init__(self):
self._state = None
def set_state(self, state: HeroState):
if self._state:
self._state.exit()
self._state = state
self._state.enter()
def on_key_pressed(self):
self._state.on_key_pressed()
def update(self, dt):
self._state.update(dt)
In GoF State Pattern, you encapsulate the data/behavior of each state in its own class.
Further Reading
This article was based on game programming patterns - state