有限狀態機(FSM)簡單實現

簡述
有限狀態機(如下用FSM指代)是一種算法思想,簡單而言,有限狀態機由一組狀態、一個初始狀態、輸入和根據輸入及現有狀態轉換爲下一個狀態的轉換函數組成。在Gof的23種設計模式裏的state模式是一種面向對象的狀態機思想,能夠適應很是複雜的狀態管理。
如今,FSM被廣泛用於搜索引擎的分詞、編譯器實現和咱們廣泛關注的遊戲開發中。遊戲開發中,一般用FSM實現NPC控制,如當NPC受到攻擊時根據健康、力量等選擇逃跑仍是反攻的行爲,通常是用FSM實現的。FSM的實現方法有不少種,不能簡單地說孰優孰劣,但現代開發中,通常都比較推薦面向對象的實現方式:由於可重用性和健壯性更高,並且當需求變動的時候,也有很好的適應性。
實踐
理論從實踐中來,也要回到實踐中去。咱們如今經過實例來探索一下FSM的實現吧。首先假設有這樣一個世界(World),世界裏只有一臺永不缺少動力的汽車(Car),汽車是次世代的,沒有油門方向盤之類的落後設備,只有兩個互斥的按鈕——中止(Stop)和行進(Run),隨着時間的流逝,汽車根據駕駛員的操做走走停停。下面的代碼能夠實現這種功能:
while True:
       key = get_key() # 按下什麼鍵
       if key == "stop":
              stop(car)
       elif key == "run":
              go(car)
       keep(car) # 保持原態
完成了功能並且直觀、簡潔的程序員萬歲!但這時候客戶(策劃或者玩家)以爲走走停停太沒意思了,他們想要掉頭、左轉和右轉的功能,咱們就要在while循環裏增長更多的if...else分支;他們想要更多的車,咱們就要要在每個分枝裏增長循環;他們不只僅想要Car了,他們還要要玩Truck,這時咱們就須要在分枝的循環裏判斷當前的車是否支持這個操做(如Truck的裝卸貨物Car就不支持);他們……
這個while循環終於無限地龐大起來,咱們認識到這樣的設計的確是有點問題的,因此咱們嘗試用另外一種方法去實現FSM。首先咱們來實現汽車(Car):
class Car(object):
       def stop(self):
              print "Stop!!!"
 
       def go(self):
              print "Goooooo!!!"
只有兩個方法stop和go,分別執行Stop和Run兩個按鈕功能。接下來咱們編寫兩個狀態管理的類,用以處理當按鈕被按下、彈起和保持時須要工做的流程:
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))
stop_fsm和run_fsm都繼承自base_fsm,base_fsm是一個純虛的接口類:
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()
enter_state在obj進入某狀態的時候調用——一般用來作一些初始化工做;exit_state也離開某狀態的時候調用——一般作一些清理工做;而exec_state則在每一幀的時候都會被調用,一般作一些必要的工做,如檢測本身的消息隊列並處理消息等。在stop_fsm和run_fsm兩個類的exec_state函數中,就調用了對象的stop和go函數,讓汽車保持原有的狀態。
至如今爲止,Car尚未接觸到FSM,因此咱們須要提供一個接口,可讓它擁有一個FSM:
       def attach_fsm(self, state, fsm):
              self.fsm = fsm
              self.curr_state = state
咱們還須要爲Car提供一個狀態轉換函數:
       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)
爲Car提供一個保持狀態的函數:
       def keep_state(self):
              self.fsm.exec_state(self)
如今只有兩個狀態,但咱們知道需求隨時會改動,因此咱們最好弄一個狀態機管理器來管理這些狀態:
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])
fsm_mgr最重要的函數就是frame,在每一幀都被調用。在這裏,frame根據對象如今的狀態和當前的輸入決定讓對象保持狀態或者改變狀態。
這時候,咱們的實例基本上完成了。但咱們還要作一件事,就是創建一個世界(World)來驅動狀態機:
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)
從代碼可見,World裏有Car對象,fsm_mgr對象;在run函數裏,每0.5s執行一次__frame函數(FPS = 2),而__frame函數只是驅動了fsm_mgr來刷新對象,新的命令是從state_factory函數裏取出來的,這個函數用以模擬駕駛員的操做(按下Stop或者Run按鈕之一):
def state_factory():
       return random.randint(0, 1)
如今咱們就要初始化世界(World)能夠跑起咱們的FSM了!
if __name__ == "__main__":
       world = World()
       world.init()
       world.run()
用python解釋器執行上面的代碼,咱們能夠看到程序不停地輸出Car的狀態:
......
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!!!
......
 
結論
這時再回頭來看看咱們以前的問題:
一、玩家想要功能更多的Car,好比掉頭。
咱們能夠經過爲Car增長一個調頭(back)的方法來執行掉頭,而後從base_fsm中繼承一個back_fsm來處理調頭。以後在fsm_mgr裏增長一個back_fsm實例,及讓state_factory產生調頭指令。聽起來彷佛比以前while+if的方式還要麻煩很多,其實否則,這裏只有back_fsm和爲fsm_mgr增長back_fsm實例纔是特有的,其它步驟兩種方法都要執行。
二、玩家要更多的Car。
這對於面向對象的FSM實現就太簡單了,咱們只要把World.__init_car裏的生產數量修改一下就好了,要多少有多少。
三、玩家要更多型號的車,如Truck。
從Car派生一個Truck,而後增長裝貨、卸貨的接口。最大的改動在於Truck狀態轉換的時候須要一些判斷,如不能直接從裝貨狀態轉換到開動狀態,而是裝貨、中止再開動。
經過這幾個簡單的問題分析,咱們能夠看到,使用面向對象的方式來設計FSM,在需求變動的時候,通常都只增刪代碼,而仍少須要改動已有代碼,程序的擴展性、適應性和健壯性都得很大的提升;所以,在世界龐大、物種煩多、狀態複雜且條件交錯的遊戲開發中應用面向對象的FSM實在是明智之選。還有一點,面向對象的FSM能夠很是容易地模擬消息機制,這有利於模塊清晰化,更容易設計出正交的程序。
相關文章
相關標籤/搜索