在 #11 我們規劃的玩家端要處理的部份有:
updateHandler(msg), turnStartHandler(), action(msg), turnEndHandler()
但後面發現 turnEnd 回合結束部份其實是玩家送出,不是主控端決定,於是改成 end()
於是要實作的部份是這四個:
我們會需要 Dispatcher(調度者) 負責接收、處理主控端送來的事件,內部需要一組 eventLoop, task queue 維持,同時它也要把玩家的操作、結束回合指令送出。
我們把玩家操作的部份封裝在 PlayerCtrl,操作指令、結束回合由它實作,startEventHandler()在此實作。因為玩家操作行為是跟介面互動,我們也用一個 eventLoop 維護,接受介面按鈕事件、鍵盤事件轉成玩家操作指令送出。這 eventLoop 是在收到「回合開始」之後才會啟動,等到「回合結束」就會關閉。透過 _ACTIVE
決定 loop 是否啟動。因為還沒正式跟 GUI 結合,所以只是模擬的。
而 PlayerModel 主要是封裝遊戲資料,updateEventHandler() 在此實作。目前是模擬,還沒加入實際遊戲資料。
實作碼如下:
from queue import Queue
import threading
from random import choice, randint
from time import sleep
class Dispatcher():
def __init__(self):
self._inbox = Queue()
self._outbox = Queue()
self._recvSet = set()
self._sub = {}
def regist(self, ev, hdlr):
if ev not in self._recvSet:
self._recvSet |= {ev}
self._sub[ev] = hdlr
print("[info] regist event: %s, handler: %s" % (ev,hdlr))
def unregist(self, ev):
if ev in self._recvSet:
del self._sub[ev]
self._recvSet -= {ev}
print("[info] unregist event: %s" % ev)
def eventLoop(self):
while True:
r = self._inbox.get() # block=True
if type(r) is str and r in self._recvSet:
cmd = r
print("[info] event %s" % cmd)
self._sub[cmd]()
elif type(r) is list and r[0] in self._recvSet:
cmd, args = r[0], r[1]
print("[info] event %s %s" % (cmd,args))
self._sub[cmd](args)
else: # unsupported event
print("[warm] unsupported event")
def send(self, cmd, args=None):
if args:
self._outbox.put([cmd,args]) # with args
else:
self._outbox.put(cmd) # no arg
return
class PlayerCtrl():
def __init__(self, dispatcher):
self._ACTIVE = False
self._dispatcher = dispatcher
def send(self, cmd, *args):
print("[info] Ctrl: send %s, %s" % (cmd,args))
self._dispatcher.send(cmd, *args)
def action(self):
print("[info] Ctrl cmd: action")
cmds = ["creature_card","spell_card","weapon_card","char_ability","creature_attack","char_attack"]
self.send("action",choice(cmds))
def end(self):
print("[info] Ctrl cmd: end")
self.send("end")
self._ACTIVE = False
def eventLoopTest(self, n=None, interval=None): # test
print("[info] Crtl: simulation for event loop")
if n is None: n=randint(1,10) # 1-10 cmd
if interval is None: interval=10+randint(1,20) # 10-30 sec
t = interval / n
print("[info] Ctrl: action_n=%d interval_t=%.1fs" % (n,t))
for i in range(n):
if self._ACTIVE:
self.action()
sleep(t)
else:
print("[erro] Ctrl: player is inactive")
return
if self._ACTIVE:
self.end()
def startEventHandler(self):
print("[info] Ctrl EventHandler: start")
self._ACTIVE = True
self.eventLoopTest()
def forcedEndEventHandler(self):
print("[info] Ctrl EventHandler: forced end")
self._ACTIVE = False
class PlayerModel():
def __init__(self):
self._ENV = {}
def updateEventHandler(self, *args):
print("[info] Model EvnetHandler: Update")
print(args) # test
def startTest(dispatcher):
print("test start event")
sleep(1)
dispatcher._inbox.put("start")
def updateTest(dispatcher):
print("test update event")
sleep(1)
dispatcher._inbox.put("update")
msgr = Dispatcher()
pctrl = PlayerCtrl(msgr)
pmodl = PlayerModel()
msgr.regist("start",pctrl.startEventHandler)
msgr.regist("update", pmodl.updateEventHandler)
t = threading.Thread(target = msgr.eventLoop)
t.start()
startTest(msgr)
updateTest(msgr)
輸出範例:
[info] regist event: start, handler: <bound method PlayerCtrl.startEventHandler of <__main__.PlayerCtrl object at 0x000001CB87EFF2C8>>
[info] regist event: update, handler: <bound method PlayerModel.updateEventHandler of <__main__.PlayerModel object at 0x000001CB87EFF308>>
test start event
test update event[info] event start
[info] Ctrl EventHandler: start
[info] Crtl: simulation for event loop
[info] Ctrl: action_n=9 interval_t=2.3s
[info] Ctrl cmd: action
[info] Ctrl: send action, ('spell_card',)
[info] Ctrl cmd: action
[info] Ctrl: send action, ('creature_card',)
[info] Ctrl cmd: action
[info] Ctrl: send action, ('creature_card',)
[info] Ctrl cmd: action
[info] Ctrl: send action, ('creature_card',)
[info] Ctrl cmd: action
[info] Ctrl: send action, ('creature_card',)
[info] Ctrl cmd: action
[info] Ctrl: send action, ('char_attack',)
[info] Ctrl cmd: action
[info] Ctrl: send action, ('char_ability',)
[info] Ctrl cmd: action
[info] Ctrl: send action, ('char_attack',)
[info] Ctrl cmd: action
[info] Ctrl: send action, ('creature_attack',)
[info] Ctrl cmd: end
[info] Ctrl: send end, ()
[info] event update
[info] Model EvnetHandler: Update
()
待處理:
接下來會試著實作主控端的部份,明天見!