更新:
再次感谢你的例子,它们对我很有帮助,我并不是说
夺走他们的一切。
Aren't the currently given examples, as far as I understand them & state-machines, only half of what we usually understand by a state-machine?
In the sense that the examples do change state but that's only represented by changing the value of a variable (and allowing different value- changes in different states), while usually, a state machine should also change its behavior, and behavior not (only) in the sense of allowing different value changes for a variable depending on the state, but in the sense of allowing different methods to be executed for different states.
还是我对状态机及其常用用法有误解?
最初的问题:
我发现了关于c#中的状态机和迭代器块的讨论,以及用于创建状态机和c#的工具,所以我发现了很多抽象的东西,但作为一个新手,所有这些都有点令人困惑。
因此,如果有人能提供一个c#源代码-示例,实现一个简单的状态机,可能只有3,4个状态,那就太好了,只是为了了解它的要点。
您可以编写一个迭代器块,使您能够以编排的方式执行代码块。代码块是如何分解的并不一定要对应于任何东西,这只是你想要如何编码它。例如:
IEnumerable<int> CountToTen()
{
System.Console.WriteLine("1");
yield return 0;
System.Console.WriteLine("2");
System.Console.WriteLine("3");
System.Console.WriteLine("4");
yield return 0;
System.Console.WriteLine("5");
System.Console.WriteLine("6");
System.Console.WriteLine("7");
yield return 0;
System.Console.WriteLine("8");
yield return 0;
System.Console.WriteLine("9");
System.Console.WriteLine("10");
}
在本例中,当调用CountToTen时,还没有实际执行任何东西。您得到的实际上是一个状态机生成器,您可以为它创建一个状态机的新实例。可以通过调用GetEnumerator()来实现。生成的IEnumerator实际上是一个状态机,您可以通过调用MoveNext(…)来驱动它。
因此,在本例中,第一次调用MoveNext(…)时,您将看到“1”写入控制台,下一次调用MoveNext(…)时,您将看到2、3、4,然后是5、6、7、8,然后是9、10。正如您所看到的,这是一种编排事情应该如何发生的有用机制。
让我们从这个简单的状态图开始:
我们有:
4种状态(非活动、活动、暂停和退出)
5种状态转换(开始命令,结束命令,暂停命令,恢复命令,退出命令)。
您可以通过几种方式将其转换为c#,例如在当前状态和命令上执行switch语句,或者在转换表中查找转换。对于这个简单的状态机,我更喜欢一个转换表,它很容易使用Dictionary表示:
using System;
using System.Collections.Generic;
namespace Juliet
{
public enum ProcessState
{
Inactive,
Active,
Paused,
Terminated
}
public enum Command
{
Begin,
End,
Pause,
Resume,
Exit
}
public class Process
{
class StateTransition
{
readonly ProcessState CurrentState;
readonly Command Command;
public StateTransition(ProcessState currentState, Command command)
{
CurrentState = currentState;
Command = command;
}
public override int GetHashCode()
{
return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
}
public override bool Equals(object obj)
{
StateTransition other = obj as StateTransition;
return other != null && this.CurrentState == other.CurrentState && this.Command == other.Command;
}
}
Dictionary<StateTransition, ProcessState> transitions;
public ProcessState CurrentState { get; private set; }
public Process()
{
CurrentState = ProcessState.Inactive;
transitions = new Dictionary<StateTransition, ProcessState>
{
{ new StateTransition(ProcessState.Inactive, Command.Exit), ProcessState.Terminated },
{ new StateTransition(ProcessState.Inactive, Command.Begin), ProcessState.Active },
{ new StateTransition(ProcessState.Active, Command.End), ProcessState.Inactive },
{ new StateTransition(ProcessState.Active, Command.Pause), ProcessState.Paused },
{ new StateTransition(ProcessState.Paused, Command.End), ProcessState.Inactive },
{ new StateTransition(ProcessState.Paused, Command.Resume), ProcessState.Active }
};
}
public ProcessState GetNext(Command command)
{
StateTransition transition = new StateTransition(CurrentState, command);
ProcessState nextState;
if (!transitions.TryGetValue(transition, out nextState))
throw new Exception("Invalid transition: " + CurrentState + " -> " + command);
return nextState;
}
public ProcessState MoveNext(Command command)
{
CurrentState = GetNext(command);
return CurrentState;
}
}
public class Program
{
static void Main(string[] args)
{
Process p = new Process();
Console.WriteLine("Current State = " + p.CurrentState);
Console.WriteLine("Command.Begin: Current State = " + p.MoveNext(Command.Begin));
Console.WriteLine("Command.Pause: Current State = " + p.MoveNext(Command.Pause));
Console.WriteLine("Command.End: Current State = " + p.MoveNext(Command.End));
Console.WriteLine("Command.Exit: Current State = " + p.MoveNext(Command.Exit));
Console.ReadLine();
}
}
}
根据个人喜好,我喜欢用GetNext函数来确定地返回下一个状态,用MoveNext函数来改变状态机。
列表的另一个状态机是我的:https://github.com/IanMercer/Abodit.StateMachine
除了具有进入和退出操作的简单状态,以及每个转换上的操作之外,这个是为在异步代码中使用而设计的。它还支持分层状态和复合状态机。所以不是很“简单”,但在使用中,它很容易编码状态和过渡。
static OpenClosedStateMachine()
{
Closed
.When(Fridge.eDoorOpens, (m, s, e, c) => Task.FromResult(Open));
Open
.When(Fridge.eDoorCloses, (m, s, e, c) => Task.FromResult(Closed));
}
不像其他的,它还支持时间转换,所以很容易过渡到不同的状态后,一个给定的时期或在给定的时间。
在网上找到了这个很棒的教程,它帮助我理解了有限状态机。
http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867
本教程是语言无关的,所以它可以很容易地适应您的c#需求。
而且,所使用的例子(一只蚂蚁寻找食物)很容易理解。
来自教程:
public class FSM {
private var activeState :Function; // points to the currently active state function
public function FSM() {
}
public function setState(state :Function) :void {
activeState = state;
}
public function update() :void {
if (activeState != null) {
activeState();
}
}
}
public class Ant
{
public var position :Vector3D;
public var velocity :Vector3D;
public var brain :FSM;
public function Ant(posX :Number, posY :Number) {
position = new Vector3D(posX, posY);
velocity = new Vector3D( -1, -1);
brain = new FSM();
// Tell the brain to start looking for the leaf.
brain.setState(findLeaf);
}
/**
* The "findLeaf" state.
* It makes the ant move towards the leaf.
*/
public function findLeaf() :void {
// Move the ant towards the leaf.
velocity = new Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y);
if (distance(Game.instance.leaf, this) <= 10) {
// The ant is extremelly close to the leaf, it's time
// to go home.
brain.setState(goHome);
}
if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
// Mouse cursor is threatening us. Let's run away!
// It will make the brain start calling runAway() from
// now on.
brain.setState(runAway);
}
}
/**
* The "goHome" state.
* It makes the ant move towards its home.
*/
public function goHome() :void {
// Move the ant towards home
velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y);
if (distance(Game.instance.home, this) <= 10) {
// The ant is home, let's find the leaf again.
brain.setState(findLeaf);
}
}
/**
* The "runAway" state.
* It makes the ant run away from the mouse cursor.
*/
public function runAway() :void {
// Move the ant away from the mouse cursor
velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y);
// Is the mouse cursor still close?
if (distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) {
// No, the mouse cursor has gone away. Let's go back looking for the leaf.
brain.setState(findLeaf);
}
}
public function update():void {
// Update the FSM controlling the "brain". It will invoke the currently
// active state function: findLeaf(), goHome() or runAway().
brain.update();
// Apply the velocity vector to the position, making the ant move.
moveBasedOnVelocity();
}
(...)
}
您可以编写一个迭代器块,使您能够以编排的方式执行代码块。代码块是如何分解的并不一定要对应于任何东西,这只是你想要如何编码它。例如:
IEnumerable<int> CountToTen()
{
System.Console.WriteLine("1");
yield return 0;
System.Console.WriteLine("2");
System.Console.WriteLine("3");
System.Console.WriteLine("4");
yield return 0;
System.Console.WriteLine("5");
System.Console.WriteLine("6");
System.Console.WriteLine("7");
yield return 0;
System.Console.WriteLine("8");
yield return 0;
System.Console.WriteLine("9");
System.Console.WriteLine("10");
}
在本例中,当调用CountToTen时,还没有实际执行任何东西。您得到的实际上是一个状态机生成器,您可以为它创建一个状态机的新实例。可以通过调用GetEnumerator()来实现。生成的IEnumerator实际上是一个状态机,您可以通过调用MoveNext(…)来驱动它。
因此,在本例中,第一次调用MoveNext(…)时,您将看到“1”写入控制台,下一次调用MoveNext(…)时,您将看到2、3、4,然后是5、6、7、8,然后是9、10。正如您所看到的,这是一种编排事情应该如何发生的有用机制。