2

Series entry

"Python3 Programming Actual Tetris Robot"

problem found

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.

solution

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.

Lock

Analysis of Locking Scheme

Keyboard response lock

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)

Timer response lock

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()

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:

("cmd",(data))

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.

Worker thread

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',()))

Timer transformation

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()

project address

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

Operation method

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

Related items

C++ version has been implemented, project address:

https://gitee.com/zhoutk/qtetris

zhoutk
2.6k 声望1.2k 粉丝

自由程序员,技术路线c,delphi,c++,c#,java,php,node.js,python,golang,typescript;超喜欢react.js的设计思路,全栈开发成为我的终极目标。开发机器macbook pro,或装ubuntu、fedora的机器,编程用vim...