原创 C语言有限状态机的实现

2010-3-26 21:27 4963 14 15 分类: MCU/ 嵌入式
有限状态机的实现

有限状态机(Finite State Machine或者Finite State Automata)是软件领域中一种重要的工具,很多东西的模型实际上就是有限状态机。


最近看了一些游戏编程AI的材料,感觉游戏中的AI,第一要说的就是有限状态机来实现精灵的AI,然后才是A*寻路,其他学术界讨论比较多的神经网络、模糊控制等问


题还不是很热。


FSM的实现方式:
1) switch/case或者if/else
这无意是最直观的方式,使用一堆条件判断,会编程的人都可以做到,对简单小巧的状态机来说最合适,但是毫无疑问,这样的方式比较原始,对庞大的状态机难以维


护。


2) 状态表
维护一个二维状态表,横坐标表示当前状态,纵坐标表示输入,表中一个元素存储下一个状态和对应的操作。这一招易于维护,但是运行时间和存储空间的代价较大。


3) 使用State Pattern
使用State Pattern使得代码的维护比switch/case方式稍好,性能上也不会有很多的影响,但是也不是100%完美。不过Robert C. Martin做了两个自动产生FSM代码的


工具,for java和for C++各一个,在http://www.objectmentor.com/resources/index上有免费下载,这个工具的输入是纯文本的状态机描述,自动产生符合State


Pattern的代码,这样developer的工作只需要维护状态机的文本描述,每必要冒引入bug的风险去维护code。


4) 使用宏定义描述状态机
一般来说,C++编程中应该避免使用#define,但是这主要是因为如果用宏来定义函数的话,很容易产生这样那样的问题,但是巧妙的使用,还是能够产生奇妙的效果。


MFC就是使用宏定义来实现大的架构的。
在实现FSM的时候,可以把一些繁琐无比的if/else还有花括号的组合放在宏中,这样,在代码中可以3)中状态机描述文本一样写,通过编译器的预编译处理产生1)一


样的效果,我见过产生C代码的宏,如果要产生C++代码,己软MFC可以,那么理论上也是可行的。


【评】:状态表的实现方法,《C专家编程》第8章有具体说明,转载【6】


☆──────────────────────传说中的分隔符──────────────────────────────☆


来源2:http://hi.baidu.com/juneshine/blog/item/6bff718bd5902f13c9fc7a14.html


【转载2】有限状态机的c实现


2007-05-11 15:12


網絡上可以搜索到很多有限狀態機的代碼和理論分析,這兒僅僅是做一個簡單的例子,僅供入門參考。


这儿以四位密码校验作为状态机的例子,连续输入2479就可以通过密码测试。一个非常简单的例子,在实际的状态机实例中,状态转移表要更復雜一些,不過方式非常


類似。在狀態查詢的地方可以做優化,同時對于輸入量也可以做有效性優化。具體代碼如下:



view plaincopy to clipboardprint?
c.h  
 
typedef enum{  
STATE1 = 1,  
STATE2,  
STATE3,  
STATE4,  
STATE5,//password pass  
//...ADD here  
}STATE;  
 
typedef enum{  
INPUT1 = '2',  
INPUT2 = '4',  
INPUT3 = '7',  
INPUT4 = '9',  
}INPUT;  
 
typedef struct 
{  
STATE cur_state;  
INPUT input;  
STATE next_state;  
}STATE_TRANS; 
c.h


typedef enum{
STATE1 = 1,
STATE2,
STATE3,
STATE4,
STATE5,//password pass
//...ADD here
}STATE;


typedef enum{
INPUT1 = '2',
INPUT2 = '4',
INPUT3 = '7',
INPUT4 = '9',
}INPUT;


typedef struct
{
STATE cur_state;
INPUT input;
STATE next_state;
}STATE_TRANS;


c.c 
 
#include   <stdio.h>   
#include "c.h"  
 
STATE_TRANS state_trans_arry[] =   
{  
{STATE1,INPUT1,STATE2},  
{STATE2,INPUT2,STATE3},  
{STATE3,INPUT3,STATE4},  
{STATE4,INPUT4,STATE5},  
}; 
#define STATE_TRANS_CNT (sizeof(state_trans_arry)/sizeof(state_trans_arry[0]))  
 
int main()    
{  
int i;  
char ch;  
STATE state_machine = STATE1;  
 
while(ch != 'e')  
{  
   ch = getchar();  
   if((ch >= '0') && (ch <= '9'))//for digit password input only  
   {  
    for(i = 0;i < STATE_TRANS_CNT;i++)  
    {  
     if((ch == state_trans_arry.input) && (state_machine == state_trans_arry.cur_state))  
     {  
      state_machine = state_trans_arry.next_state;  
      continue;  
     }  
     else if(i == (STATE_TRANS_CNT - 1))//no transfer match,reset state  
     {  
      state_machine = STATE1;  
     }  
    }  
    if(state_machine == STATE5)  
     printf("Password correct,state transfer machine pass!\n");  
   }  
}  
return 0;  

c.c


#include   <stdio.h> 
#include "c.h"


STATE_TRANS state_trans_arry[] =
{
{STATE1,INPUT1,STATE2},
{STATE2,INPUT2,STATE3},
{STATE3,INPUT3,STATE4},
{STATE4,INPUT4,STATE5},
};
#define STATE_TRANS_CNT (sizeof(state_trans_arry)/sizeof(state_trans_arry[0]))


int main() 
{
int i;
char ch;
STATE state_machine = STATE1;


while(ch != 'e')
{
   ch = getchar();
   if((ch >= '0') && (ch <= '9'))//for digit password input only
   {
    for(i = 0;i < STATE_TRANS_CNT;i++)
    {
     if((ch == state_trans_arry.input) && (state_machine == state_trans_arry.cur_state))
     {
      state_machine = state_trans_arry.next_state;
    // continue;


     break;      //做了修改在vc上可以实现
     }
     else if(i == (STATE_TRANS_CNT - 1))//no transfer match,reset state
     {
      state_machine = STATE1;
     }
    }
    if(state_machine == STATE5)
     printf("Password correct,state transfer machine pass!\n");
   }
}
return 0;
}


☆──────────────────────传说中的分隔符──────────────────────────────☆


来源3:http://lionwq.spaces.eepw.com.cn/articles/article/item/16363


【转载3】有限状态机自动机


状态图--一个图的数据结构!
1.while + switch;
2.状态机:就是指定系统的所有可能的状态及状态间跳转的条件,然后设一个初始状态输入给这台机器,机器就会自动运转,或最后处于终止状态,或在某一个状态不


断循环。
游戏中状态切换是很频繁的。 可能以前要切换状态就是if~else,或者设标志,但这些都不太结构化, 如果把它严格的设为一种标准的状态机,会清楚的多。


比如控制一扇门的运动, 初始时门是关的, 当有力作用在门上时, 门开始慢慢打开,力的作用完后,门渐渐停止不动, 当有反向的力时,门又渐渐关上, 知道回


到初始关的状态。 这个你会怎么来编程实现呢。 似乎很麻烦, 的确,没有状态机的思想时会很烦,设很多标志,一堆if条件。
用状态机的话,不只是代码更清晰, 关键是更符合逻辑和自然规律, 不同状态不同处理, 满足条件则跳转到相关状态。


伪码如下:



enum 
{  
 CLOSED, // 关上状态  
 CLOSING, // 正在关状态  
 OPENED, // 打开状态  
 OPENING, // 正在开的状态  
}doorState = CLOSED; // 初始为关  
 
Update()  
{  
     switch(doorState)  
     case CLOSED:  
          if (有人推门)  
               doorState = OPENING; // 跳转到正在开状态  
     break;  
     case OPENING:  
          door.Rotation += DeltaAngle; // 门的旋转


量递增  
          if (门的速度为零) / / 力的作用已去  
               doorState = OPENED; // 跳转到开状态  
    break;  
    case OPENED:  
         if (有人关门)  
              doorState = CLOSING;  
   break;  
   case CLOSING:  
         door.Rotation -= DeltaAngle; // 门的旋转量递减  
         if (门的旋转角度减为零)  
              doorState = CLOSED; // 门已关上  
    break;   
}  
 
// 而绘制代码几乎不用怎么变, 门就是会严格按照状态机的指示去运动, 该停就会停  
Render()  
{  
     RotateView(door.Rotation);  
     DrawDoor(door.Position);  

enum
{
 CLOSED, // 关上状态
 CLOSING, // 正在关状态
 OPENED, // 打开状态
 OPENING, // 正在开的状态
}doorState = CLOSED; // 初始为关


Update()
{
     switch(doorState)
     case CLOSED:
          if (有人推门)
               doorState = OPENING; // 跳转到正在开状态
     break;
     case OPENING:
          door.Rotation += DeltaAngle; // 门的旋转量递增
          if (门的速度为零) / / 力的作用已去
               doorState = OPENED; // 跳转到开状态
    break;
    case OPENED:
         if (有人关门)
              doorState = CLOSING;
   break;
   case CLOSING:
         door.Rotation -= DeltaAngle; // 门的旋转量递减
         if (门的旋转角度减为零)
              doorState = CLOSED; // 门已关上
    break;
}


// 而绘制代码几乎不用怎么变, 门就是会严格按照状态机的指示去运动, 该停就会停
Render()
{
     RotateView(door.Rotation);
     DrawDoor(door.Position);
}


这是一个简单但很典型的例子, 状态机的应用太多了。
就说一个基本游戏的运转: (用到了一个状态然后还有子状态)
UpdateGame()  
BEGIN;  
   switch(gameState)  
   case 等待选择菜单: //它有三个子状态。  
   if (选择菜单项 == 开始)  
   {  
       游戏初始;  
      gameState = 开始游戏  
   }  
   if (选择菜单项 == 选项)  
      gameState = 设置  
   if (选择菜单项 == 退出)  
       gameState = 退出  
 
 case 开始:  
   
 游戏运行;  
 if (用户按退出键)  
 gameState = 等待选择菜单 ;  
 ...其他的状态跳转处理;  
 case 退出:  
 释放资源;  
 退出;  
 case 设置:  
 分别处理不同的选项, 跳转不同的子状态;  
 case .... // 其他状态的处理  
   
END; 
UpdateGame()
BEGIN;
   switch(gameState)
   case 等待选择菜单: //它有三个子状态。
   if (选择菜单项 == 开始)
   {
       游戏初始;
      gameState = 开始游戏
   }
   if (选择菜单项 == 选项)
      gameState = 设置
   if (选择菜单项 == 退出)
       gameState = 退出


 case 开始:
 
 游戏运行;
 if (用户按退出键)
 gameState = 等待选择菜单 ;
 ...其他的状态跳转处理;
 case 退出:
 释放资源;
 退出;
 case 设置:
 分别处理不同的选项, 跳转不同的子状态;
 case .... // 其他状态的处理
 
END;



某一个状态可以包含更多的子状态, 这样最好是同一层次的状态设为一个枚举, 并分到另一个switch处理
如 enum STATES{state1, state2, state3}; state2又包含若干状态
则再定义enum SUB_STATE2{sub_state2_1, sub_state2_2, sub_state2_3,};


想很多基本的渲染效果, 如淡入淡出, 闪烁等等, 用状态的思想会事半功倍, 思路也更清晰。


其实像Opengl, Direct3D这样的渲染引擎本身就是状态机, 当你设置渲染的状态, 这台机器就保持这个状态进行渲染工作,如保持光照位置,保持片元颜色, 直到


你再次改变它的状态。


状态机的应用实在太广, 相关理论也很多, 最近上课学的随机过程里也讲到一些, 数字电路里的时序逻辑器件也是用状态机来描述。 这些不必多说了。


总之, 用状态机的角度去看待问题, 往往能把比较复杂的系统分解为能单独处理的众多子状态, 处理也会得心应手。希望大家多用它, 很好的东西。



二、
推荐这个:[程序员杂志2004.8月刊_state模式和composite模式实现的状态机引擎]
http://www.contextfree.net/wangyw/source/oofsm.html


个人感觉状态机的几个不同实现阶段:
1、switch/case 最原始的实现方式,是很多的c程序员习惯采用的方式。


2、查找表[状态、事件、动作],稍微做了一点改进。有点类似MFC的雏形。


3、在以上基础上做的一些改进或者变体。
[比如用一个栈结构,激活的状态位于栈顶,自动的映射事件和动作的对应,再或者通过一些巧妙的宏等手段进行包装。但是线性结构在实际中使用比较受限、过于技


巧性的宏比较难于理解...]


4、面向对象的设计下、灵活运用模式。如上面给出的链接。重用性和灵活性方面都有不错的表现。沿袭类似的设计思路、根据实际开发内容进行改造后利用。


【评】:伪代码部分,可以帮助很好的理解【转载1】文章中叙述的FSM的实现方法1;查找表[状态、事件、动作],稍微做了一点改进。有点类似MFC的雏形类似于【转


载1】文章中叙述的FSM的实现方法2(状态表)


☆──────────────────────传说中的分隔符──────────────────────────────☆



【转载4】fsm implemented in C code(FSM状态机用C实现)


用C语言实现一个状态机,很简单,和大家分享
这是我做毕业设计时,用nRF24L01组建了一个简单的网络,做的一个小的状态机,网络中三个节点,开始拓扑为网状,后来为星型。


#include <stdio.h> 
#include <stdlib.h> 
#include <string.h>  
 
 
//Finite state machine declaration  
//state declaration 
#define IDLE                  0 //idle state in rx mode 
#define M_BROADCAST           1 //broadcast state in tx mode,broadcast to be a master point 
#define M_WAIT_BROADCAST_ACK  2 //wait for broadcast ack state in rx mode,wait for the point ack in a specific time window 
#define M_WAIT_COMMAND        3 //wait for command state,wait for PC command via UART 
#define M_BROADCAST_CANCEL    4 //broadcast cancel state,broadcast to cancel master point 
 
#define S_BROADCAST_ACK       5 //slave mode,send back self physical address 
#define S_WAIT_COMMAND        6 //slave mode, wait for command from the master point  
 
//state transition trig  
//used in master mode  
int isReqBeMaster = 0;         //Is PC request the point to be master?  
int isTimeout = 0;             //Is time out?  
int isReqCancelMaster = 0;     //Is request to cancel master?  
//used in slave mode  
int isRxBroadcast = 0;         //Is there a point broadcast to be master?  
int isRxBroadcastCancel = 0;   //Is receive broadcast cancel master?  
 
typedef struct fsmtag  
{  
    int state; //state  
    int timeouttime;         //time out time in milliseconds  
}fsm;  
 
//function prototype  
 
int main()  
{  
    fsm f;  
 
    f.state = IDLE;  
    f.timeouttime = 0;  
 
    while(1)  
    {  
        switch(f.state)  
        {  
        case IDLE:  
            puts("IDLE\nWait for isReqBeMaster(1/0) isRxBroadcast(1/0):");  
            scanf("%d %d",&isReqBeMaster,&isRxBroadcast);  
            if(isReqBeMaster)  
            {  
                f.state = M_BROADCAST;  
                break;  
            }  
            else if(isRxBroadcast)  
            {  
                f.state = S_BROADCAST_ACK;  
                break;  
            }  
            else 
                break;  
        case M_BROADCAST:  
            puts("M_BROADCAST\nBroadcasting...\n");  
            f.state = M_WAIT_BROADCAST_ACK;  
        case M_WAIT_BROADCAST_ACK:  
            puts("M_WAIT_BROADCAST_ACK\nWaiting for isTimeout(1/0):");  
            scanf("%d",&isTimeout);  
            if(isTimeout)  
            {  
                f.state = M_WAIT_COMMAND;  
                break;  
            }  
            else 
                break;  
        case M_WAIT_COMMAND:  
            puts("M_WAIT_COMMAND\nWaiting for isReqCancelMaster(1/0):");  
            scanf("%d",&isReqCancelMaster);  
            if(isReqCancelMaster)  
            {  
                f.state = IDLE;  
                break;  
            }  
            else 
                break;  
        //Slave mode routine  
        case S_BROADCAST_ACK:  
            puts("S_BROADCAST_ACK\nAcking...\n");  
            f.state = S_WAIT_COMMAND;  
            break;  
        case S_WAIT_COMMAND:  
            puts("S_WAIT_COMMAND\nWaiting for isRxBroadcastCancel(1/0):");  
            scanf("%d",&isRxBroadcastCancel);  
            if(isRxBroadcastCancel)  
            {  
                f.state = IDLE;  
                break;  
            }  
            else 
                break;  
        default:  
            puts("default");  
            printf("%d\n",rand());  
            f.state = IDLE;  
        }  
    }  
      
    return 0;  

文章评论1条评论)

登录后参与讨论

用户70812 2010-3-29 17:19

值得收藏的好资料
相关推荐阅读
用户562915 2013-10-02 09:33
android学习:android:padding和android:layout_margin区别
android:padding / android:layout_margin区别: android:padding 是指该view里面的内容与view边界的距离,例如TextView里...
用户562915 2013-10-02 09:32
android学习:android:gravity和android:layout_Gravity的区别
android:gravity / android:layout_Gravity区别: android:gravity 是设置该view里面的内容相对于该view的位置...
用户562915 2013-10-02 09:28
Android特效 五种Toast详解
 Toast是Android中用来显示显示信息的一种机制,和Dialog不一样的是,Toast是没有焦点的,而且Toast显示的时间有限,过一定的时间就会自动消失。 1.默认效果: ...
用户562915 2013-08-11 14:25
STM32(Cortex-M3)中的优先级概念
STM32(Cortex-M3)中有两个优先级的概念——抢占式优先级和响应优先级,有人把响应优先级称作'亚优先级'或'副优先级',每个中断源都需要被指定这两种优先级。 具有高抢占式优先级的中断可...
用户562915 2013-07-30 12:17
STM32如何使用内部HSI作为PLL的系统时钟源
首先,必须屏弊掉库函数部分,因为此部分在启动文件中,先于main函数启动。 void SystemInit (void) { #if 0   /*!< RCC system res...
用户562915 2013-07-29 10:43
keil 未调用函数出现警告的去除办法
按下图设置,Warning->Disable Warning Number为16, 同时,Misc Control输入相关字符数字     ...
我要评论
1
14
关闭 站长推荐上一条 /2 下一条