SoFunction
Updated on 2024-11-20

Python code example for writing a Tetris game using the pygame module

The article starts with a few terms about the game of Tetris.

  • Border - consists of 10*20 spaces in which the squares fall.
  • Box - one of the smaller squares that make up the cube, the basic unit that makes up the cube.
  • Cubes - things that fall from the top of the borders, which the gamer can flip and change the position. Each square consists of 4 boxes.
  • Shapes - different types of squares. Here the names of the shapes are called T, S, Z ,J, L, I , O. As shown below:

 (490×159)

Template - A list to store all possible styles for the shape after it has been flipped. All stored in variables with names like S_SHAPE_TEMPLATE or J_SHAPE_TEMPLATE
Landing - We say that a square has landed when it reaches the bottom of a border or touches another box. In that case, the other box will start to fall.
Here's a first crack at the code to try to understand the author's intent and get a feel for how the Tetris game was made.

import random, time, pygame, sys 
from  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 
  () 
  FPSCLOCK = () 
  DISPLAYSURF = .set_mode((WINDOWWIDTH, WINDOWHEIGHT)) 
  BASICFONT = ('', 18) 
  BIGFONT = ('', 100) 
  .set_caption('Tetromino') 
 
  showTextScreen('Tetromino') 
  while True: # game loop 
    if (0, 1) == 0: 
      ('') 
    else: 
      ('') 
    (-1, 0.0) 
    runGame() 
    () 
    showTextScreen('Game Over') 
 
 
def runGame(): 
  # setup variables for the start of the game 
  board = getBlankBoard() 
  lastMoveDownTime = () 
  lastMoveSidewaysTime = () 
  lastFallTime = () 
  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 = () # reset lastFallTime 
 
      if not isValidPosition(board, fallingPiece): 
        return # can't fit a new piece on the board, so game over 
 
    checkForQuit() 
    for event in (): # event handling loop 
      if  == KEYUP: 
        if ( == K_p): 
          # Pausing the game 
          (BGCOLOR) 
          () 
          showTextScreen('Paused') # pause until a key press 
          (-1, 0.0) 
          lastFallTime = () 
          lastMoveDownTime = () 
          lastMoveSidewaysTime = () 
        elif ( == K_LEFT or  == K_a): 
          movingLeft = False 
        elif ( == K_RIGHT or  == K_d): 
          movingRight = False 
        elif ( == K_DOWN or  == K_s): 
          movingDown = False 
 
      elif  == KEYDOWN: 
        # moving the piece sideways 
        if ( == K_LEFT or  == K_a) and isValidPosition(board, fallingPiece, adjX=-1): 
          fallingPiece['x'] -= 1 
          movingLeft = True 
          movingRight = False 
          lastMoveSidewaysTime = () 
 
        elif ( == K_RIGHT or  == K_d) and isValidPosition(board, fallingPiece, adjX=1): 
          fallingPiece['x'] += 1 
          movingRight = True 
          movingLeft = False 
          lastMoveSidewaysTime = () 
 
        # rotating the piece (if there is room to rotate) 
        elif ( == K_UP or  == 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 ( == 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 ( == K_DOWN or  == K_s): 
          movingDown = True 
          if isValidPosition(board, fallingPiece, adjY=1): 
            fallingPiece['y'] += 1 
          lastMoveDownTime = () 
 
        # move the current piece all the way down 
        elif  == 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 () - 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 = () 
 
    if movingDown and () - lastMoveDownTime > MOVEDOWNFREQ and isValidPosition(board, fallingPiece, adjY=1): 
      fallingPiece['y'] += 1 
      lastMoveDownTime = () 
 
    # let the piece fall if it is time to fall 
    if () - 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 = () 
 
    # drawing everything on the screen 
    (BGCOLOR) 
    drawBoard(board) 
    drawStatus(score, level) 
    drawNextPiece(nextPiece) 
    if fallingPiece != None: 
      drawPiece(fallingPiece) 
 
    () 
    (FPS) 
 
 
def makeTextObjs(text, font, color): 
  surf = (text, True, color) 
  return surf, surf.get_rect() 
 
 
def terminate(): 
  () 
  () 
 
 
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 ([KEYDOWN, KEYUP]): 
    if  == KEYDOWN: 
      continue 
    return  
  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) 
   = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)) 
  (titleSurf, titleRect) 
 
  # Draw the text 
  titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTCOLOR) 
   = (int(WINDOWWIDTH / 2) - 3, int(WINDOWHEIGHT / 2) - 3) 
  (titleSurf, titleRect) 
 
  # Draw the additional "Press a key to play." text. 
  pressKeySurf, pressKeyRect = makeTextObjs('Press a key to play.', BASICFONT, TEXTCOLOR) 
   = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 100) 
  (pressKeySurf, pressKeyRect) 
 
  while checkForKeyPress() == None: 
    () 
    () 
 
 
def checkForQuit(): 
  for event in (QUIT): # get all the QUIT events 
    terminate() # terminate if any QUIT events are present 
  for event in (KEYUP): # get all the KEYUP events 
    if  == K_ESCAPE: 
      terminate() # terminate if the KEYUP event was for the Esc key 
    (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 = (list(())) 
  newPiece = {'shape': shape, 
        'rotation': (0, len(PIECES[shape]) - 1), 
        'x': int(BOARDWIDTH / 2) - int(TEMPLATEWIDTH / 2), 
        'y': -2, # start it above the board (. less than 0) 
        'color': (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): 
    ([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) 
  (DISPLAYSURF, COLORS[color], (pixelx + 1, pixely + 1, BOXSIZE - 1, BOXSIZE - 1)) 
  (DISPLAYSURF, LIGHTCOLORS[color], (pixelx + 1, pixely + 1, BOXSIZE - 4, BOXSIZE - 4)) 
 
 
def drawBoard(board): 
  # draw the border around the board 
  (DISPLAYSURF, BORDERCOLOR, (XMARGIN - 3, TOPMARGIN - 7, (BOARDWIDTH * BOXSIZE) + 8, (BOARDHEIGHT * BOXSIZE) + 8), 5) 
 
  # fill the background of the board 
  (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 = ('Score: %s' % score, True, TEXTCOLOR) 
  scoreRect = scoreSurf.get_rect() 
   = (WINDOWWIDTH - 150, 20) 
  (scoreSurf, scoreRect) 
 
  # draw the level text 
  levelSurf = ('Level: %s' % level, True, TEXTCOLOR) 
  levelRect = levelSurf.get_rect() 
   = (WINDOWWIDTH - 150, 50) 
  (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 = ('Next:', True, TEXTCOLOR) 
  nextRect = nextSurf.get_rect() 
   = (WINDOWWIDTH - 120, 80) 
  (nextSurf, nextRect) 
  # draw the "next" piece 
  drawPiece(piece, pixelx=WINDOWWIDTH-120, pixely=100) 
 
 
if __name__ == '__main__': 
  main() 

The code starts with the initialization of some variables, and we load the time module, which will be used later. BOXSIZE, BOARDWIDTH, BOARDHEIGHT are initialized similarly to the previous initialization related to Snack, so that they are associated with the pixel points on the screen.

MOVESIDEWAYSFREQ = 0.15
MOVEDOWNFREQ = 0.1

The function of these two variables is such that whenever the gamer presses the left or right arrow keys, the descending square moves one square to the left or right accordingly; however, the gamer can also keep pressing the left or right arrow keys to keep the square moving. However, the player can also keep pressing the left or right arrow keys to keep the square moving, and the fixed value of MOVESIDEWAYSFREQ means that if the left or right arrow keys are kept pressed, then the square will only continue to move every 0.15 seconds.
MOVEDOWNFREQ This fixed value is the same as the one above except that it tells how often the cube falls when the gamer keeps pressing the down arrow key.

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

The meaning of these two sentences is clear when you look at the chart below.

 (477×389)

Then there are some definitions of color values. One of the things to note is COLORS and LIGHTCOLORS, COLORS are the colors of the small squares that make up the square, while LIGHTCOLORS are the colors that surround the small squares and are designed to emphasize the outlines.
Then it's time to define the squares. The game must know how many shapes there are for each type of cube, and here we form this template by embedding a list containing strings in a list, and a template for a cube type contains all the possible shapes that the cube can transform into. For example, the template for I is as follows:

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

TEMPLATEWIDTH = 5 and TEMPLATEHEIGHT = 5 then indicate the rows and columns that make up the shape, as shown below:

 (316×322)

In looking at this definition.

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} 

The variable PIECES is a dictionary that stores all the different templates. Because each has all the transformations of a type of square. That means that the PIECES variable contains every type of square and all the transformed shapes. This is the data structure that holds the shapes used in our game. (And it reinforces the understanding of dictionaries.)
Main function main()
The first part of the main function focuses on creating some global variables and displaying a start screen before the game starts.

while True: # game loop 
  if (0, 1) == 0: 
    ('') 
  else: 
    ('') 
  (-1, 0.0) 
  runGame() 
  () 
  showTextScreen('Game Over') 

The runGame() in the above code is the core part of the program. The loop first simply randomizes which background music is used. Then runGame() is called, and when the game fails, runGame() returns to the main() function, which stops the background music and displays the failed game screen.
When the gamer presses a key, the showTextScreen() show game failed function returns. The game loop will start again and then move on to the next game.
runGame()

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

Before the game starts and the pieces fall, we need to initialize some variables related to the start of the game. fallingPiece is assigned to the current falling piece, and nextPiece is assigned to the next piece that the player can see in the NEXT area of the screen.

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 = () # reset lastFallTime 
 
    if not isValidPosition(board, fallingPiece): 
      return # can't fit a new piece on the board, so game over 
 
  checkForQuit() 

This section contains all the code that will be used when the pieces fall to the bottom. fallingPiece variable is set to None after the piece lands, which means that the next piece in nextPiece variable should be assigned to fallingPiece variable, and then a random piece will be assigned to nextPiece variable. The lastFallTime variable is also assigned to the current time, so that we can control the frequency of the falling pieces with the fallFreq variable.
Only a portion of the square from the getNewPiece function is placed in the box area. But if this is an illegal placement, such as when the game box is already filled at this point (the isVaildPostion() function returns False), then we know that the box is full and the gamer has lost the game. When these happen, the runGame() function returns.
event processing cycle
The event loop mainly handles things that happen when a cube is flipped, when a cube is moved, or when the game is paused.
Pause the game

if ( == K_p): 
  # Pausing the game 
  (BGCOLOR) 
  () 
  showTextScreen('Paused') # pause until a key press 
  (-1, 0.0) 
  lastFallTime = () 
  lastMoveDownTime = () 
  lastMoveSidewaysTime = () 

If the gamer presses the P button, the game is paused. We should hide the game screen to prevent the gamer from cheating (otherwise the gamer will look at the screen and think about what to do with the squares), and use (BGCOLOR) to achieve this effect. Note that we also have to save some time variable values.

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

Stopping the arrow keys or ASD keys will set the moveLeft,moveRight,movingDown variable to False., indicating that the gamer no longer wants to move the cube in that direction. The code behind will handle things based on the moving variables. Note that the up and W keys are used to flip the cube, not to move it. That's why there is no movingUp variable.

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

When the left arrow key is pressed (and moving to the left works, as known by calling the isVaildPosition() function), then we should change the position of a square so that it moves one to the left by letting the fallingPiece['x'] minus() function have a parameter option that is adjX and adjY. normally, the isVaildPostion( ) function checks the position of the cube by passing the second argument of the function. However, sometimes we don't want to check the current position of the square, but rather the position a few squares off in the current direction.
For example, adjX=-1 indicates the position of the square after moving one square to the left, and +1 indicates the position after moving one square to the right. adjY is the same.
The movingLeft variable will be set to True to ensure that the cube does not move to the right, at which point the movingRight variable is set to False. the value of lastMoveSidewaysTime needs to be updated as well.
The reason for this lastMoveSidewaysTime variable setting is this. Because it is possible for the gamer to keep pressing the arrow keys to make their cube move. If moveLeft is set to True, the program knows that the left arrow key has been pressed. If 0.15 seconds (stored in the MOVESIDEAYSFREQ variable) passes on top of the time stored in the lastMoveSidewaysTime variable, then at that point the program moves the cube one square to the left again.

elif ( == K_UP or  == 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']]) 

If the arrow keys are up or the W key is pressed, then the cube is flipped. What the code above does is add 1 to the key value of the 'rotation' key stored in the fallingPiece dictionary.However, when the value of the 'rotation' key added is greater than the number of shapes of all squares of the current type (this variable is stored in the len(SHAPES[fallingPiece[' shape']]) variable, then it flips to the original shape.
 

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

If the flipped shape is invalid because some of the little squares in it have gone beyond the border, then we're going to change it back to the original shape by subtracting fallingPiece['rotation') by 1.

elif ( == 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']]) 

This code is the same as the previous one above, except that it flips the cube in the opposite direction when the gamer presses the Q key. Here we subtract 1 instead of adding 1.

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

If the down key is pressed, the gamer wants the cube to fall faster than usual. fallingPiece['y'] += 1 causes the cube to fall one square (provided it is a valid fall). moveDown is set to True, and the lastMoceDownTime variable is set to the current time. This variable will later be checked when the down key is pressed all the time to ensure that the cube is falling at a faster rate than usual.

elif  == 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 

When the gamer presses the spacebar, the cube will fall rapidly to a landing. The program first needs to find out how many squares it will take to land. The three variables for moving should be set to False (to make sure that the code in later parts of the program knows that the player has stopped pressing all the arrow keys).

if (movingLeft or movingRight) and () - 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 = () 

This code deals with the case when a certain arrow key is pressed all the time.
If the user holds the key for more than 0.15 seconds. Then the expression (movingLeft or movingRight) and () - lastMoveSidewaysTime > MOVESIDEWAYSFREQ: returns True. in which case we can move the square one square to the left or to the right.
This practice is useful because it is annoying if the user repeatedly presses the arrow keys to make the square move multiple squares. It is good practice that the user can hold down the arrow keys to keep the square moving until the keys are released. Finally don't forget to update the lastMoveSideWaysTime variable.

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

This code means pretty much the same thing as the code above.

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

The rate at which the squares naturally fall is determined by the lastFallTime variable. If enough time has passed since the last square fell one square, then the code above will move the square one more square.