[Copy] 有限状态自动机(FSM)的简单实现

[i][color=red][size=11]Sometimes, a quite simple concept comes out with lots of related theories that make beginners have to spend an exausting time before figure out the basic one. Watching something through such thick fog, we'll never make it without a powerful straight light which directly reach the key point.

The light for programmers is sure the code.

This article I copied, is trying to explain an useful basic concept of "FSM" in an uncommon way, which I think, greatly shows how "code rules". Think about it while learning what FSM is. :)[/size][/color][/i]


[b][size=16]简述[/size][/b]

[size=11]有限状态机(以下用 FSM 指代)是一种算法思想,简单而言,有限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组成。在 Gof 的 23 种设计模式里的 state 模式是一种面向对象的状态机思想,可以适应非常复杂的状态管理。
现在,FSM 被普遍用于搜索引擎的分词、编译器实现和我们普遍关注的游戏开发中。游戏开发中,通常用 FSM 实现 NPC 控制,如当 NPC 受到攻击时根据健康、力量等选择逃跑还是反攻的行为,一般是用FSM实现的。FSM 的实现方法有很多种,不能简单地说孰优孰劣,但现代开发中,一般都比较推荐面向对象的实现方式:因为可重用性和健壮性更高,而且当需求变更的时候,也有很好的适应性。[/size]

[b][size=16]实践[/size][/b]

[size=11]理论从实践中来,也要回到实践中去。我们现在通过实例来探索一下 FSM 的实现吧。首先假设有这样一个世界(World),世界里只有一台永不缺乏动力的汽车(Car),汽车是次世代的,没有油门方向盘之类的落后设备,只有两个互斥的按钮——停止(Stop)和行进(Run),随着时间的流逝,汽车根据驾驶员的操作走走停停。下面的代码可以实现这种功能:[/size]
while True:
key = get_key() # 按下什么键
if key == "stop":
stop(car)
elif key == "run":
go(car)
keep(car) # 保持原态

[size=11]完成了功能而且直观、简洁的程序员万岁!但这时候客户(策划或者玩家)觉得走走停停太没意思了,他们想要掉头、左转和右转的功能,我们就要在 while 循环里增加更多的 if...else 分支;他们想要更多的车,我们就要要在每一个分枝里增加循环;他们不仅仅想要 Car 了,他们还要要玩 Truck,这时我们就需要在分枝的循环里判断当前的车是否支持这个操作(如 Truck 的装卸货物 Car 就不支持);他们……
这个 while 循环终于无限地庞大起来,我们认识到这样的设计的确是有点问题的,所以我们尝试用另一种方法去实现FSM。首先我们来实现汽车(Car):[/size]
class Car(object):
def stop(self):
print "Stop!!!"

def go(self):
print "Goooooo!!!"

[size=11]只有两个方法 stop 和 go,分别执行 Stop 和 Run 两个按钮功能。接下来我们编写两个状态管理的类,用以处理当按钮被按下、弹起和保持时需要工作的流程:[/size]
class stop_fsm(base_fsm):
def enter_state(self, obj):
print "Car%s enter stop state!"%(id(obj))

def exec_state(self, obj):
print "Car%s in stop state!"%(id(obj))
obj.stop()

def exit_state(self, obj):
print "Car%s exit stop state!"%(id(obj))

class run_fsm(base_fsm):
def enter_state(self, obj):
print "Car%s enter run state!"%(id(obj))

def exec_state(self, obj):
print "Car%s in run state!"%(id(obj))
obj.go()

def exit_state(self, obj):
print "Car%s exit run state!"%(id(obj))

[size=11]stop_fsm 和 run_fsm 都继承自 base_fsm,base_fsm,是一个纯虚的接口类:[/size]
class base_fsm(object):
def enter_state(self, obj):
raise NotImplementedError()

def exec_state(self, obj):
raise NotImplementedError()

def exit_state(self, obj):
raise NotImplementedError()

[size=11]enter_state 在 obj 进入某状态的时候调用——通常用来做一些初始化工作;exit_state 也离开某状态的时候调用——通常做一些清理工作;而 exec_state 则在每一帧的时候都会被调用,通常做一些必要的工作,如检测自己的消息队列并处理消息等。在 stop_fsm 和 run_fsm 两个类的 exec_state 函数中,就调用了对象的 stop 和 go 函数,让汽车保持原有的状态。
至现在为止,Car 还没有接触到 FSM,所以我们需要提供一个接口,可以让它拥有一个 FSM:[/size]
def attach_fsm(self, state, fsm):
self.fsm = fsm
self.curr_state = state

[size=11]我们还需要为Car提供一个状态转换函数:[/size]
def change_state(self, new_state, new_fsm):
self.curr_state = new_state
self.fsm.exit_state(self)
self.fsm = new_fsm
self.fsm.enter_state(self)
self.fsm.exec_state(self)

[size=11]为Car提供一个保持状态的函数:[/size]
def keep_state(self):
self.fsm.exec_state(self)

[size=11]现在只有两个状态,但我们知道需求随时会改动,所以我们最好弄一个状态机管理器来管理这些状态:[/size]
class fsm_mgr(object):
def __init__(self):
self._fsms = {}
self._fsms[0] = stop_fsm()
self._fsms[1] = run_fsm()

def get_fsm(self, state):
return self._fsms[state]

def frame(self, objs, state):
for obj in objs:
if state == obj.curr_state:
obj.keep_state()
else:
obj.change_state(state, self._fsms[state])

[size=11]fsm_mgr 最重要的函数就是 frame,在每一帧都被调用。在这里,frame 根据对象现在的状态和当前的输入决定让对象保持状态或者改变状态。
这时候,我们的实例基本上完成了。但我们还要做一件事,就是建立一个世界(World)来驱动状态机:[/size]
class World(object):
def init(self):
self._cars = []
self._fsm_mgr = fsm_mgr()
self.__init_car()

def __init_car(self):
for i in xrange(1): # 生产汽车
tmp = Car()
tmp.attach_fsm(0, self._fsm_mgr.get_fsm(0))
self._cars.append(tmp)

def __frame(self):
self._fsm_mgr.frame(self._cars, state_factory())

def run(self):
while True:
self.__frame()
sleep(0.5)

[size=11]从代码可见,World 里有 Car 对象,fsm_mgr 对象;在 run 函数里,每 0.5s 执行一次 __frame 函数(FPS = 2),而 __frame 函数只是驱动了 fsm_mgr 来刷新对象,新的命令是从 state_factory 函数里取出来的,这个函数用以模拟驾驶员的操作(按下 Stop 或者 Run 按钮之一):[/size]
def state_factory():
return random.randint(0, 1)

[size=11]现在我们就要初始化世界(World)可以跑起我们的 FSM 了![/size]
if __name__ == "__main__":
world = World()
world.init()
world.run()

[size=11]用 python 解释器执行上面的代码,我们可以看到程序不停地输出 Car 的状态:[/size][quote]......
Car8453392 exit run state!
Car8453392 enter stop state!
Car8453392 in stop state!
Stop!!!
Car8453392 in stop state!
Stop!!!
Car8453392 exit stop state!
Car8453392 enter run state!
Car8453392 in run state!
Goooooo!!!
Car8453392 exit run state!
Car8453392 enter stop state!
Car8453392 in stop state!
Stop!!!
Car8453392 exit stop state!
Car8453392 enter run state!
Car8453392 in run state!
Goooooo!!!
Car8453392 in run state!
Goooooo!!!
Car8453392 exit run state!
Car8453392 enter stop state!
Car8453392 in stop state!
Stop!!!
Car8453392 in stop state!
Stop!!!
Car8453392 exit stop state!
Car8453392 enter run state!
Car8453392 in run state!
Goooooo!!!
......[/quote]

[b][size=16]结论[/size][/b]

[size=11]这时再回头来看看我们之前的问题:
[*]1、玩家想要功能更多的 Car,比如掉头
我们可以通过为 Car 增加一个调头(back)的方法来执行掉头,然后从 base_fsm 中继承一个 back_fsm 来处理调头。之后在 fsm_mgr 里增加一个 back_fsm 实例,及让 state_factory 产生调头指令。听起来似乎比之前 while+if 的方式还要麻烦不少,其实不然,这里只有 back_fsm 和为 fsm_mgr 增加 back_fsm 实例才是特有的,其它步骤两种方法都要执行。

[*]2、玩家要更多的 Car
这对于面向对象的 FSM 实现就太简单了,我们只要把 World.__init_car 里的生产数量修改一下就行了,要多少有多少。

[*]3、玩家要更多型号的车,如 Truck
从 Car 派生一个 Truck,然后增加装货、卸货的接口。最大的改动在于 Truck 状态转换的时候需要一些判断,如不能直接从装货状态转换到开动状态,而是装货、停止再开动。
通过这几个简单的问题分析,我们可以看到,使用面向对象的方式来设计 FSM,在需求变更的时候,一般都只增删代码,而仍少需要改动已有代码,程序的扩展性、适应性和健壮性都得很大的提高;因此,在世界庞大、物种烦多、状态复杂且条件交错的游戏开发中应用面向对象的 FSM 实在是明智之选。还有一点,面向对象的 FSM 可以非常容易地模拟消息机制,这有利于模块清晰化,更容易设计出正交的程序。[/size]

[color=red][i]本文发表于恋花蝶的博客:[url]http://blog.csdn.net/lanphaday[/url],欢迎转载,但必须保留文章内容完整且包含本声明。违者必究。[/i][/color]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值