iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 14
2
自我挑戰組

卡牌連線遊戲開發經驗分享系列 第 14

#14 遊戲實作:玩家回合開始、操作、回合結束

  • 分享至 

  • xImage
  •  

#11 我們規劃的玩家端要處理的部份有:
updateHandler(msg), turnStartHandler(), action(msg), turnEndHandler()
但後面發現 turnEnd 回合結束部份其實是玩家送出,不是主控端決定,於是改成 end()

於是要實作的部份是這四個:

  1. startEventHandler() 回合開始,進行玩家操作
  2. action() 玩家操作
  3. updateEventHandler() 主要是同步對面玩家操作後的結果
  4. 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
()

待處理:

  1. 實作從外部停止 playerCtrl.eventloop() 的方法,目前沒辦法中斷
  2. 思考 Dispatcher 是否要跟 PlayerCtrl 合併?
  3. 實作 playerCtrl.eventLoop 跟 GUI event 的嫁接的部份

接下來會試著實作主控端的部份,明天見!


上一篇
#13 研究筆記:事件迴圈的世界觀
下一篇
#15 遊戲實作:遊戲流程、玩家行動、切換玩家
系列文
卡牌連線遊戲開發經驗分享30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言