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

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

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

模版——用一个列表存放形状被翻转后的所有可能样式。全部存放在变量里,变量名字如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

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

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

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

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

在看这段定义。

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变量决定。如果自从上个方块掉落了一个格子后过去了足够的时间,那么上面代码就会再让方块移动一个格子。

(0)

相关推荐

  • 基于Python实现的扫雷游戏实例代码

    本文实例借鉴mvc模式,核心数据为model,维护1个矩阵,0表无雷,1表雷,-1表已经检测过. 本例使用python的tkinter做gui,由于没考虑可用性问题,因此UI比较难看,pygame更有趣更强大更好看,做这些小游戏更合适,感兴趣的读者可以尝试一下! 具体的功能代码如下: # -*- coding: utf-8 -*- import random import sys from Tkinter import * class Model: """ 核心数据类,维护一

  • Python实现的破解字符串找茬游戏算法示例

    本文实例讲述了Python实现的破解字符串找茬游戏算法.分享给大家供大家参考,具体如下: 最近在一个QQ群里发现有那种机器人, 发出来字符串找茬游戏: 有点类似于: 没没没没没没没没没没没没没没没没没没 没没没没没没没没没没没没没没没没没没 没没没没没役没没没没没没没没没没没没 没没没没没没没没役没没没没没没没没没 没没没没没没没没没没没没没没没没没没 没没没没没没没没没没没没没没没没没没 玩法就是用户发消息到群里: #找茬 然后群里有个自动聊天的机器人, 他接到这句话之后, 会将上面一大堆文字

  • python基础教程之实现石头剪刀布游戏示例

    下面是规则.你和你的对手,在同一时间做出特定的手势,必须是下面一种手势:石头,剪子,布.胜利者从下面的规则中产生,这个规则本身是个悖论.(a) 布包石头.(b)石头砸剪子,(c)剪子剪破布.在你的计算机版本中,用户输入她/他的选项,计算机找一个随机选项,然后由你的程序来决定一个胜利者或者平手.注意:最好的算法是尽量少的使用 if 语句 复制代码 代码如下: #coding:utf-8 import randomguess_list = ["石头","剪刀",&quo

  • 用Python实现QQ游戏大家来找茬辅助工具

    好久没写技术相关的文章,这次写篇有意思的,关于一个有意思的游戏--QQ找茬,关于一种有意思的语言--Python,关于一个有意思的库--Qt. 这是一个用于QQ大家来找茬(美女找茬)的辅助外挂,开发的原因是看到老爸天天在玩这个游戏,分数是惨不忍睹的负4000多.他玩游戏有他的乐趣,并不很在意输赢,我做这个也只是自我娱乐,顺便讨他个好,毕竟我们搞编程的实在难有机会在父辈面前露露手.本来是想写个很简单的东西,但由于过程中老爸的多次嘲讽,逼得我不得不尽力完善,最后形成了一个小小的产品. 接触Pytho

  • Python写的贪吃蛇游戏例子

    第一次用Python写这种比较实用且好玩的东西,权当练手吧 游戏说明: * P键控制"暂停/开始"* 方向键控制贪吃蛇的方向 源代码如下: 复制代码 代码如下: from Tkinter import *import tkMessageBox,sysfrom random import randint class Grid(object):    def __init__(self,master=None,window_width=800,window_height=600,grid_

  • 朴素贝叶斯算法的python实现方法

    本文实例讲述了朴素贝叶斯算法的python实现方法.分享给大家供大家参考.具体实现方法如下: 朴素贝叶斯算法优缺点 优点:在数据较少的情况下依然有效,可以处理多类别问题 缺点:对输入数据的准备方式敏感 适用数据类型:标称型数据 算法思想: 比如我们想判断一个邮件是不是垃圾邮件,那么我们知道的是这个邮件中的词的分布,那么我们还要知道:垃圾邮件中某些词的出现是多少,就可以利用贝叶斯定理得到. 朴素贝叶斯分类器中的一个假设是:每个特征同等重要 函数 loadDataSet() 创建数据集,这里的数据集

  • python实现的生成随机迷宫算法核心代码分享(含游戏完整代码)

    完整代码下载:http://xiazai.jb51.net/201407/tools/python-migong.rar 最近研究了下迷宫的生成算法,然后做了个简单的在线迷宫游戏.游戏地址和对应的开源项目地址可以通过上面的链接找到.开源项目中没有包含服务端的代码,因为服务端的代码实在太简单了.下面将简单的介绍下随机迷宫的生成算法.一旦理解后你会发现这个算法到底有多简单. 1.将迷宫地图分成多个房间,每个房间都有四面墙. 2.让"人"从地图任意一点A出发,开始在迷宫里游荡.从A房间的1/

  • Python 连连看连接算法

    功能:为连连看游戏提供连接算法 说明:模块中包含一个Point类,该类是游戏的基本单元"点",该类包含属性:x,y,value. 其中x,y代表了该点的坐标,value代表该点的特征:0代表没有被填充,1-8代表被填充为游戏图案,9代表被填充为墙壁 模块中还包含一个名为points的Point列表,其中保存着整个游戏界面中的每个点 使用模块的时候应首先调用createPoints方法,初始化游戏界面中每个点,然后可通过points访问到每个点,继而初始化界面 模块中核心的方法是link

  • 一步步教你用Python实现2048小游戏

    前言 2048游戏规则:简单的移动方向键让数字叠加,并且获得这些数字每次叠加后的得分,当出现2048这个数字时游戏胜利.同时每次移动方向键时,都会在这个4*4的方格矩阵的空白区域随机产生一个数字2或者4,如果方格被数字填满了,那么就GameOver了. 主逻辑图 逻辑图解:黑色是逻辑层,蓝色是外部方法,红色是类内方法,稍后即可知道~ 下面容我逐行解释主逻辑main()函数,并且在其中穿叉外部定义的函数与类. 主逻辑代码解读(完整代码见文末) 主逻辑main如下,之后的是对主函数中的一些方法的解读

  • Python算法之栈(stack)的实现

    本文以实例形式展示了Python算法中栈(stack)的实现,对于学习数据结构域算法有一定的参考借鉴价值.具体内容如下: 1.栈stack通常的操作: Stack() 建立一个空的栈对象 push() 把一个元素添加到栈的最顶层 pop() 删除栈最顶层的元素,并返回这个元素 peek()  返回最顶层的元素,并不删除它 isEmpty()  判断栈是否为空 size()  返回栈中元素的个数 2.简单案例以及操作结果: Stack Operation Stack Contents Return

  • Python实现破解猜数游戏算法示例

    本文实例讲述了Python实现破解猜数游戏算法.分享给大家供大家参考,具体如下: QQ群里的聊天机器人会发起猜数小游戏. 玩法如下: 1. 用户发 #猜数    到群里 2. 机器人响应: 猜数已经开始, 范围是1-10000之间的某个数 3. 你发送 #猜数[123] 到群里 4. 机器人响应: 大了或者小了, 或者恭喜你猜中了 5. 你根据刚才猜的123, 和返回, 猜一个更小或更大的数, 发送 #猜数[111] , 即返回第2步 那么最好的猜测方法肯定是找居中的数了, 由于心算耗时, 所以

随机推荐