系列文章入口
《Python3 编程实战 Tetris 机器人》
发现问题
在测试过程中,发现程序出错,但敞开定时器,不进行主动着落就不会有问题。起因是 Timer 会新开一个线程,线程和主线会产生资源抵触。
解决方案
首先想到的是加锁,游戏逻辑很简略,加锁应该很容易解决问题。但不论我粗粒度加,还是尽量细粒度加,最初都会死锁。最初进行打印,发现程序停在了 tkinter.Canvas.move 处,集体认为这是 tkinter 的 bug。
此路不通,换个思路。开一个工作线程,来实现所有的操作,主线程与定时器操作,都只是往工作线程中提交工作。也就是只让一个工作线程来做工作,这样就把资源抵触的问题避开了。
加锁
加锁计划剖析
键盘响应加锁
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,键盘响应与定时器响应往队列中减少工作单元,工作线程逐个解决这些工作。工作单元如下设计:
("cmd",(data))
每一个工作单元都是一个二元元组(不便数据解构),第一个是字符串,为命令;第二个是元组,是数据包(也按不便解构的形式去设计),由每个命令自行定义。
工作线程
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