Опубликован: 06.08.2013 | Доступ: свободный | Студентов: 974 / 64 | Длительность: 27:51:00
Лекция 7:

Tetromino

< Лекция 6 || Лекция 7: 12345 || Лекция 8 >

The main() Function

158. def main():
159.     global FPSCLOCK, DISPLAYSURF, BASICFONT, BIGFONT
160.     pygame.init()
161.     FPSCLOCK = pygame.time.Clock()
162.     DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
163.     BASICFONT = pygame.font.Font('freesansbold.ttf', 18)
164.     BIGFONT = pygame.font.Font('freesansbold.ttf', 100)
165.     pygame.display.set_caption('Tetromino')
166.
167.     showTextScreen('Tetromino')

The main() function handles creating some more global constants and showing the start screen that appears when the program is run.

168.     while True: # game loop
169.         if random.randint(0, 1) == 0:
170.             pygame.mixer.music.load('tetrisb.mid')
171.         else:
172.             pygame.mixer.music.load('tetrisc.mid')
173.         pygame.mixer.music.play(-1, 0.0)
174.         runGame()
175.         pygame.mixer.music.stop()
176.         showTextScreen('Game Over')

The code for the actual game is all in runGame(). The main() function here simply randomly decides what background music to start playing (either the tetrisb.mid or tetrisc.mid MIDI music file), then calls runGame() to begin the game. When the player loses, runGame() will return to main(), which then stops the background music and displays the game over screen.

When the player presses a key, the showTextScreen() function that displays the game over screen will return. The game loop will loop back to the beginning at line 169 and start another game.

The Start of a New Game

179. def runGame():
180.     # setup variables for the start of the game
181.     board = getBlankBoard()
182.     lastMoveDownTime = time.time()
183.     lastMoveSidewaysTime = time.time()
184.     lastFallTime = time.time()
185.     movingDown = False # note: there is no movingUp variable
186.     movingLeft = False
187.     movingRight = False
188.     score = 0
189.     level, fallFreq = calculateLevelAndFallFreq(score)
190.
191.     fallingPiece = getNewPiece()
192.     nextPiece = getNewPiece()

Before the game begins and pieces start falling, we need to initialize some variables to their start- of-game values. On line 191 the fallingPiece variable will be set to the currently falling piece that can be rotated by the player. On line 192 the nextPiece variable will be set to the piece that shows up in the "Next" part of the screen so that player knows what piece is coming up after setting the falling piece.

The Game Loop

194.     while True: # main game loop
195.         if fallingPiece == None:
196.             # No falling piece in play, so start a new piece at the top
197.             fallingPiece = nextPiece
198.             nextPiece = getNewPiece()
199.             lastFallTime = time.time() # reset lastFallTime
200.
201.             if not isValidPosition(board, fallingPiece):
202.                 return # can't fit a new piece on the board, so game over
203.
204.         checkForQuit()

The main game loop that starts on line 194 handles all of the code for the main part of the game when pieces are falling to the bottom. The fallingPiece variable is set to None after the falling piece has landed. This means that the piece in nextPiece should be copied to the fallingPiece variable, and a random new piece should be put into the nextPiece variable. A new piece can be generated from the getNewPiece() function. The lastFallTime variable is also reset to the current time so that the piece will fall in however many seconds is in fallFreq.

The pieces that getNewPiece() are positioned a little bit above the board, usually with part of the piece already on the board. But if this is an invalid position because the board is already filled up there (in which case the isValidPosition() call on line 201 will return False), then we know that the board is full and the player should lose the game. When this happens, the runGame() function returns.

The Event Handling Loop

205.         for event in pygame.event.get(): # event handling loop
206.             if event.type == KEYUP:

The event handling loop takes care of when the player rotates the falling piece, moves the falling piece, or pauses the game.

Pausing the Game

207.                 if (event.key == K_p):
208.                     # Pausing the game
209.                     DISPLAYSURF.fill(BGCOLOR)
210.                     pygame.mixer.music.stop()
211.                     showTextScreen('Paused') # pause until a key press
212.                     pygame.mixer.music.play(-1, 0.0)
213.                     lastFallTime = time.time()
214.                     lastMoveDownTime = time.time()
215.                     lastMoveSidewaysTime = time.time()

If the player has pressed the P key, then the game should pause. We need to hide the board from the player (otherwise the player could cheat by pausing the game and taking time to decide where to move the piece).

The code blanks out the display Surface with a call to DISPLAYSURF.fill(BGCOLOR) and stops the music. The showTextScreen() function is called to display the "Paused" text and wait for the player to press a key to continue.

Once the player has pressed a key, showTextScreen() will return. Line 212 will restart the background music. Also, since a large amount of time could have passed since the player paused the game, the lastFallTime, lastMoveDownTime and lastMoveSidewaysTime variables should all be reset to the current time (which is done on lines 213 to 215).

Using Movement Variables to Handle User Input

216.                 elif (event.key == K_LEFT or event.key == K_a):
217.                     movingLeft = False
218.                 elif (event.key == K_RIGHT or event.key == K_d):
219.                     movingRight = False
220.                 elif (event.key == K_DOWN or event.key == K_s):
221.                     movingDown = False

Letting up on one of the arrow keys (or the WASD keys) will set the movingLeft, movingRight, or movingDown variables back to False, indicating that the player no longer wants to move the piece in those directions. The code later will handle what to do based on the Boolean values inside these "moving" variables. Note that the up arrow and W keys are used for rotating the piece, not moving the piece up. This is why there is no movingUp variable.

Checking if a Slide or Rotation is Valid

223.             elif event.type == KEYDOWN:
224.                 # moving the block sideways
225.                 if (event.key == K_LEFT or event.key == K_a) and
isValidPosition(board, fallingPiece, adjX=-1):
226.                     fallingPiece['x'] -= 1
227.                     movingLeft = True
228.                     movingRight = False
229.                     lastMoveSidewaysTime = time.time()

When the left arrow key is pressed down (and moving to the left is a valid move for the falling piece, as determined by the call to isValidPosition()), then we should change the position to one space to the left by subtracting the value of fallingPiece['x'] by 1. The isValidPosition() function has optional parameters called adjX and adjY. Normally the isValidPosition() function checks the position of the data provided by the piece object that is passed for the second parameter. However, sometimes we don’t want to check where the piece is currently located, but rather a few spaces over from that position.

If we pass -1 for the adjX (a short name for "adjusted X"), then it doesn’t check the validity of the position in the piece’s data structure, but rather if the position of where the piece would be if it was one space to the left. Passing 1 for adjX would check one space to the right. There is also an adjY optional parameter. Passing -1 for adjY checks one space above where the piece is currently positioned, and passing a value like 3 for adjY would check three spaces down from where the piece is.

The movingLeft variable is set to True, and just to make sure the falling piece won’t move both left and right, the movingRight variable is set to False on line 228. The lastMoveSidewaysTime variable will be updated to the current time on line 229.

These variables are set so that the player can just hold down the arrow key to keep moving the piece over. If the movingLeft variable is set to True, the program can know that the left arrow key (or A key) has been pressed and not yet let go. And if 0.15 seconds (the number stored in MOVESIDEWAYSFREQ) has passed since the time stored in lastMoveSidewaysTime, then it is time for the program to move the falling piece to the left again. The lastMoveSidewaysTime works just like how the lastClickTime variable did in the Simulate chapter.

231.                 elif (event.key == K_RIGHT or event.key == K_d) and
isValidPosition(board, fallingPiece, adjX=1):
232.                     fallingPiece['x'] += 1
233.                     movingRight = True
234.                     movingLeft = False
235.                     lastMoveSidewaysTime = time.time()

The code on lines 231 to 235 is almost identical to lines 225 to 229, except that it handles moving the falling piece to the right when the right arrow key (or D key) has been pressed.

237.                 # rotating the block (if there is room to rotate)
238.                 elif (event.key == K_UP or event.key == K_w):
239.                     fallingPiece['rotation'] = (fallingPiece['rotation'] +
1) % len(SHAPES[fallingPiece['shape']])

The up arrow key (or W key) will rotate the falling piece to its next rotation. All the code has to do is increment the 'rotation' key’s value in the fallingPiece dictionary by 1. However, if incrementing the 'rotation' key’s value makes it larger than the total number of rotations, then "modding" by the total number of possible rotations for that shape (which is what len(SHAPES[fallingPiece['shape']]) is) then it will "roll over" to 0.

Here’s an example of this modding with the J shape, which has 4 possible rotations:

>>> 0 % 4
0
>>> 1 % 4
1
>>> 2 % 4
2
>>> 3 % 4
3
>>> 5 % 4
1
>>> 6 % 4
2
>>> 7 % 4
3
>>> 8 % 4
0
>>>
240.                     if not isValidPosition(board, fallingPiece):
241.                         fallingPiece['rotation'] =
(fallingPiece['rotation'] - 1) % len(SHAPES[fallingPiece['shape']])

If the new rotated position is not valid because it overlaps some boxes already on the board, then we want to switch it back to the original rotation by subtracting 1 from fallingPiece['rotation']. We can also mod it by len(SHAPES[fallingPiece['shape']]) so that if the new value is -1, the modding will change it back to the last rotation in the list. Here’s an example of modding a negative number:

>>> -1 % 4
3
242.                 elif (event.key == K_q): # rotate the other direction
243.                     fallingPiece['rotation'] = (fallingPiece['rotation'] -
1) % len(SHAPES[fallingPiece['shape']])
244.                     if not isValidPosition(board, fallingPiece):
245.                         fallingPiece['rotation'] =
(fallingPiece['rotation'] + 1) % len(SHAPES[fallingPiece['shape']])

Lines 242 to 245 do the same thing 238 to 241, except they handle the case where the player has pressed the Q key which rotates the piece in the opposite direction. In this case, we subtract 1 from fallingPiece['rotation'] (which is done on line 243) instead of adding 1.

247.                 # making the block fall faster with the down key
248.                 elif (event.key == K_DOWN or event.key == K_s):
249.                     movingDown = True
250.                     if isValidPosition(board, fallingPiece, adjY=1):
251.                         fallingPiece['y'] += 1
252.                     lastMoveDownTime = time.time()

If the down arrow or S key is pressed down, then the player wants the piece to fall faster than normal. Line 251 moves the piece down one space on the board (but only if it is a valid space). The movingDown variable is set to True and lastMoveDownTime is reset to the current time. These variables will be checked later so that the piece keeps falling at the faster rate as long as the down arrow or S key is held down.

< Лекция 6 || Лекция 7: 12345 || Лекция 8 >