系列文章入口
《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
来源: https://segmentfault.com/a/1190000040218933