During the test, it was found that there was a program error, but the timer was turned off and there would be no problem without automatic falling. The reason is that the Timer will open a new thread, which will cause resource conflicts with the main thread.
The first thing that comes to mind is locking. The game logic is very simple, and locking should be easy to solve the problem. But no matter if I add coarse-grained or fine-grained as much as possible, it will deadlock in the end. Finally, I printed it and found that the program stopped at tkinter.Canvas.move. I personally think this is a bug in tkinter.
This road fails, so change your mind. Open a worker thread to complete all operations. The main thread and timer operations are just submitting tasks to the worker thread. That is, only one worker thread is allowed to do the task, thus avoiding the problem of resource conflicts.
Analysis of Locking Scheme
Keyboard response lock
tickLock = 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)
Timer response lock
def tickoff(self): if self.gameRunningStatus == 1: if not tickLock: with curTetrisLock: print("------------------ get lock", tickLock) self.moveDown() print("================== lose lock", tickLock) self.tick = Timer(self.gameSpeedInterval / 1000, self.tickoff) self.tick.start()
identify the problem
The program finally stopped at tkinter.Canvas.move in the Block class, which was triggered by the timer each time and could not be released.
Interested students can go to the project and switch to the lockbug branch to study. I wrote a lot of printouts to facilitate problem location.
Increase worker threads
Task unit design
A new Queue is added. The keyboard response and timer response add task units to the queue, and the worker threads process these tasks one by one. The task unit is designed as follows:
Each task unit is a two-tuple (convenient for data deconstruction), the first is a string, which is a command; the second is a tuple, which is a data packet (also designed in a convenient way to deconstruct), by each Each command is self-defined.
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)
Keyboard response transformation
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',()))
The main function of game control. After the cube drops to the bottom, it will eliminate the layer, count the score, determine the speed level, determine whether the game is over, and move the next cube into the game space and generate a cube to display in the next cube display 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++ version has been implemented, project address: