Tetromino
How to Play Tetromino
Tetromino is a Tetris clone. Differently shaped blocks (each made up of four boxes) fall from the top of the screen, and the player must guide them down to form complete rows that have no gaps in them. When a complete row is formed, the row disappears and each row above it moves down one row. The player tries to keep forming complete lines until the screen fills up and a new falling block cannot fit on the screen.
Some Tetromino Nomenclature
In this chapter, I have come up with a set of terms for the different things in the game program.
- Board – The board is made up of 10 x 20 spaces that the blocks fall and stack up in.
- Box – A box is a single filled-in square space on the board.
- Piece – The things that fall from the top of the board that the player can rotate and position. Each piece has a shape and is made up of 4 boxes.
- Shape – The shapes are the different types of pieces in the game. The names of the shapes are T, S, Z, J, L, I, and O.
- Template – A list of shape data structures that represents all the possible rotations of a shape. These are store in variables with names like S_SHAPE_TEMPLATE or J_SHAPE_TEMPLATE.
- Landed – When a piece has either reached the bottom of the board or is touching a box on the board, we say that the piece has landed. At that point, the next piece should start falling.
Source Code to Tetromino
This source code can be downloaded from http://invpy.com/tetromino.py. If you get any error messages, look at the line number that is mentioned in the error message and check your code for any typos. You can also copy and paste your code into the web form at http://invpy.com/diff/tetromino to see if the differences between your code and the code in the book.
You will also need the background music files in the same folder of as the tetromino.py file. You can download them from here:
The Usual Setup Code
1. # Tetromino (a Tetris clone) 2. # By Al Sweigart al@inventwithpython.com 3. # http://inventwithpython.com/pygame 4. # Creative Commons BY-NC-SA 3.0 US 5. 6. import random, time, pygame, sys 7. from pygame.locals import * 8. 9. FPS = 25 10. WINDOWWIDTH = 640 11. WINDOWHEIGHT = 480 12. BOXSIZE = 20 13. BOARDWIDTH = 10 14. BOARDHEIGHT = 20 15. BLANK = '.'
These are the constants used by our Tetromino game. Each box is a square that is 20 pixels wide and high. The board itself is 10 boxes wide and 20 boxes tall. The BLANK constant will be used as a value to represent blank spaces in the board’s data structure.
Setting up Timing Constants for Holding Down Keys
17. MOVESIDEWAYSFREQ = 0.15 18. MOVEDOWNFREQ = 0.1
Every time the player pushes the left or right arrow key down, the falling piece should move one box over to the left or right, respectively. However, the player can also hold down the left or right arrow key to keep moving the falling piece. The MOVESIDEWAYSFREQ constant will set it so that every 0.15 seconds that passes with the left or right arrow key held down, the piece will move another space over.
The MOVEDOWNFREQ constant is the same thing except it tells how frequently the piece drops by one box while the player has the down arrow key held down.
More Setup Code
20. XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * BOXSIZE) / 2) 21. TOPMARGIN = WINDOWHEIGHT - (BOARDHEIGHT * BOXSIZE) - 5
The program needs to calculate how many pixels are to the left and right side of the board to use later in the program. WINDOWWIDTH is the total number of pixels wide the entire window is. The board is BOARDWIDTH boxes wide and each box is BOXSIZE pixels wide. If we subtract BOXSIZE pixels from this for each of the boxes wide in the board (which is BOARDWIDTH *BOXSIZE), we’ll have the size of the margin to the left and right of the board. If we divide this by 2, then we will have the size of just one margin. Since the margins are the same size, we can use XMARGIN for either the left-side or right-side margin.
We can calculate the size of the space between the top of the board and the top of the window in a similar manner. The board will be drawn 5 pixels above the bottom of the window, so 5 is subtracted from topmargin to account for this.
23. # R G B 24. WHITE = (255, 255, 255) 25. GRAY = (185, 185, 185) 26. BLACK = ( 0, 0, 0) 27. RED = (155, 0, 0) 28. LIGHTRED = (175, 20, 20) 29. GREEN = ( 0, 155, 0) 30. LIGHTGREEN = ( 20, 175, 20) 31. BLUE = ( 0, 0, 155) 32. LIGHTBLUE = ( 20, 20, 175) 33. YELLOW = (155, 155, 0) 34. LIGHTYELLOW = (175, 175, 20) 35. 36. BORDERCOLOR = BLUE 37. BGCOLOR = BLACK 38. TEXTCOLOR = WHITE 39. TEXTSHADOWCOLOR = GRAY 40. COLORS = ( BLUE, GREEN, RED, YELLOW) 41. LIGHTCOLORS = (LIGHTBLUE, LIGHTGREEN, LIGHTRED, LIGHTYELLOW) 42. assert len(COLORS) == len(LIGHTCOLORS) # each color must have light color
The pieces will come in four colors: blue, green, red, and yellow. When we draw the boxes though, there will be a thin highlight on the box in a lighter color. So this means we need to create light blue, light green, light red, and light yellow colors as well. Each of these four colors will be stored in tuples named COLORS (for the normal colors) and LIGHTCOLORS (for the lighter colors).
Setting Up the Piece Templates
44. TEMPLATEWIDTH = 5 45. TEMPLATEHEIGHT = 5 46. 47. S_SHAPE_TEMPLATE = [['.....', 48. '.....', 49. '..OO.', 50. '.OO..', 51. '.....'], 52. ['.....', 53. '..O..', 54. '..OO.', 55. '...O.', 56. '.....']] 57. 58. Z_SHAPE_TEMPLATE = [['.....', 59. '.....', 60. '.OO..', 61. '..OO.', 62. '.....'], 63. ['.....', 64. '..O..', 65. '.OO..', 66. '.O...', 67. '.....']] 68. 69. I_SHAPE_TEMPLATE = [['..O..', 70. '..O..', 71. '..O..', 72. '..O..', 73. '.....'], 74. ['.....', 75. '.....', 76. 'OOOO.', 77. '.....', 78. '.....']] 79. 80. O_SHAPE_TEMPLATE = [['.....', 81. '.....', 82. '.OO..', 83. '.OO..', 84. '.....']] 85. 86. J_SHAPE_TEMPLATE = [['.....', 87. '.O...', 88. '.OOO.', 89. '.....', 90. '.....'], 91. ['.....', 92. '..OO.', 93. '..O..', 94. '..O..', 95. '.....'], 96. ['.....', 97. '.....', 98. '.OOO.', 99. '...O.', 100. '.....'], 101. ['.....', 102. '..O..', 103. '..O..', 104. '.OO..', 105. '.....']] 106. 107. L_SHAPE_TEMPLATE = [['.....', 108. '...O.', 109. '.OOO.', 110. '.....', 111. '.....'], 112. ['.....', 113. '..O..', 114. '..O..', 115. '..OO.', 116. '.....'], 117. ['.....', 118. '.....', 119. '.OOO.', 120. '.O...', 121. '.....'], 122. ['.....', 123. '.OO..', 124. '..O..', 125. '..O..', 126. '.....']] 127. 128. T_SHAPE_TEMPLATE = [['.....', 129. '..O..', 130. '.OOO.', 131. '.....', 132. '.....'], 133. ['.....', 134. '..O..', 135. '..OO.', 136. '..O..', 137. '.....'], 138. ['.....', 139. '.....', 140. '.OOO.', 141. '..O..', 142. '.....'], 143. ['.....', 144. '..O..', 145. '.OO..', 146. '..O..', 147. '.....']]
Our game program needs to know how each of the shapes are shaped, including for all of their possible rotations. In order to do this, we will create lists of lists of strings. The inner list of strings will represent a single rotation of a shape, like this:
['.....', '.....', '..OO.', '.OO..', '.....']
We will write the rest of our code so that it interprets a list of strings like the one above to represent a shape where the periods are empty spaces and the O’s are boxes, like this:
Splitting a "Line of Code" Across Multiple Lines
You can see that this list is spread across many lines in the file editor. This is perfectly valid Python, because the Python interpreter realizes that until it sees the ] closing square bracket, the list isn’t finished. The indentation doesn’t matter because Python knows you won’t have different indentation for a new block in the middle of a list. This code below works just fine:
spam = ['hello', 3.14, 'world', 42, 10, 'fuzz'] eggs = ['hello', 3.14, 'world' , 42, 10, 'fuzz']
Though, of course, the code for the eggs list would be much more readable if we lined up all the items in the list or put on a single line like spam.
Normally, splitting a line of code across multiple lines in the file editor would require putting a \ character at the end of the line. The \ tells Python, "This code continues onto the next line." (This slash was first used in the Sliding Puzzle game in the isValidMove() function.)
We will make "template" data structures of the shapes by creating a list of these list of strings, and store them in variables such as S_SHAPE_TEMPLATE. This way, len(S_SHAPE_TEMPLATE) will represent how many possible rotations there are for the S shape, and S_SHAPE_TEMPLATE[0] will represent the S shape’s first possible rotation. Lines 47 to 147 will create "template" data structures for each of the shapes.
Imagine that each possible piece in a tiny 5 x 5 board of empty space, with some of the spaces on the board filled in with boxes. The following expressions that use S_SHAPE_TEMPLATE[0] are True:
S_SHAPE_TEMPLATE[0][2][2] == 'O' S_SHAPE_TEMPLATE[0][2][3] == 'O' S_SHAPE_TEMPLATE[0][3][1] == 'O' S_SHAPE_TEMPLATE[0][3][2] == 'O'
If we represented this shape on paper, it would look something like this:
This is how we can represent things like Tetromino pieces as Python values such as strings and lists. The TEMPLATEWIDTH and TEMPLATEHEIGHT constants simply set how large each row and column for each shape’s rotation should be (The templates will always be 5x5).
149. SHAPES = {'S': S_SHAPE_TEMPLATE, 150. 'Z': Z_SHAPE_TEMPLATE, 151. 'J': J_SHAPE_TEMPLATE, 152. 'L': L_SHAPE_TEMPLATE, 153. 'I': I_SHAPE_TEMPLATE, 154. 'O': O_SHAPE_TEMPLATE, 155. 'T': T_SHAPE_TEMPLATE}
The SHAPES variable will be a dictionary that stores all of the different templates. Because each template has all the possible rotations of a single shape, this means that the SHAPES variable contains all possible rotations of every possible shape. This will be the data structure that contains all of the shape data in our game.