Simple State MAChine Example in C#

State-machine - Stateless vs. traditional if-else code, hard to grasp the benefit

One benefit of using a state machine is that you reduce the number of states an object can be in. I worked with someone who had 22 bool flags in a single class. There was a lot of if !(something && !somethingElse || !userClicked) …

This sort of code is hard to read, hard to debug, hard to unit test and it's more or less impossible to reason about what the state of the class really is. 22 bool flags means that the class can be in over 4 million states. Try making unit tests for that...

State machines can reduce the complexity of code, but it will almost always make the somewhat more complex at the beginning of a new project. However, in the long term I've found that the overall complexity ends up being overall lower. This is because it's easy to extend, and add more states, since the already defined states can be left alone.

What I've found over the years is that OOP and state machines are often two aspects of the same. And I've also found that OOP is hard, and difficult to get 'right'.

I think the state machine should not be visible to the outside of an object, including its triggers. You most likely want to have a public readonly state property.

I design the classes in such a way that the caller can not directly change the state, or let the caller call Fire method directly. Instead I use methods that are verbs that are actions, like Validate().

Your work flow needs conditionals, but you have some freedom of where to put them. I would suggest separating the business logic from the state machine configuration. I think this makes the state machine easier to read.

How about something like this:

namespace ConsoleApp1
{
using Stateless;
using System;

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press Q to stop validating events");
ConsoleKeyInfo c;

do
{
var mpe = new MarketPriceEvent();
mpe.Validate();
c = Console.ReadKey();

} while (c.Key != ConsoleKey.Q);
}
}

public class MarketPriceEvent
{
public void Validate()
{
_machine.Fire(Trigger.Validate);
}

public enum State { Validate, Compare2, ErrorAuditing, Compare1, Storing }
private enum Trigger { Validate, CompareOneOk, CompareTwoOk, Error, }

private readonly StateMachine<State, Trigger> _machine;
public MarketPriceEvent()
{
_machine = new StateMachine<State, Trigger>(State.Validate);

_machine.Configure(State.Validate)
.Permit(Trigger.Validate, State.Compare1);

_machine.Configure(State.Compare1)
.OnEntry(DoEventValidation)
.Permit(Trigger.CompareOneOk, State.Compare2)
.Permit(Trigger.Error, State.ErrorAuditing);

_machine.Configure(State.Compare2)
.OnEntry(DoEventValidationAgainstResource2)
.Permit(Trigger.CompareTwoOk, State.Storing)
.Permit(Trigger.Error, State.ErrorAuditing);

_machine.Configure(State.Storing)
.OnEntry(HandleStoring);

_machine.Configure(State.ErrorAuditing)
.OnEntry(HandleError);
}

private void DoEventValidation()
{
// Business logic goes here
if (isValid())
_machine.Fire(Trigger.CompareOneOk);
else
_machine.Fire(Trigger.Error);
}

private void DoEventValidationAgainstResource2()
{
// Business logic goes here
if (isValid())
_machine.Fire(Trigger.CompareTwoOk);
else
_machine.Fire(Trigger.Error);
}
private bool isValid()
{
// Returns false every five seconds...
return (DateTime.UtcNow.Second % 5) != 0;
}

private void HandleStoring()
{
Console.WriteLine("Awesome, validation OK!");
}

private void HandleError()
{
Console.WriteLine("Oh noes, validation failed!");
}

}
}

How do I use this state machine with my movement code?

In TestMovement, do you have a Start or Awake method which assigns something to the playerState field? If not, that field is never assigned and is therefore always null.

Another thing you could do is make it public PlayerStateManager playerState; and assign something to it in the editor.

How To Refactor Switch Statement To Flexible Finite State Machine

If you have multiple states and it is necessary to change behaviour based on state, then we can use strategy pattern combined with factory pattern.

At first, we need to declare states:

public enum State
{
State_1,
State_2,
State_3,
State_4
}

Then based on state we need to choose some behaviour. We can put behaviour in class. Let's call it product. So we need to create some abstract class and put all duplicated logic in this abstract class. Then if behaviour of some classes is different or we want to add new behavior, then we will create derived classes. By adding new classes we keep to Open Closed principle.

public abstract class Product
{
public string Name { get; set; }

public decimal Price { get; set; }

public string TheSameBehaviorForAllProducts()
{
return "TheSameHehaviorForAllProducts";
}

public virtual string BehaviorThatCanBeOverridenInDerivedClasses()
{
return "TheSameHehaviorForAllProducts";
}
}

and derived classes of Product:

public class Product_A : Product
{
public override string BehaviorThatCanBeOverridenInDerivedClasses()
{
return "New Behavior Here";
}
}

public class Product_B : Product
{
public override string BehaviorThatCanBeOverridenInDerivedClasses()
{
return "New Behavior Here";
}
}

public class Product_C : Product
{
public override string BehaviorThatCanBeOverridenInDerivedClasses()
{
return "New Behavior Here";
}
}

Then we can create a something like mapper which maps State to Product. It is also can be considered as Factory pattern:

public class StateToProduct 
{
public Dictionary<State, Product> ProductByState = new()
{
{ State.A, new Product_A() },
{ State.B, new Product_B() },
{ State.C, new Product_C() }
};
}

and our state machine:

public class StateMachine
{
// Here your logic to get state
public State GetState()
{
return State.A;
}
}

Then we can run big system like this:

public class SomeBigSystem
{
public void Run()
{
StateMachine stateMachine = new();
StateToProduct stateToProduct = new();
Product product = stateToProduct.ProductByState[stateMachine.GetState()];

// executing some business logic
product.BehaviorThatCanBeOverridenInDerivedClasses();
}
}

So we've created simple classes that are testable and we used here Strategy pattern.

I highly recommend you to read about SOLID and techniques of refactoring

Basic State Machine setup using Stateless


The machine is in only one state at a time; the state it is in at any
given time is called the current state. It can change from one state
to another when initiated by a triggering event or condition, this is
called a transition.
from Finite-state machine on Wiki

I believe, the trigger is this triggering event.

Update:

Of course trigger name sometimes can be equal to some of state names.

New (initial state)
New -> Filled (trigger "Filled")
New -> Cancelled (trigger "Cancelled")
Filled -> Shipping (trigger "ToBeShipped")
Filled -> Cancelled (trigger "Cancelled")
Shipping -> Complete (trigger "Completed").

Update:

stateless is really nice framework!
I've tried to implemented the functionality.

States:

public enum State
{
New,
Filled,
Shipping,
Cancelled,
Completed
}

Triggers:

public enum Trigger
{
Filled,
Cancelled,
ToBeShipped,
Completed
}

Order class:

public class Order
{
private readonly StateMachine<State, Trigger> _stateMachine;

public Order()
{
_stateMachine = CreateStateMachine();
}

public bool TryUpdateOrderStatus(Trigger trigger)
{
if (!_stateMachine.CanFire(trigger))
return false;

_stateMachine.Fire(trigger);
return true;
}

public State Status
{
get
{
return _stateMachine.State;
}
}

private StateMachine<State, Trigger> CreateStateMachine()
{
StateMachine<State, Trigger> stateMachine = new StateMachine<State, Trigger>(State.New);
stateMachine.Configure(State.New)
.Permit(Trigger.Filled, State.Filled)
.Permit(Trigger.Cancelled, State.Cancelled);

stateMachine.Configure(State.Filled)
.Permit(Trigger.ToBeShipped, State.Shipping)
.Permit(Trigger.Cancelled, State.Cancelled);

stateMachine.Configure(State.Shipping)
.Permit(Trigger.Completed, State.Completed);

stateMachine.OnUnhandledTrigger((state, trigger) =>
{
Console.WriteLine("Unhandled: '{0}' state, '{1}' trigger!");
});
return stateMachine;
}
}

Tester for Order class:

Order order = new Order();
bool result = order.TryUpdateOrderStatus(Trigger.Completed);
Console.WriteLine("Attemp to complete order: {0}", result);
Console.WriteLine("Order status: {0}", order.Status);

result = order.TryUpdateOrderStatus(Trigger.ToBeShipped);
Console.WriteLine("Attemp to ship order: {0}", result);
Console.WriteLine("Order status: {0}", order.Status);

result = order.TryUpdateOrderStatus(Trigger.Cancelled);
Console.WriteLine("Attemp to cancel order: {0}", result);
Console.WriteLine("Order status: {0}", order.Status);

State Pattern in c#

Usually a game is run in a game-loop. A very simple one could look like this:

  1. Read input
  2. Process input (move character, advance enemies etc)
  3. Draw the result

This loop would typically at a more or less fixed rate. this is in contrast to most windows programs that only run when the user has provided some kind of input, or the program has some timer that has triggered.

So the active state would receive an update call every frame, and it has then a chance to decide what to do. The state would typically check the game-state and user input, from this it can then decide what to do.

Instead of reading the input in a separate thread you can use Console.KeyAvailable to check if a key is available, and if so, ReadKey should not block.

The way I'm used to program state machines is that the current state is called with some input parameters, and returns the next state to use. There are a bunch of details that can be taken into consideration:

  • Should you process all input? only the last input each frame? Only if the key is pressed when the frame starts?
  • Should it be possible to switch states more than once per frame? I.e. "Jumping" may transition to "Idle", but if the user is holding the shift-key then it could immediately transition to "crouching".
  • It is common to separate "state" and "transition". This helps making the state machine more abstract. I.e. Instead of having hard coding that the hero should jump on space in each state, you can have a OnKeyTransition that takes a input key and a target state as input. You can then add this transition to all states that should support jumping.
  • There can be many different kinds of state machines in a game. One high level might be if the player is driving a car, on foot, piloting an airplane etc. A lower level state machine might handle animations. A third kind could be used for AI. All of these have different kinds of requirements, so need to be treated differently.

This would be an example of a very simple state machine. I'm not sure that it is exactly what you are looking for, but I hope it will provide some inspiration at least.

public class State
{
public virtual State Update(ConsoleKey? input, TimeSpan deltaTime) => this;
}

public class IdleState : State
{
public override State Update(ConsoleKey? input, TimeSpan deltaTime)
{
if (input == ConsoleKey.Spacebar)
{
return new JumpingState();
}
return base.Update(input, deltaTime);
}
}

public class JumpingState : State
{
private TimeSpan elapsed = TimeSpan.Zero;

public override State Update(ConsoleKey? input, TimeSpan deltaTime)
{
elapsed += deltaTime;
if (elapsed > TimeSpan.FromSeconds(1))
{
return new IdleState();
}
return base.Update(input, deltaTime);
}
}

public class Game
{
static void Main(string[] args)
{
var game = new Game();
game.StartGame();

}

State currentState = new IdleState();
private TimeSpan frameRate = TimeSpan.FromMilliseconds(30);

public void StartGame()
{
Console.WriteLine("Game Started");
while (true)
{

var input = GetLastKeypress()?.Key;
if (input == ConsoleKey.Escape)
{
Console.WriteLine("Game Over");
return;
}

// Update the state
var nextState = currentState.Update(input, frameRate);
if (nextState != currentState)
{
currentState = nextState;
Console.WriteLine(currentState.GetType().Name);
}
Thread.Sleep(frameRate);
}
}

private ConsoleKeyInfo? GetLastKeypress()
{
ConsoleKeyInfo? info = null;
while (Console.KeyAvailable)
{
info = Console.ReadKey();
}
return info;
}
}


Related Topics



Leave a reply



Submit