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:
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.
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:
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.