Python3程式設計實戰Tetris機器人(game類)

zhoutk發表於2021-06-21

系列文章入口

《Python3程式設計實戰Tetris機器人》

game類

遊戲邏輯控制類,是介面與Tetris類之間的粘合者,接受介面的滑鼠及鍵盤事件,操作Tetris類,實現遊戲邏輯。單個方塊的操作,在Tetris中已經實現,game類主要是實現消行演算法、新方塊的產生、遊戲速度控制等。

設計思路

消層演算法簡單的處理就是發現一行,消除一行。本專案使用一點技巧,先找出所有可消除行,把行號存入陣列中,一次性消除。遊戲速度的控制,使用定時器來實現,但發現python的定時器與其它語言有些差別,會不斷產生新的定時器物件,開始感覺有些不對勁,但也沒有重視。後來確認,這會產生記憶體洩漏,後使用tkinter.after替換了。

相關常數

SCORES = (0,1,3,7,10)      # 消層分值設定

STEPUPSCORE = 50           # 每增長50分,速度加快一個等級
STEPUPINTERVAL = 100       # 每增長一個等級,定時器間隔時間減少100毫秒

具體實現

遊戲狀態變數

game.gameRunningStatus

  • 0 : 遊戲未開始
  • 1 : 手動遊戲
  • 2 : 遊戲回放
  • 5 : 遊戲暫停

開始遊戲

def start(self):
    self.gameRunningStatus = 1
    self.gameSpeedInterval = 1000        # 初始遊戲速度
    self.gameSpeed = 1                   # 遊戲速度等級
    self.gameLevels = 0                  # 消層數
    self.gameScores = 0                  # 總得分
    self.app.updateGameInfo(1,0,0)       # 初始化介面資訊
    self.canvas.delete(ALL)              # 清空遊戲空間
    self.nextCanvas.delete(ALL)          # 下一方塊空間清空
    initGameRoom()                       # 初始化遊戲空間資料

    self.tetris = Tetris(self.canvas, 4, 0, random.randint(0,6))             # 隨機生成第一個方塊
    for i in range(random.randint(0,4)):                                     # 旋轉隨機次數,方塊出場式
        self.tetris.rotate()
    self.nextTetris = Tetris(self.nextCanvas, 1, 1, random.randint(0,6))     # 隨機生成下一方塊
    for i in range(random.randint(0,4)):                                     # 下一方塊初始形態(隨機)
        self.nextTetris.rotate()

    self.tick = Timer(self.gameSpeedInterval / 1000, self.tickoff)           # 控制遊戲速度定時器
    self.tick.start()

生成下一方塊

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

def generateNext(self):
    cleanLevels = self.clearRows()                               # 統計可消除層數
    if cleanLevels > 0:                                          # 有可消層,計算分值
        self.gameLevels += cleanLevels
        self.gameScores += SCORES[cleanLevels]
        if self.gameScores / STEPUPSCORE >= self.gameSpeed:
            self.gameSpeed += 1
            self.gameSpeedInterval -= STEPUPINTERVAL
        self.app.updateGameInfo(self.gameSpeed, self.gameLevels, self.gameScores)
    self.tetris = Tetris(self.canvas, 4, 0, self.nextTetris.getTetrisShape())    # 複製nexTetris到遊戲空間
    for i in range(self.nextTetris.getRotateCount()):
        if not self.tetris.rotate():
            break
    if self.tetris.canPlace(4, 0):                   # 判定遊戲是否結束
        self.nextCanvas.delete(ALL)                  # 遊戲未結束,生成新的方塊放入下一方塊空間
        self.nextTetris = Tetris(self.nextCanvas, 1, 1, random.randint(0,6))
        for i in range(random.randint(0,4)):
            self.nextTetris.rotate()
    else:                                            # 遊戲結束
        self.gameRunningStatus = 0
        self.canvas.create_text(150, 200, text = "Game is over!", fill="white", font = "Times 28 italic bold")
        self.app.setStartButtonText("Start")
        print("game is over!")

統計可消除層

clearRows函式查詢能消除的層,將其消除,返回可消除層總數。

def clearRows(self):
    occupyLines = []                  # 儲存可消除層行號
    h = 20
    while h > 0:
        allOccupy = 0
        for i in range(1, 11):
            if GameRoom[h][i]:
                allOccupy += 1        # block統計
        if allOccupy == 10:           # 行滿
            occupyLines.append(h)     # 儲存行號
        elif allOccupy == 0:          # 有一個空位,跳過些行
            break
        h -= 1
    if len(occupyLines) > 0:          # 有可消層
        self.doCleanRows(occupyLines) # 消除可消層
    return len(occupyLines)

消層函式

消層函式,根據clearRows函式統計的可消層行號,消除遊戲空間的滿行。演算法的難點在於要同時控制兩個變數,一個是從下到上遍歷遊戲空間,另一方面要將滿行以上的空間資料下移,下移的步長為已經消除的行數。

def doCleanRows(self, lines):
    index = 0                                 # 儲存已經消除了多少行
    h = lines[index]                          # 滿行行號資料
    while h >= 0:                             # 只需要從最下面一滿行開始即可
        if index < len(lines) and h == lines[index]:         # 找到一可消行
            index += 1                        # 已消行總數加1
            for j in range(1, 11):
                GameRoom[h][j] = 0            # 遊戲空間資料消行
                for b in self.canvas.find_closest(\         # Canvas元件消除
                    j * BLOCKSIDEWIDTH - HALFBLOCKWIDTH, \
                    h  * BLOCKSIDEWIDTH - HALFBLOCKWIDTH):
                    self.canvas.delete(b)
        else:                                 # 移動遊戲空間資料
            count = 0                         # 空位統計,全空,可以提前結束迴圈
            for j in range(1, 11):
                if GameRoom[h][j] == 1:
                    count += 1
                    GameRoom[h + index][j] = GameRoom[h][j]     # 注意index變數,這是移動步長,與已經消除行數有關
                    GameRoom[h][j] = 0
                    for b in self.canvas.find_closest(j * BLOCKSIDEWIDTH - HALFBLOCKWIDTH, h  * BLOCKSIDEWIDTH - HALFBLOCKWIDTH):
                        self.canvas.move(b, 0, index * BLOCKSIDEWIDTH)
            if count == 0:                   # 發現整行位全空,提前退出
                break
        h -= 1

方塊控制

方塊控制已經在Tetris類實現,在game類中,只是轉發事件到當前方塊即可。唯一多了一個moveDownEnd - 方塊直落函式。

def moveDownEnd(self):
    while self.moveDown():      # 迴圈下落,直到不能再落
        pass

遊戲速度控制

遊戲速度控制實現很容易,只需要定時觸發一次down函式就可以了。

def tickoff(self):
    if self.gameRunningStatus == 1:
        self.moveDown()
        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

相關文章