Python使用pygame模块编写俄罗斯方块游戏的代码实例

发表于 5年以前  | 总阅读数:902 次

文章先介绍了关于俄罗斯方块游戏的几个术语。

  • 边框――由10*20个空格组成,方块就落在这里面。
  • 盒子――组成方块的其中小方块,是组成方块的基本单元。
  • 方块――从边框顶掉下的东西,游戏者可以翻转和改变位置。每个方块由4个盒子组成。
  • 形状――不同类型的方块。这里形状的名字被叫做T, S, Z ,J, L, I , O。如下图所示:

2015128143118229.png \(490×159\)

模版――用一个列表存放形状被翻转后的所有可能样式。全部存放在变量里,变量名字如S_SHAPE_TEMPLATE or J_SHAPE_TEMPLATE
着陆――当一个方块到达边框的底部或接触到在其他的盒子话,我们就说这个方块着陆了。那样的话,另一个方块就会开始下落。
下面先把代码敲一遍,试着了解作者意图,体会俄罗斯方块游戏的制作过程。


    import random, time, pygame, sys 
    from pygame.locals import * 

    FPS = 25 
    WINDOWWIDTH = 640 
    WINDOWHEIGHT = 480 
    BOXSIZE = 20 
    BOARDWIDTH = 10 
    BOARDHEIGHT = 20 
    BLANK = '.' 

    MOVESIDEWAYSFREQ = 0.15 
    MOVEDOWNFREQ = 0.1 

    XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * BOXSIZE) / 2) 
    TOPMARGIN = WINDOWHEIGHT - (BOARDHEIGHT * BOXSIZE) - 5 
    #        R  G  B 
    WHITE    = (255, 255, 255) 
    GRAY    = (185, 185, 185) 
    BLACK    = ( 0,  0,  0) 
    RED     = (155,  0,  0) 
    LIGHTRED  = (175, 20, 20) 
    GREEN    = ( 0, 155,  0) 
    LIGHTGREEN = ( 20, 175, 20) 
    BLUE    = ( 0,  0, 155) 
    LIGHTBLUE  = ( 20, 20, 175) 
    YELLOW   = (155, 155,  0) 
    LIGHTYELLOW = (175, 175, 20) 

    BORDERCOLOR = BLUE 
    BGCOLOR = BLACK 
    TEXTCOLOR = WHITE 
    TEXTSHADOWCOLOR = GRAY 
    COLORS   = (   BLUE,   GREEN,   RED,   YELLOW) 
    LIGHTCOLORS = (LIGHTBLUE, LIGHTGREEN, LIGHTRED, LIGHTYELLOW) 
    assert len(COLORS) == len(LIGHTCOLORS) # each color must have light color 

    TEMPLATEWIDTH = 5 
    TEMPLATEHEIGHT = 5 

    S_SHAPE_TEMPLATE = [['.....', 
               '.....', 
               '..OO.', 
               '.OO..', 
               '.....'], 
              ['.....', 
               '..O..', 
               '..OO.', 
               '...O.', 
               '.....']] 

    Z_SHAPE_TEMPLATE = [['.....', 
               '.....', 
               '.OO..', 
               '..OO.', 
               '.....'], 
              ['.....', 
               '..O..', 
               '.OO..', 
               '.O...', 
               '.....']] 

    I_SHAPE_TEMPLATE = [['..O..', 
               '..O..', 
               '..O..', 
               '..O..', 
               '.....'], 
              ['.....', 
               '.....', 
               'OOOO.', 
               '.....', 
               '.....']] 

    O_SHAPE_TEMPLATE = [['.....', 
               '.....', 
               '.OO..', 
               '.OO..', 
               '.....']] 

    J_SHAPE_TEMPLATE = [['.....', 
               '.O...', 
               '.OOO.', 
               '.....', 
               '.....'], 
              ['.....', 
               '..OO.', 
               '..O..', 
               '..O..', 
               '.....'], 
              ['.....', 
               '.....', 
               '.OOO.', 
               '...O.', 
               '.....'], 
              ['.....', 
               '..O..', 
               '..O..', 
               '.OO..', 
               '.....']] 

    L_SHAPE_TEMPLATE = [['.....', 
               '...O.', 
               '.OOO.', 
               '.....', 
               '.....'], 
              ['.....', 
               '..O..', 
               '..O..', 
               '..OO.', 
               '.....'], 
              ['.....', 
               '.....', 
               '.OOO.', 
               '.O...', 
               '.....'], 
              ['.....', 
               '.OO..', 
               '..O..', 
               '..O..', 
               '.....']] 

    T_SHAPE_TEMPLATE = [['.....', 
               '..O..', 
               '.OOO.', 
               '.....', 
               '.....'], 
              ['.....', 
               '..O..', 
               '..OO.', 
               '..O..', 
               '.....'], 
              ['.....', 
               '.....', 
               '.OOO.', 
               '..O..', 
               '.....'], 
              ['.....', 
               '..O..', 
               '.OO..', 
               '..O..', 
               '.....']] 

    PIECES = {'S': S_SHAPE_TEMPLATE, 
         'Z': Z_SHAPE_TEMPLATE, 
         'J': J_SHAPE_TEMPLATE, 
         'L': L_SHAPE_TEMPLATE, 
         'I': I_SHAPE_TEMPLATE, 
         'O': O_SHAPE_TEMPLATE, 
         'T': T_SHAPE_TEMPLATE} 


    def main(): 
      global FPSCLOCK, DISPLAYSURF, BASICFONT, BIGFONT 
      pygame.init() 
      FPSCLOCK = pygame.time.Clock() 
      DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 
      BASICFONT = pygame.font.Font('freesansbold.ttf', 18) 
      BIGFONT = pygame.font.Font('freesansbold.ttf', 100) 
      pygame.display.set_caption('Tetromino') 

      showTextScreen('Tetromino') 
      while True: # game loop 
        if random.randint(0, 1) == 0: 
          pygame.mixer.music.load('tetrisb.mid') 
        else: 
          pygame.mixer.music.load('tetrisc.mid') 
        pygame.mixer.music.play(-1, 0.0) 
        runGame() 
        pygame.mixer.music.stop() 
        showTextScreen('Game Over') 


    def runGame(): 
      # setup variables for the start of the game 
      board = getBlankBoard() 
      lastMoveDownTime = time.time() 
      lastMoveSidewaysTime = time.time() 
      lastFallTime = time.time() 
      movingDown = False # note: there is no movingUp variable 
      movingLeft = False 
      movingRight = False 
      score = 0 
      level, fallFreq = calculateLevelAndFallFreq(score) 

      fallingPiece = getNewPiece() 
      nextPiece = getNewPiece() 

      while True: # game loop 
        if fallingPiece == None: 
          # No falling piece in play, so start a new piece at the top 
          fallingPiece = nextPiece 
          nextPiece = getNewPiece() 
          lastFallTime = time.time() # reset lastFallTime 

          if not isValidPosition(board, fallingPiece): 
            return # can't fit a new piece on the board, so game over 

        checkForQuit() 
        for event in pygame.event.get(): # event handling loop 
          if event.type == KEYUP: 
            if (event.key == K_p): 
              # Pausing the game 
              DISPLAYSURF.fill(BGCOLOR) 
              pygame.mixer.music.stop() 
              showTextScreen('Paused') # pause until a key press 
              pygame.mixer.music.play(-1, 0.0) 
              lastFallTime = time.time() 
              lastMoveDownTime = time.time() 
              lastMoveSidewaysTime = time.time() 
            elif (event.key == K_LEFT or event.key == K_a): 
              movingLeft = False 
            elif (event.key == K_RIGHT or event.key == K_d): 
              movingRight = False 
            elif (event.key == K_DOWN or event.key == K_s): 
              movingDown = False 

          elif event.type == KEYDOWN: 
            # moving the piece sideways 
            if (event.key == K_LEFT or event.key == K_a) and isValidPosition(board, fallingPiece, adjX=-1): 
              fallingPiece['x'] -= 1 
              movingLeft = True 
              movingRight = False 
              lastMoveSidewaysTime = time.time() 

            elif (event.key == K_RIGHT or event.key == K_d) and isValidPosition(board, fallingPiece, adjX=1): 
              fallingPiece['x'] += 1 
              movingRight = True 
              movingLeft = False 
              lastMoveSidewaysTime = time.time() 

            # rotating the piece (if there is room to rotate) 
            elif (event.key == K_UP or event.key == K_w): 
              fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(PIECES[fallingPiece['shape']]) 
              if not isValidPosition(board, fallingPiece): 
                fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']]) 
            elif (event.key == K_q): # rotate the other direction 
              fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']]) 
              if not isValidPosition(board, fallingPiece): 
                fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(PIECES[fallingPiece['shape']]) 

            # making the piece fall faster with the down key 
            elif (event.key == K_DOWN or event.key == K_s): 
              movingDown = True 
              if isValidPosition(board, fallingPiece, adjY=1): 
                fallingPiece['y'] += 1 
              lastMoveDownTime = time.time() 

            # move the current piece all the way down 
            elif event.key == K_SPACE: 
              movingDown = False 
              movingLeft = False 
              movingRight = False 
              for i in range(1, BOARDHEIGHT): 
                if not isValidPosition(board, fallingPiece, adjY=i): 
                  break 
              fallingPiece['y'] += i - 1 

        # handle moving the piece because of user input 
        if (movingLeft or movingRight) and time.time() - lastMoveSidewaysTime > MOVESIDEWAYSFREQ: 
          if movingLeft and isValidPosition(board, fallingPiece, adjX=-1): 
            fallingPiece['x'] -= 1 
          elif movingRight and isValidPosition(board, fallingPiece, adjX=1): 
            fallingPiece['x'] += 1 
          lastMoveSidewaysTime = time.time() 

        if movingDown and time.time() - lastMoveDownTime > MOVEDOWNFREQ and isValidPosition(board, fallingPiece, adjY=1): 
          fallingPiece['y'] += 1 
          lastMoveDownTime = time.time() 

        # let the piece fall if it is time to fall 
        if time.time() - lastFallTime > fallFreq: 
          # see if the piece has landed 
          if not isValidPosition(board, fallingPiece, adjY=1): 
            # falling piece has landed, set it on the board 
            addToBoard(board, fallingPiece) 
            score += removeCompleteLines(board) 
            level, fallFreq = calculateLevelAndFallFreq(score) 
            fallingPiece = None 
          else: 
            # piece did not land, just move the piece down 
            fallingPiece['y'] += 1 
            lastFallTime = time.time() 

        # drawing everything on the screen 
        DISPLAYSURF.fill(BGCOLOR) 
        drawBoard(board) 
        drawStatus(score, level) 
        drawNextPiece(nextPiece) 
        if fallingPiece != None: 
          drawPiece(fallingPiece) 

        pygame.display.update() 
        FPSCLOCK.tick(FPS) 


    def makeTextObjs(text, font, color): 
      surf = font.render(text, True, color) 
      return surf, surf.get_rect() 


    def terminate(): 
      pygame.quit() 
      sys.exit() 


    def checkForKeyPress(): 
      # Go through event queue looking for a KEYUP event. 
      # Grab KEYDOWN events to remove them from the event queue. 
      checkForQuit() 

      for event in pygame.event.get([KEYDOWN, KEYUP]): 
        if event.type == KEYDOWN: 
          continue 
        return event.key 
      return None 


    def showTextScreen(text): 
      # This function displays large text in the 
      # center of the screen until a key is pressed. 
      # Draw the text drop shadow 
      titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTSHADOWCOLOR) 
      titleRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)) 
      DISPLAYSURF.blit(titleSurf, titleRect) 

      # Draw the text 
      titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTCOLOR) 
      titleRect.center = (int(WINDOWWIDTH / 2) - 3, int(WINDOWHEIGHT / 2) - 3) 
      DISPLAYSURF.blit(titleSurf, titleRect) 

      # Draw the additional "Press a key to play." text. 
      pressKeySurf, pressKeyRect = makeTextObjs('Press a key to play.', BASICFONT, TEXTCOLOR) 
      pressKeyRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 100) 
      DISPLAYSURF.blit(pressKeySurf, pressKeyRect) 

      while checkForKeyPress() == None: 
        pygame.display.update() 
        FPSCLOCK.tick() 


    def checkForQuit(): 
      for event in pygame.event.get(QUIT): # get all the QUIT events 
        terminate() # terminate if any QUIT events are present 
      for event in pygame.event.get(KEYUP): # get all the KEYUP events 
        if event.key == K_ESCAPE: 
          terminate() # terminate if the KEYUP event was for the Esc key 
        pygame.event.post(event) # put the other KEYUP event objects back 


    def calculateLevelAndFallFreq(score): 
      # Based on the score, return the level the player is on and 
      # how many seconds pass until a falling piece falls one space. 
      level = int(score / 10) + 1 
      fallFreq = 0.27 - (level * 0.02) 
      return level, fallFreq 

    def getNewPiece(): 
      # return a random new piece in a random rotation and color 
      shape = random.choice(list(PIECES.keys())) 
      newPiece = {'shape': shape, 
            'rotation': random.randint(0, len(PIECES[shape]) - 1), 
            'x': int(BOARDWIDTH / 2) - int(TEMPLATEWIDTH / 2), 
            'y': -2, # start it above the board (i.e. less than 0) 
            'color': random.randint(0, len(COLORS)-1)} 
      return newPiece 


    def addToBoard(board, piece): 
      # fill in the board based on piece's location, shape, and rotation 
      for x in range(TEMPLATEWIDTH): 
        for y in range(TEMPLATEHEIGHT): 
          if PIECES[piece['shape']][piece['rotation']][y][x] != BLANK: 
            board[x + piece['x']][y + piece['y']] = piece['color'] 


    def getBlankBoard(): 
      # create and return a new blank board data structure 
      board = [] 
      for i in range(BOARDWIDTH): 
        board.append([BLANK] * BOARDHEIGHT) 
      return board 


    def isOnBoard(x, y): 
      return x >= 0 and x < BOARDWIDTH and y < BOARDHEIGHT 


    def isValidPosition(board, piece, adjX=0, adjY=0): 
      # Return True if the piece is within the board and not colliding 
      for x in range(TEMPLATEWIDTH): 
        for y in range(TEMPLATEHEIGHT): 
          isAboveBoard = y + piece['y'] + adjY < 0 
          if isAboveBoard or PIECES[piece['shape']][piece['rotation']][y][x] == BLANK: 
            continue 
          if not isOnBoard(x + piece['x'] + adjX, y + piece['y'] + adjY): 
            return False 
          if board[x + piece['x'] + adjX][y + piece['y'] + adjY] != BLANK: 
            return False 
      return True 

    def isCompleteLine(board, y): 
      # Return True if the line filled with boxes with no gaps. 
      for x in range(BOARDWIDTH): 
        if board[x][y] == BLANK: 
          return False 
      return True 


    def removeCompleteLines(board): 
      # Remove any completed lines on the board, move everything above them down, and return the number of complete lines. 
      numLinesRemoved = 0 
      y = BOARDHEIGHT - 1 # start y at the bottom of the board 
      while y >= 0: 
        if isCompleteLine(board, y): 
          # Remove the line and pull boxes down by one line. 
          for pullDownY in range(y, 0, -1): 
            for x in range(BOARDWIDTH): 
              board[x][pullDownY] = board[x][pullDownY-1] 
          # Set very top line to blank. 
          for x in range(BOARDWIDTH): 
            board[x][0] = BLANK 
          numLinesRemoved += 1 
          # Note on the next iteration of the loop, y is the same. 
          # This is so that if the line that was pulled down is also 
          # complete, it will be removed. 
        else: 
          y -= 1 # move on to check next row up 
      return numLinesRemoved 


    def convertToPixelCoords(boxx, boxy): 
      # Convert the given xy coordinates of the board to xy 
      # coordinates of the location on the screen. 
      return (XMARGIN + (boxx * BOXSIZE)), (TOPMARGIN + (boxy * BOXSIZE)) 


    def drawBox(boxx, boxy, color, pixelx=None, pixely=None): 
      # draw a single box (each tetromino piece has four boxes) 
      # at xy coordinates on the board. Or, if pixelx & pixely 
      # are specified, draw to the pixel coordinates stored in 
      # pixelx & pixely (this is used for the "Next" piece). 
      if color == BLANK: 
        return 
      if pixelx == None and pixely == None: 
        pixelx, pixely = convertToPixelCoords(boxx, boxy) 
      pygame.draw.rect(DISPLAYSURF, COLORS[color], (pixelx + 1, pixely + 1, BOXSIZE - 1, BOXSIZE - 1)) 
      pygame.draw.rect(DISPLAYSURF, LIGHTCOLORS[color], (pixelx + 1, pixely + 1, BOXSIZE - 4, BOXSIZE - 4)) 


    def drawBoard(board): 
      # draw the border around the board 
      pygame.draw.rect(DISPLAYSURF, BORDERCOLOR, (XMARGIN - 3, TOPMARGIN - 7, (BOARDWIDTH * BOXSIZE) + 8, (BOARDHEIGHT * BOXSIZE) + 8), 5) 

      # fill the background of the board 
      pygame.draw.rect(DISPLAYSURF, BGCOLOR, (XMARGIN, TOPMARGIN, BOXSIZE * BOARDWIDTH, BOXSIZE * BOARDHEIGHT)) 
      # draw the individual boxes on the board 
      for x in range(BOARDWIDTH): 
        for y in range(BOARDHEIGHT): 
          drawBox(x, y, board[x][y]) 


    def drawStatus(score, level): 
      # draw the score text 
      scoreSurf = BASICFONT.render('Score: %s' % score, True, TEXTCOLOR) 
      scoreRect = scoreSurf.get_rect() 
      scoreRect.topleft = (WINDOWWIDTH - 150, 20) 
      DISPLAYSURF.blit(scoreSurf, scoreRect) 

      # draw the level text 
      levelSurf = BASICFONT.render('Level: %s' % level, True, TEXTCOLOR) 
      levelRect = levelSurf.get_rect() 
      levelRect.topleft = (WINDOWWIDTH - 150, 50) 
      DISPLAYSURF.blit(levelSurf, levelRect) 


    def drawPiece(piece, pixelx=None, pixely=None): 
      shapeToDraw = PIECES[piece['shape']][piece['rotation']] 
      if pixelx == None and pixely == None: 
        # if pixelx & pixely hasn't been specified, use the location stored in the piece data structure 
        pixelx, pixely = convertToPixelCoords(piece['x'], piece['y']) 

      # draw each of the boxes that make up the piece 
      for x in range(TEMPLATEWIDTH): 
        for y in range(TEMPLATEHEIGHT): 
          if shapeToDraw[y][x] != BLANK: 
            drawBox(None, None, piece['color'], pixelx + (x * BOXSIZE), pixely + (y * BOXSIZE)) 


    def drawNextPiece(piece): 
      # draw the "next" text 
      nextSurf = BASICFONT.render('Next:', True, TEXTCOLOR) 
      nextRect = nextSurf.get_rect() 
      nextRect.topleft = (WINDOWWIDTH - 120, 80) 
      DISPLAYSURF.blit(nextSurf, nextRect) 
      # draw the "next" piece 
      drawPiece(piece, pixelx=WINDOWWIDTH-120, pixely=100) 


    if __name__ == '__main__': 
      main() 

代码一开始仍是一些变量的初始化,我们这里还加载了time模块,后面会用到。BOXSIZE, BOARDWIDTH, BOARDHEIGHT与前面贪吃蛇相关初始化类似,使其与屏幕像素点联系起来。


    MOVESIDEWAYSFREQ = 0.15
    MOVEDOWNFREQ = 0.1

这两个变量的作用是这样的,每当游戏者按下左键或右键,下降的方块相应的向左或右移一个格子。然而游戏者也可以一直按下方向左键或右键让方块保持移动。MOVESIDEWAYSFREQ这个固定值表示如果一直按下方向左键或右键那么每0.15秒方块才会继续移动。
MOVEDOWNFREQ 这个固定值与上面的是一样的除了它是告诉当游戏者一直按下方向下键时方块下落的频率。


    XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * BOXSIZE) / 2)
    TOPMARGIN = WINDOWHEIGHT - (BOARDHEIGHT * BOXSIZE) - 5

这两句的意思就看下面这个图就明白了。

2015128143232882.png \(477×389\)

然后是一些颜色值的定义。其中要注意的是COLORS和LIGHTCOLORS,COLORS是组成方块的小方块的颜色,而LIGHTCOLORS是围绕在小方块周围的颜色,为了强调出轮廓而设计的。
接着是定义方块了。游戏必须知道每个类型的方块有多少种形状,在这里我们用在列表中嵌入含有字符串的列表来构成这个模版,一个方块类型的模版含有了这个方块可能变换的所有形状。比如I的模版如下:


    I_SHAPE_TEMPLATE = [['..O..', 
               '..O..', 
               '..O..', 
               '..O..', 
               '.....'], 
              ['.....', 
               '.....', 
               'OOOO.', 
               '.....', 
               '.....']] 

TEMPLATEWIDTH = 5和TEMPLATEHEIGHT = 5则表示组成形状的行和列,如下图所示:

2015128143252602.png \(316×322\)

在看这段定义。


    PIECES = {'S': S_SHAPE_TEMPLATE, 
         'Z': Z_SHAPE_TEMPLATE, 
         'J': J_SHAPE_TEMPLATE, 
         'L': L_SHAPE_TEMPLATE, 
         'I': I_SHAPE_TEMPLATE, 
         'O': O_SHAPE_TEMPLATE, 
         'T': T_SHAPE_TEMPLATE} 

PIECES这个变量是一个字典,里面储存了所有的不同模版。因为每个又有一个类型的方块的所有变换形状。那就意味着PIECES变量包含了每个类型的方块和所有的的变换形状。这就是存放我们游戏中用到的形状的数据结构。(又加强了对字典的理解)
主函数main()
主函数的前部分主要是创建一些全局变量和在游戏开始之前显示一个开始画面。


    while True: # game loop 
      if random.randint(0, 1) == 0: 
        pygame.mixer.music.load('tetrisb.mid') 
      else: 
        pygame.mixer.music.load('tetrisc.mid') 
      pygame.mixer.music.play(-1, 0.0) 
      runGame() 
      pygame.mixer.music.stop() 
      showTextScreen('Game Over') 

上面这段代码中runGame()是程序的核心部分。循环中首先简单的随机决定采用哪个背景音乐。然后调用runGame(),当游戏失败,runGame()就会返回到main()函数,这时会停止背景音乐和显示游戏失败的画面。
当游戏者按下一个键,showTextScreen()显示游戏失败的函数就会返回。游戏循环会再次开始然后继续下一次游戏。
runGame()


    def runGame(): 
      # setup variables for the start of the game 
      board = getBlankBoard() 
      lastMoveDownTime = time.time() 
      lastMoveSidewaysTime = time.time() 
      lastFallTime = time.time() 
      movingDown = False # note: there is no movingUp variable 
      movingLeft = False 
      movingRight = False 
      score = 0 
      level, fallFreq = calculateLevelAndFallFreq(score) 

      fallingPiece = getNewPiece() 
      nextPiece = getNewPiece() 

在游戏开始和方块掉落之前,我们需要初始化一些跟游戏开始相关的变量。fallingPiece变量被赋值成当前掉落的变量,nextPiece变量被赋值成游戏者可以在屏幕NEXT区域看见的下一个方块。


    while True: # game loop 
      if fallingPiece == None: 
        # No falling piece in play, so start a new piece at the top 
        fallingPiece = nextPiece 
        nextPiece = getNewPiece() 
        lastFallTime = time.time() # reset lastFallTime 

        if not isValidPosition(board, fallingPiece): 
          return # can't fit a new piece on the board, so game over 

      checkForQuit() 

这部分包含了当方块往底部掉落时的的所有代码。fallingPiece变量在方块着陆后被设置成None。这意味着nextPiece变量中的下一个方块应该被赋值给fallingPiece变量,然后一个随机的方块又会被赋值给nextPiece变量。lastFallTime变量也被赋值成当前时间,这样我们就可以通过fallFreq变量控制方块下落的频率。
来自getNewPiece函数的方块只有一部分被放置在方框区域中。但是如果这是一个非法的位置,比如此时游戏方框已经被填满(isVaildPostion()函数返回False),那么我们就知道方框已经满了,游戏者输掉了游戏。当这些发生时,runGame()函数就会返回。
事件处理循环
事件循环主要处理当翻转方块,移动方块时或者暂停游戏时的一些事情。
暂停游戏


    if (event.key == K_p): 
      # Pausing the game 
      DISPLAYSURF.fill(BGCOLOR) 
      pygame.mixer.music.stop() 
      showTextScreen('Paused') # pause until a key press 
      pygame.mixer.music.play(-1, 0.0) 
      lastFallTime = time.time() 
      lastMoveDownTime = time.time() 
      lastMoveSidewaysTime = time.time() 

如果游戏者按下P键,游戏就会暂停。我们应该隐藏掉游戏界面以防止游戏者作弊(否则游戏者会看着画面思考怎么处理方块),用DISPLAYSURF.fill(BGCOLOR)就可以实现这个效果。注意的是我们还要保存一些时间变量值。


    elif (event.key == K_LEFT or event.key == K_a): 
      movingLeft = False 
    elif (event.key == K_RIGHT or event.key == K_d): 
      movingRight = False 
    elif (event.key == K_DOWN or event.key == K_s): 
      movingDown = False 

停止按下方向键或ASD键会把moveLeft,moveRight,movingDown变量设置为False.,表明游戏者不再想要在此方向上移动方块。后面的代码会基于moving变量处理一些事情。注意的上方向键和W键是用来翻转方块的而不是移动方块。这就是为什么没有movingUp变量.


    elif event.type == KEYDOWN: 
      # moving the piece sideways 
      if (event.key == K_LEFT or event.key == K_a) and isValidPosition(board, fallingPiece, adjX=-1): 
        fallingPiece['x'] -= 1 
        movingLeft = True 
        movingRight = False 
        lastMoveSidewaysTime = time.time() 

当左方向键按下(而且往左移动是有效的,通过调用isVaildPosition()函数知道的),那么我们应该改变一个方块的位置使其向左移动一个通过让rallingPiece['x']减1.isVaildPosition()函数有个参数选项是adjX和adjY.平常,isVaildPostion()函数检查方块的位置通过函数的第二个参数的传递。然而,有时我们不想检查方块当前的位置,而是偏离当前方向几个格子的位置。
比如adjX=-1,则表示向左移动一个格子后方块的位置,为+1则表示向右移动一个格子后的位置。adjY同理如此。
movingLeft变量会被设置为True,确保方块不会向右移动,此时movingRight变量设置为False。同时需要更新lastMoveSidewaysTime的值。
这个lastMoveSidewaysTime变量设置的原因是这样。因为游戏者有可能一直按着方向键让其方块移动。如果moveLeft被设置为True,程序就会知道方向左键已经被按下。如果在lastMoveSidewaysTime变量储存的时间基础上,0.15秒(储存在MOVESIDEAYSFREQ变量中)过去后,那么此时程序就会将方块再次向左移动一个格子。


    elif (event.key == K_UP or event.key == K_w): 
              fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(PIECES[fallingPiece['shape']]) 
              if not isValidPosition(board, fallingPiece): 
                fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']]) 

如果方向键上或W键被按下,那么就会翻转方块。上面的代码做的就是将储存在fallingPiece字典中的'rotation'键的键值加1.然而,当增加的'rotation'键值大于所有当前类型方块的形状的数目的话(此变量储存在len(SHAPES[fallingPiece['shape']])变量中),那么它翻转到最初的形状。


    if not isValidPosition(board, fallingPiece): 
                fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']]) 

如果翻转后的形状无效因为其中的一些小方块已经超过边框的范围,那么我们就要把它变回原来的形状通过将fallingPiece['rotation')减去1.


    elif (event.key == K_q): # rotate the other direction 
              fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']]) 
              if not isValidPosition(board, fallingPiece): 
                fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(PIECES[fallingPiece['shape']]) 

这段代码与上面之前的那段代码是一个意思,不同的是这段代码是当游戏者按下Q键时翻转方块朝相反的方向。这里我们减去1而不是加1.


    elif (event.key == K_DOWN or event.key == K_s): 
      movingDown = True 
      if isValidPosition(board, fallingPiece, adjY=1): 
        fallingPiece['y'] += 1 
      lastMoveDownTime = time.time() 

如果下键被按下,游戏者此时希望方块下降的比平常快。fallingPiece['y'] += 1使方块下落一个格子(前提是这是一个有效的下落)moveDown被设置为True,lastMoceDownTime变量也被设置为当前时间。这个变量以后将被检查当方向下键一直按下时从而保证方块以一个比平常快的速率下降。


    elif event.key == K_SPACE: 
      movingDown = False 
      movingLeft = False 
      movingRight = False 
      for i in range(1, BOARDHEIGHT): 
        if not isValidPosition(board, fallingPiece, adjY=i): 
          break 
      fallingPiece['y'] += i - 1 

当游戏者按下空格键,方块将会迅速的下落至着陆。程序首先需要找出到它着陆需要下降个多少个格子。其中有关moving的三个变量都要被设置为False(保证程序后面部分的代码知道游戏者已经停止了按下所有的方向键)。


    if (movingLeft or movingRight) and time.time() - lastMoveSidewaysTime > MOVESIDEWAYSFREQ: 
      if movingLeft and isValidPosition(board, fallingPiece, adjX=-1): 
        fallingPiece['x'] -= 1 
      elif movingRight and isValidPosition(board, fallingPiece, adjX=1): 
        fallingPiece['x'] += 1 
      lastMoveSidewaysTime = time.time() 

这段代码是处理一直按下某个方向键时的情况。
如果用户按住键超过0.15秒。那么表达式(movingLeft or movingRight) and time.time() - lastMoveSidewaysTime > MOVESIDEWAYSFREQ:返回True。这样的话我们就可以移动方块向左或向右移动一个格子。
这个做法是很用的,因为如果用户重复的按下方向键让方块移动多个格子是很烦人的。好的做法是,用户可以按住方向键让方块保持移动直到松开键为止。最后别忘了更新lastMoveSideWaysTime变量。


    if movingDown and time.time() - lastMoveDownTime > MOVEDOWNFREQ and isValidPosition(board, fallingPiece, adjY=1): 
      fallingPiece['y'] += 1 
      lastMoveDownTime = time.time() 

这段代码的意思跟上面的代码差不多。


    if time.time() - lastFallTime > fallFreq: 
      # see if the piece has landed 
      if not isValidPosition(board, fallingPiece, adjY=1): 
        # falling piece has landed, set it on the board 
        addToBoard(board, fallingPiece) 
        score += removeCompleteLines(board) 
        level, fallFreq = calculateLevelAndFallFreq(score) 
        fallingPiece = None 
      else: 
        # piece did not land, just move the piece down 
        fallingPiece['y'] += 1 
        lastFallTime = time.time() 

方块自然下落的速率由lastFallTime变量决定。如果自从上个方块掉落了一个格子后过去了足够的时间,那么上面代码就会再让方块移动一个格子。

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录