Python3編程實戰Tetris機器人(多線程問題)

系列文章入口

《Python3編程實戰Tetris機器人》python

發現問題

在測試過程當中,發現程序出錯,但關閉定時器,不進行自動下落就不會有問題。緣由是Timer會新開一個線程,線程和主線會產生資源衝突。linux

解決方案

首先想到的是加鎖,遊戲邏輯很簡單,加鎖應該很容易解決問題。但無論我粗粒度加,仍是儘可能細粒度加,最後都會死鎖。最後進行打印,發現程序停在了tkinter.Canvas.move處,我的認爲這是tkinter的bug。
此路不通,換個思路。開一個工做線程,來完成全部的操做,主線程與定時器操做,都只是往工做線程中提交任務。也就是隻讓一個工做線程來作任務,這樣就把資源衝突的問題避開了。git

加鎖

加鎖方案分析github

鍵盤響應加鎖

tickLock[0] = True
with curTetrisLock:
    print("-------+++---00000000--- get lock", tickLock)
    if ke.keysym == 'Left':
        self.game.moveLeft()
    if ke.keysym == 'Right':
        self.game.moveRight()
    if ke.keysym == 'Up':
        self.game.rotate()
    if ke.keysym == 'Down':
        self.game.moveDown()
    if ke.keysym == 'space':
        self.game.moveDownEnd()
print("-------+++---00000000--- lose lock", tickLock)

定時器響應加鎖

def tickoff(self):
    if self.gameRunningStatus == 1:
        if not tickLock[0]:
            with curTetrisLock:
                print("------------------ get lock", tickLock[1])
                self.moveDown()
            print("================== lose lock", tickLock[1])
        self.tick = Timer(self.gameSpeedInterval / 1000, self.tickoff)
        self.tick.start()

問題定位

程序最後停在了Block類中的tkinter.Canvas.move處,每次都由定時器觸發,沒法釋放。

有興趣的同窗能夠到項目中,切換到lockbug分支去研究,我寫了不少打印輸出方便問題定位。編程

增長工做線程

任務單元設計

新增一個Queue,鍵盤響應與定時器響應往隊列中增長任務單元,工做線程逐一處理這些任務。任務單元以下設計:segmentfault

("cmd",(data))

每個任務單元都是一個二元元組(方便數據解構),第一個是字符串,爲命令;第二個是元組,是數據包(也按方便解構的方式去設計),由每一個命令自行定義。windows

工做線程

def opWork(self):
    while True:
        if not opQueue.empty():
            cmd,data = opQueue.get()
            if op == "Left":
                self.moveLeft()
            elif op == "Right":
                self.moveRight()
            elif op == "Up":
                self.rotate()
            elif op == "Down":
                self.moveDown()
            elif op == "space":
                self.moveDownEnd()
            elif op == "quit":
                break
        else:
            time.sleep(0.01)

鍵盤響應改造

def processKeyboardEvent(self, ke):
    if self.game.getGameRunningStatus() == 1:
        if ke.keysym == 'Left':
            opQueue.put(('Left',()))
        if ke.keysym == 'Right':
            opQueue.put(('Right',()))
        if ke.keysym == 'Up':
            opQueue.put(('Up',()))
        if ke.keysym == 'Down':
            opQueue.put(('Down',()))
        if ke.keysym == 'space':
            opQueue.put(('space',()))

定時器改造

遊戲控制主要函數,在方塊下落到底部後,進行消層、統計得分、速度等級斷定、遊戲是否結束斷定以及將下一方塊移入遊戲空間並再生成一個方塊顯示在下一方塊顯示空間中。函數

def tickoff(self):
    if self.gameRunningStatus == 1:
        opQueue.put(('Down'),())
        self.tick = Timer(self.gameSpeedInterval / 1000, self.tickoff)
        self.tick.start()

項目地址

https://gitee.com/zhoutk/ptetris
或
https://github.com/zhoutk/ptetris

運行方法

1. install python3, git
2. git clone https://gitee.com/zhoutk/ptetris (or download and unzip source code)
3. cd ptetris
4. python3 tetris

This project surpport windows, linux, macOs

on linux, you must install tkinter first, use this command:  
sudo apt install python3-tk

相關項目

已經實現了C++版,項目地址:測試

https://gitee.com/zhoutk/qtetris
相關文章
相關標籤/搜索