pyQt4教程中俄羅斯方塊遊戲的註釋

#!/usr/bin/python
# -*- coding: utf-8 -*-


import sys, random
from PyQt4 import QtCore, QtGui


class Tetris(QtGui.QMainWindow):
    #Tetris的構造函數,由於是QMainWindow的子類,所以要先調用父類的構造函數
    def __init__(self):
        super(Tetris, self).__init__()
        #QtGui.QMainWindow.__init__(self)
        self.initUI()
        
        
    def initUI(self):    

        self.tboard = Board(self)   #創建一個Board類的實例
        self.setCentralWidget(self.tboard)  #將遊戲窗口放到屏幕的中間

        self.statusbar = self.statusBar()   #創建狀態欄     
        self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)  #3種可能的信息:1.score 2.game over 3.pause
        
        self.tboard.start() #開始初始化程序
        
        self.resize(180, 380)   #遊戲窗口的大小
        #self.resize(480, 380)   #遊戲窗口的大小
        self.center()
        self.setWindowTitle('Tetris')   #窗口的名字        
        self.show() #這句一定不能忘了,顯示窗口
        

    def center(self):
        #將遊戲窗口放到屏幕的中間
        screen = QtGui.QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width()-size.width())/2, 
            (screen.height()-size.height())/2)
        

class Board(QtGui.QFrame):
    
    msg2Statusbar = QtCore.pyqtSignal(str)
    
    #遊戲窗口的寬和高(單位爲塊)
    BoardWidth = 10     #寬度爲10塊
    BoardHeight = 22    #高度爲22塊
    Speed = 300 #遊戲的速度

    def __init__(self, parent):
        super(Board, self).__init__(parent)
        
        self.initBoard()
        
        
    def initBoard(self):     
        '''
        初始化一些關鍵的變量
        '''
        self.timer = QtCore.QBasicTimer()   #創建一個定時器
        self.isWaitingAfterLine = False
        
        self.curX = 0
        self.curY = 0
        self.numLinesRemoved = 0
        #a list of numbers from 0-7.
        #It represents the position of various shapes and remains of the shapes on the board.
        self.board = [] 
        

        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.isStarted = False
        self.isPaused = False
        self.clearBoard()
        
    #determine the type of a shape at a given block.    
    #返回(x,y)座標處對應的點的類型
    def shapeAt(self, x, y):
        return self.board[(y * Board.BoardWidth) + x]

    #設置(x,y)座標處對應的點的類型
    def setShapeAt(self, x, y, shape):
        self.board[(y * Board.BoardWidth) + x] = shape
        
    #calculate the width of the single square in pixels and return it
    #The Board.BoardWidth is the size of the board in blocks
    def squareWidth(self):
        return self.contentsRect().width() / Board.BoardWidth
        

    def squareHeight(self):
        return self.contentsRect().height() / Board.BoardHeight
        

    def start(self):
        
        if self.isPaused:   #如果暫停,直接返回
            return

        self.isStarted = True
        self.isWaitingAfterLine = False
        self.numLinesRemoved = 0
        self.clearBoard()

        self.msg2Statusbar.emit(str(self.numLinesRemoved))

        self.newPiece()
        self.timer.start(Board.Speed, self)

        
    def pause(self):
        
        if not self.isStarted:
            return

        self.isPaused = not self.isPaused
        
        if self.isPaused:
            self.timer.stop()
            self.msg2Statusbar.emit("paused")
            
        else:
            self.timer.start(Board.Speed, self)
            self.msg2Statusbar.emit(str(self.numLinesRemoved))

        self.update()

        
    def paintEvent(self, event):
        
        painter = QtGui.QPainter(self)
        rect = self.contentsRect()

        boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()

        for i in range(Board.BoardHeight):
            for j in range(Board.BoardWidth):
                shape = self.shapeAt(j, Board.BoardHeight - i - 1)
                
                if shape != Tetrominoe.NoShape:
                    self.drawSquare(painter,
                        rect.left() + j * self.squareWidth(),
                        boardTop + i * self.squareHeight(), shape)

        if self.curPiece.shape() != Tetrominoe.NoShape:
            
            for i in range(4):
                
                x = self.curX + self.curPiece.x(i)
                y = self.curY - self.curPiece.y(i)
                self.drawSquare(painter, rect.left() + x * self.squareWidth(),
                    boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
                    self.curPiece.shape())

    #按鍵相應函數
    def keyPressEvent(self, event):
        #如果遊戲沒有開始(暫停)或者curPiece爲空(遊戲結束),響應父窗口的按鍵事件,返回
        if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape:
            super(Board, self).keyPressEvent(event)
            return
        
        key = event.key()   #捕獲按鍵
        
        if key == QtCore.Qt.Key_P:  #如果按鍵爲P,暫停或者重新繼續
            self.pause()
            return
            
        if self.isPaused:   #暫停時不響應按鍵
            return
                
        elif key == QtCore.Qt.Key_Left:     #如果按下了左箭頭會嘗試向左移動(也有可能移動不了)
            self.tryMove(self.curPiece, self.curX - 1, self.curY)
            
        elif key == QtCore.Qt.Key_Right:
            self.tryMove(self.curPiece, self.curX + 1, self.curY)
            
        elif key == QtCore.Qt.Key_Down:     #按下下箭頭,向右旋轉
            self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY)
            
        elif key == QtCore.Qt.Key_Up:       #按下上箭頭,向左旋轉
            self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)
            
        elif key == QtCore.Qt.Key_Space:    #按下空格鍵,直接掉到底部
            self.dropDown()
            
        elif key == QtCore.Qt.Key_D:
            self.oneLineDown()
            
        else:
            super(Board, self).keyPressEvent(event)
                

    def timerEvent(self, event):
        '''
        we either create a new piece, after the previous one was dropped to the bottom, 
        or we move a falling piece one line down.
        '''
        if event.timerId() == self.timer.timerId():
            
            if self.isWaitingAfterLine:
                self.isWaitingAfterLine = False
                self.newPiece()
            else:
                self.oneLineDown()
                
        else:
            super(Board, self).timerEvent(event)

            
    def clearBoard(self):
        #   清除board,全部設置爲NoShape
        for i in range(Board.BoardHeight * Board.BoardWidth):
            self.board.append(Tetrominoe.NoShape)

        
    def dropDown(self):
        
        newY = self.curY
        
        while newY > 0:
            #使curPiece一直沿着y減小的方向移動,直到不能移動或者到達底部爲止
            if not self.tryMove(self.curPiece, self.curX, newY - 1):
                break              
            newY -= 1   #自減1

        self.pieceDropped()
        

    def oneLineDown(self):
        
        if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
            self.pieceDropped()
            
    #到達底部的時候
    def pieceDropped(self):
        
        for i in range(4):
            
            x = self.curX + self.curPiece.x(i)
            y = self.curY - self.curPiece.y(i)
            self.setShapeAt(x, y, self.curPiece.shape())
        
        self.removeFullLines()  #清除排滿的行

        if not self.isWaitingAfterLine: #如果不是在暫停,開始新的塊
            self.newPiece()
            

    def removeFullLines(self):
        '''
        If the piece hits the bottom, we call the removeFullLines() method. 
        We find out all full lines and remove them. 
        We do it by moving all lines above the current full line to be removed one line down. 
        Notice that we reverse the order of the lines to be removed. 
        Otherwise, it would not work correctly. 
        In our case we use a naive gravity. This means, that the pieces may be floating above empty gaps.
        '''
        numFullLines = 0
        rowsToRemove = []

        for i in range(Board.BoardHeight):
            
            n = 0   #n記錄每行shape的個數
            for j in range(Board.BoardWidth):
                if not self.shapeAt(j, i) == Tetrominoe.NoShape:
                    n = n + 1
            #如果n等於10,將行號加入要刪除的隊列
            if n == 10:
                rowsToRemove.append(i)

        rowsToRemove.reverse()  #行號隊列反置
        

        for m in rowsToRemove:
            #從m行以上的shape均下移一行
            for k in range(m, Board.BoardHeight):
                for l in range(Board.BoardWidth):
                        self.setShapeAt(l, k, self.shapeAt(l, k + 1))

        numFullLines = numFullLines + len(rowsToRemove) #統計消除的行數

        if numFullLines > 0:
            
            self.numLinesRemoved = self.numLinesRemoved + numFullLines
            self.msg2Statusbar.emit(str(self.numLinesRemoved))
                
            self.isWaitingAfterLine = True
            self.curPiece.setShape(Tetrominoe.NoShape)
            self.update()
            

    def newPiece(self):
        '''
        The newPiece() method creates randomly a new tetris piece. 
        If the piece cannot go into its initial position, the game is over.
        隨機地創建一個方塊。如果方塊不能在它起始的位置,遊戲結束。
        '''
        self.curPiece = Shape()     #當前塊
        self.curPiece.setRandomShape()  #隨機設置
        self.curX = Board.BoardWidth / 2 + 1        #current X位置在中心
        self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
        

        #將self.curPiece移動到當前的座標處,如果不能移動,遊戲結束。
        #curPiece置爲空,timer停止,顯示消息'game over'
        if not self.tryMove(self.curPiece, self.curX, self.curY):
             
            self.curPiece.setShape(Tetrominoe.NoShape)
            self.timer.stop()
            self.isStarted = False
            self.msg2Statusbar.emit("Game over")



    def tryMove(self, newPiece, newX, newY):
        '''
        如果the shape is at the edge of the board 或者 is adjacent to some other piece, 返回False
        否則的話,變動位置並返回True
        '''
        for i in range(4):
            
            x = newX + newPiece.x(i)
            y = newY - newPiece.y(i)
            '''
            如果x<0說明已經到了左邊緣;如果x>=Board.BoardWidth,說明已經到了右邊緣
            如果y<0說明已經到了底部;如果x>=Board.BoardHeight,說明已經到了最頂部
            以上情況均不能移動,返回False
            '''
            if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
                return False
            #如果當前的位置不爲空,返回False
            if self.shapeAt(x, y) != Tetrominoe.NoShape:
                return False

        self.curPiece = newPiece
        self.curX = newX    #現在的座標變爲新座標
        self.curY = newY
        self.update()   #frame更新
        
        return True
        

    def drawSquare(self, painter, x, y, shape):
        
        colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
                      0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]

        color = QtGui.QColor(colorTable[shape])
        painter.fillRect(x + 1, y + 1, self.squareWidth() - 2, 
            self.squareHeight() - 2, color)

        painter.setPen(color.light())
        painter.drawLine(x, y + self.squareHeight() - 1, x, y)
        painter.drawLine(x, y, x + self.squareWidth() - 1, y)

        painter.setPen(color.dark())
        painter.drawLine(x + 1, y + self.squareHeight() - 1,
            x + self.squareWidth() - 1, y + self.squareHeight() - 1)
        painter.drawLine(x + self.squareWidth() - 1, 
            y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)


class Tetrominoe(object):
    '''
    定義遊戲中出現的形狀,共有8種,分別用0-7表示。
    其中0表示沒有形狀,1-7表示可能出現的形狀:Z,S,Line,T,Square,L,MirroredL。
    
    相當於C++中的枚舉類型,用有意義的字符串名代替數字
    '''
    NoShape = 0
    ZShape = 1
    SShape = 2
    LineShape = 3
    TShape = 4
    SquareShape = 5
    LShape = 6
    MirroredLShape = 7


class Shape(object):
    '''
    Shape類保存每種方塊的信息
    '''
    #coordsTable tuple holds all possible coordinate values of our Tetris pieces. 0-7
    coordsTable = (
        ((0, 0),     (0, 0),     (0, 0),     (0, 0)),
        ((0, -1),    (0, 0),     (-1, 0),    (-1, 1)),
        ((0, -1),    (0, 0),     (1, 0),     (1, 1)),
        ((0, -1),    (0, 0),     (0, 1),     (0, 2)),
        ((-1, 0),    (0, 0),     (1, 0),     (0, 1)),
        ((0, 0),     (1, 0),     (0, 1),     (1, 1)),
        ((-1, -1),   (0, -1),    (0, 0),     (0, 1)),
        ((1, -1),    (0, -1),    (0, 0),     (0, 1))
    )

    def __init__(self):
        
        self.coords = [[0,0] for i in range(4)]     #[[0, 0], [0, 0], [0, 0], [0, 0]]
        self.pieceShape = Tetrominoe.NoShape

        self.setShape(Tetrominoe.NoShape)
        
    #返回當前shape類型
    def shape(self):
        return self.pieceShape
        

    def setShape(self, shape):
        
        table = Shape.coordsTable[shape]    #table是對應的tuple元組
        #將對應的table賦給self.coords
        for i in range(4):
            for j in range(2):
                self.coords[i][j] = table[i][j]

        self.pieceShape = shape
        
    #隨機獲取一個塊形狀(從1,2,3,4,5,6,7中隨機選1個)
    def setRandomShape(self):
        self.setShape(random.randint(1, 7))

    #返回index的x座標,index是從0-3,分別表示方塊對應的4個點
    def x(self, index):
        return self.coords[index][0]

    #返回index的y座標 
    def y(self, index):
        return self.coords[index][1]

    #設置當前index的x座標
    def setX(self, index, x):
        self.coords[index][0] = x

    #設置當前index的y座標
    def setY(self, index, y):
        self.coords[index][1] = y

    #返回當前塊的最小x座標
    def minX(self): 
        m = self.coords[0][0]   
        for i in range(4):
            m = min(m, self.coords[i][0])
        return m

    #返回當前塊的最大x座標   
    def maxX(self):
        m = self.coords[0][0]
        for i in range(4):
            m = max(m, self.coords[i][0])
        return m

    #返回當前塊的最小y座標    
    def minY(self):      
        m = self.coords[0][1]
        for i in range(4):
            m = min(m, self.coords[i][1])
        return m

    #返回當前塊的最大y座標 
    def maxY(self):     
        m = self.coords[0][1]
        for i in range(4):
            m = max(m, self.coords[i][1])
        return m

        
    def rotateLeft(self):   #rotate a piece to the left
        #如果塊是方塊的話,直接返回當前塊,不做任何處理
        if self.pieceShape == Tetrominoe.SquareShape:
            return self

        result = Shape()
        result.pieceShape = self.pieceShape      
        for i in range(4):        
            #將i點的x座標換爲y座標
            result.setX(i, self.y(i))
            #將i點的y座標換爲-x座標
            result.setY(i, -self.x(i))
        #返回新的左旋後的方塊
        return result

        
    def rotateRight(self):
        #如果塊是方塊的話,直接返回當前塊,不做任何處理
        if self.pieceShape == Tetrominoe.SquareShape:
            return self

        result = Shape()
        result.pieceShape = self.pieceShape    
        for i in range(4):
            #將i點的x座標換爲-y座標
            result.setX(i, -self.y(i))
            #將i點的y座標換爲x座標
            result.setY(i, self.x(i))
        #返回新的右旋後的方塊
        return result


'''
The game is simplified a bit so that it is easier to understand. 
The game starts immediately after it is launched. 
We can pause the game by pressing the p key. 
The space key will drop the Tetris piece instantly to the bottom. 
The game goes at constant speed, no acceleration is implemented. 
The score is the number of lines that we have removed.

'''
def main():
    #創建一個界面app
    app = QtGui.QApplication([])
    #創建一個俄羅斯方塊類
    tetris = Tetris()   
    #進入主循環
    app.exec_()


if __name__ == '__main__':
    main()


源代碼來自:http://zetcode.com/gui/pyqt4/thetetrisgame/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章