01/10/2018, 16:52

Hỏi về code trò chơi Tetris bằng python

Trong lúc tìm hiểu về cách làm game Tetris bằng pygame, em có tìm được một bài mẫu(Trong cuốn Invent with python của AI Sweigart), code ông ấy viết như sau:

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

Mọi người giúp em hiểu cách mà người viết code trên đã sử dụng để có thể hiển thị các khối hộp với? Đồng thời em cũng cần ai đó giải thích kĩ giúp em về hàm getBlankBoard với hàm isValidPosition,về hàm addtoBoard(và cả hàm isValidPosition) tại sao lại là [y][x] mà ko phải là [x][y]?

Bài liên quan
0