(рис 6.1)
Wormy is a
This
(рис 6.2)
If you play the game a little, you’ll notice that the apple and the segments of the
1. # Wormy (a Nibbles 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, pygame, sys 7. from pygame.locals import * 8. 9. FPS = 15 10. WINDOWWIDTH = 640 11. WINDOWHEIGHT = 480 12. CELLSIZE = 20 13. assert WINDOWWIDTH % CELLSIZE == 0, "Window width must be a multiple of cell size." 14. assert WINDOWHEIGHT % CELLSIZE == 0, "Window height must be a multiple of cell size." 15. CELLWIDTH = int(WINDOWWIDTH / CELLSIZE) 16. CELLHEIGHT = int(WINDOWHEIGHT / CELLSIZE)
The code at the start of the program just sets up some CELLSIZE. The CELLSIZE was 10 and
the WINDOWWIDTH or WINDOWHEIGHT constants were set to 15, then only 1.5 cells could fit.
The
18. # R G B 19. WHITE = (255, 255, 255) 20. BLACK = ( 0, 0, 0) 21. RED = (255, 0, 0) 22. GREEN = ( 0, 255, 0) 23. DARKGREEN = ( 0, 155, 0) 24. DARKGRAY = ( 40, 40, 40) 25. BGCOLOR = BLACK 26. 27. UP = 'up' 28. DOWN = 'down' 29. LEFT = 'left' 30. RIGHT = 'right' 31. 32. HEAD = 0 # syntactic sugar: index of the worm's head
Some more constants are set on lines 19 to 32. The HEAD constant will be
34. def main():
35. global FPSCLOCK, DISPLAYSURF, BASICFONT
36.
37. pygame.init()
38. FPSCLOCK = pygame.time.Clock()
39. DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
40. BASICFONT = pygame.font.Font('freesansbold.ttf', 18)
41. pygame.display.set_caption('Wormy')
42.
43. showStartScreen()
44. while True:
45. runGame()
46. showGameOverScreen()
In the Wormy runGame(). This is because we only want to show the "start screen" (the animation with the
rotating "Wormy" text) once when the program starts (by calling the showStartScreen() function).
Then we want to call runGame(), which will start a game of Wormy. This function
will return when the player’s
At that point we will show the game over screen by calling showGameOverScreen(). When
that calls runGame() again. The
while loop on line 44 will loop forever until the
49. def runGame():
50. # Set a random start point.
51. startx = random.randint(5, CELLWIDTH - 6)
52. starty = random.randint(5, CELLHEIGHT - 6)
53. wormCoords = [{'x': startx, 'y': starty},
54. {'x': startx - 1, 'y': starty},
55. {'x': startx - 2, 'y': starty}]
56. direction = RIGHT
57.
58. # Start the apple in a random place.
59. apple = getRandomLocation()
At the beginning of a game, we want the startx and starty. (Remember
that CELLWIDTH and CELLHEIGHT is the number of cells wide and high the window is, not the
number of pixels wide and high).
The body of the startx and starty. The other
two body segments will be one and two cells to the left of the head.
The head of the wormCoords[0]. To make this code
more HEAD constant to 0 on line 32, so that we can use
wormCoords[HEAD] instead of wormCoords[0].
61. while True: # main game loop 62. for event in pygame.event.get(): # event handling loop 63. if event.type == QUIT: 64. terminate() 65. elif event.type == KEYDOWN: 66. if (event.key == K_LEFT or event.key == K_a) and direction != RIGHT: 67. direction = LEFT 68. elif (event.key == K_RIGHT or event.key == K_d) and direction != LEFT: 69. direction = RIGHT 70. elif (event.key == K_UP or event.key == K_w) and direction != DOWN: 71. direction = UP 72. elif (event.key == K_DOWN or event.key == K_s) and direction != UP: 73. direction = DOWN 74. elif event.key == K_ESCAPE: 75. terminate()
Line 61 is the start of the main game loop and line 62 is the start of the QUIT event, then we call terminate() (which we’ve defined the same as the
terminate() function in the previous
Otherwise, if the event is a KEYDOWN event, then we check if the key that was pressed down is an
arrow key or a WASD key. We want an additional check so that the
That is why we have this check for the current value of the direction variable. That way, if
the player accidentally presses an arrow key that would cause them to immediately
77. # check if the worm has hit itself or the edge 78. if wormCoords[HEAD]['x'] == -1 or wormCoords[HEAD]['x'] == CELLWIDTH or wormCoords[HEAD]['y'] == -1 or wormCoords[HEAD]['y'] == CELLHEIGHT: 79. return # game over 80. for wormBody in wormCoords[1:]: 81. if wormBody['x'] == wormCoords[HEAD]['x'] and wormBody['y'] == wormCoords[HEAD]['y']: 82. return # game over
The
We can check if the head has moved off the edge of the grid by seeing if either the X coordinate
of the head (which is stored in wormCoords[HEAD]['x']) is -1 (which is past the left edge
of the grid) or equal to CELLWIDTH (which is past the right edge, since the CELLWIDTH).
The head has also moved off the grid if the Y coordinate of the head (which is stored in
wormCoords[HEAD]['y']) is either -1 (which is past the top edge) or CELLHEIGHT
(which is past the bottom edge).
All we have to do to end the current game is to return out ofrunGame(). When runGame()
returns to the main(), the next line after the runGame() call (line 46) is the
call to showGameOverScreen() which makes the large "Game Over" text appear. This is
why we have the return statement on line 79.
Line 80 loops through every body segment in wormCoords after the head (which is at index 0.
This is why the for wormCoords[1:] instead of just wormCoords). If
both the 'x' and 'y' values of the body segment are the same as the 'x' and 'y' of the head,
then we also end the game by returning out of the runGame() function.
84. # check if worm has eaten an apply 85. if wormCoords[HEAD]['x'] == apple['x'] and wormCoords[HEAD]['y'] == apple['y']: 86. # don't remove worm's tail segment 87. apple = getRandomLocation() # set a new apple somewhere 88. else: 89. del wormCoords[-1] # remove worm's tail segment
We do a similar getRandomLocation()).
If the head has not collided with an apple, then we delete the last body segment in the
wormCoords list. Remember that
The code on lines 91 to 100 (
91. # move the worm by adding a segment in the direction it is moving
92. if direction == UP:
93. newHead = {'x': wormCoords[HEAD]['x'], 'y':
wormCoords[HEAD]['y'] - 1}
94. elif direction == DOWN:
95. newHead = {'x': wormCoords[HEAD]['x'], 'y':
wormCoords[HEAD]['y'] + 1}
96. elif direction == LEFT:
97. newHead = {'x': wormCoords[HEAD]['x'] - 1, 'y':
wormCoords[HEAD]['y']}
98. elif direction == RIGHT:
99. newHead = {'x': wormCoords[HEAD]['x'] + 1, 'y':
wormCoords[HEAD]['y']}
100. wormCoords.insert(0, newHead)
To move the wormCoords list.
Because the body segment is being added to the beginning of the list, it will become the new
head. The coordinates of the new head will be right next to the old head’s coordinates. Whether 1
is added or
This new head segment is added to wormCoords with the insert() list method on line 100.
Unlike the append() list method that can only add items to the end of a list, the insert() list
method can add items anywhere inside the list. The first parameter for insert() is the index
where the item should go (all the items originally at this index and after have their indexes
increase by one). If the append() does). The second
parameter for insert() is the item value to be added. Type the following into the interactive
shell to see how insert() works:
>>> spam = ['cat', 'dog', 'bat'] >>> spam.insert(0, 'frog') >>> spam ['frog', 'cat', 'dog', 'bat'] >>> spam.insert(10, 42) >>> spam ['frog', 'cat', 'dog', 'bat', 42] >>> spam.insert(2, 'horse') >>> spam ['frog', 'cat', 'horse', 'dog', 'bat', 42] >>>
101. DISPLAYSURF.fill(BGCOLOR) 102. drawGrid() 103. drawWorm(wormCoords) 104. drawApple(apple) 105. drawScore(len(wormCoords) - 3) 106. pygame.display.update() 107. FPSCLOCK.tick(FPS)
The code for drawing the screen in the runGame() function is fairly simple. Line 101 fills in
the entire display pygame.display.update() draws
the display
109. def drawPressKeyMsg():
110. pressKeySurf = BASICFONT.render('Press a key to play.', True,
DARKGRAY)
111. pressKeyRect = pressKeySurf.get_rect()
112. pressKeyRect.topleft = (WINDOWWIDTH - 200, WINDOWHEIGHT - 30)
113. DISPLAYSURF.blit(pressKeySurf, pressKeyRect)
While the start screen animation is playing or the game over screen is being shown, there will be
some small text in the bottom right showStartScreen() and the showGameOverScreen(), we
put it in a this separate function and simply call the function from showStartScreen() and
showGameOverScreen().
116. def checkForKeyPress(): 117. if len(pygame.event.get(QUIT)) > 0: 118. terminate() 119. 120. keyUpEvents = pygame.event.get(KEYUP) 121. if len(keyUpEvents) == 0: 122. return None 123. if keyUpEvents[0].key == K_ESCAPE: 124. terminate() 125. return keyUpEvents[0].key
This function first checks if there are any QUIT events in the pygame.event.get() on line 117 returns a list of all the QUIT events in the QUIT as an argument). If there are not QUIT events in the pygame.event.get() returns will be the
The len() call on line 117 will return 0 if pygame.event.get() returned an pygame.event.get() (and remember,
any items in this list will only be QUIT events because we passed QUIT as the argument to
pygame.event.get()), then the terminate() function gets called on line 118 and the
After that, the call to pygame.event.get() gets a list of any KEYUP events in the event
queue. If the key event is for the Esc key, then the pygame.event.get() is
returned from this checkForKeyPress() function.
128. def showStartScreen():
129. titleFont = pygame.font.Font('freesansbold.ttf', 100)
130. titleSurf1 = titleFont.render('Wormy!', True, WHITE, DARKGREEN)
131. titleSurf2 = titleFont.render('Wormy!', True, GREEN)
132.
133. degrees1 = 0
134. degrees2 = 0
135. while True:
136. DISPLAYSURF.fill(BGCOLOR)
When the Wormy
The Wormy start screen requires two Font() constructor
Line 135 begins the animation loop for the start screen. During this animation, the two pieces of
text will be rotated and drawn to the display
137. rotatedSurf1 = pygame.transform.rotate(titleSurf1, degrees1) 138. rotatedRect1 = rotatedSurf1.get_rect() 139. rotatedRect1.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2) 140. DISPLAYSURF.blit(rotatedSurf1, rotatedRect1) 141. 142. rotatedSurf2 = pygame.transform.rotate(titleSurf2, degrees2) 143. rotatedRect2 = rotatedSurf2.get_rect() 144. rotatedRect2.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2) 145. DISPLAYSURF.blit(rotatedSurf2, rotatedRect2) 146. 147. drawPressKeyMsg() 148. 149. if checkForKeyPress(): 150. pygame.event.get() # clear event queue 151. return 152. pygame.display.update() 153. FPSCLOCK.tick(FPS)
The showStartScreen() function will rotate the images on the pygame.transform.rotate() function doesn’t change the
Note that this new
(рис 6.3)
The amount you rotate it is given in pygame.transform.rotate() is 360 or larger,
then Pygame automatically keeps
(рис 6.4)
The two rotated "Wormy!"
On line 147 the drawPressKeyMsg() checkForKeyPress() returns a value that is not None, which happens if the player presses a
key. Before returning, pygame.event.get() is called simply to clear out any other events
that have
You may wonder why we store the rotated
First, rotating a
(The only exception to this is if you rotate an image by a multiple of 90
Second, if you rotate a pygame.error: Width or height is too large.
154. degrees1 += 3 # rotate by 3 degrees each frame 155. degrees2 += 7 # rotate by 7 degrees each frame
The amount that we rotate the two "Wormy!" text degrees1 and
degrees2. On each iteration through the animation loop, we increase the number stored in
degrees1 by 3 and degrees2 by 7. This means on the next iteration of the animation loop
the white text "Wormy!"
158. def terminate(): 159. pygame.quit() 160. sys.exit()
The terminate() pygame.quit() and sys.exit() so that the game
correctly terminate() functions in the previous game
programs.
163. def getRandomLocation():
164. return {'x': random.randint(0, CELLWIDTH - 1), 'y': random.randint(0,
CELLHEIGHT - 1)}
The getRandomLocation() function is called whenever new coordinates for the apple are
needed. This function returns a dictionary with keys 'x' and 'y', with the values set to random
XY coordinates.
167. def showGameOverScreen():
168. gameOverFont = pygame.font.Font('freesansbold.ttf', 150)
169. gameSurf = gameOverFont.rende('Game', True, WHITE)
170. overSurf = gameOverFont.render('Over', True, WHITE)
171. gameRect = gameSurf.get_rect()
172. overRect = overSurf.get_rect()
173. gameRect.midtop = (WINDOWWIDTH / 2, 10)
174. overRect.midtop = (WINDOWWIDTH / 2, gameRect.height + 10 + 25)
175.
176. DISPLAYSURF.blit(gameSurf, gameRect)
177. DISPLAYSURF.blit(overSurf, overRect)
178. drawPressKeyMsg()
179. pygame.display.update()
The game over screen is similar to the start screen, except it isn’t animated. The words "Game"
and "Over" are rendered to two
180. pygame.time.wait(500) 181. checkForKeyPress() # clear out any key presses in the event queue 182. 183. while True: 184. if checkForKeyPress(): 185. pygame.event.get() # clear event queue 186. return
The Game Over text will pygame.time.wait() on line 180 (The 500 argument
Then, checkForKeyPress() is called so that any key events that were made since the
showGameOverScreen() function started are ignored. This pause and dropping of the key
events is to showGameOverScreen() was called, and that key press would cause the game over screen to
disappear almost instantly. The next game would start immediately after that, and might take the
player by surprise. Adding this pause helps the make the game more "user friendly".
The code to draw the
188. def drawScore(score>):
189. scoreSurf = BASICFONT.render('Score: %s' % (score), True, WHITE)
190. scoreRect = scoreSurf.get_rect()
191. scoreRect.topleft = (WINDOWWIDTH - 120, 10)
192. DISPLAYSURF.blit(scoreSurf, scoreRect)
The drawScore() function simply renders and draws the text of the parameter on the display
195. def drawWorm(wormCoords): 196. for coord in wormCoords: 197. x = coord['x'] * CELLSIZE 198. y = coord['y'] * CELLSIZE 199. wormSegmentRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE) 200. pygame.draw.rect(DISPLAYSURF, DARKGREEN, wormSegmentRect) 201. wormInnerSegmentRect = pygame.Rect(x + 4, y + 4, CELLSIZE - 8, CELLSIZE - 8) 202. pygame.draw.rect(DISPLAYSURF, GREEN, wormInnerSegmentRect)
The drawWorm() function will draw a green box for each of the segments of the wormCoords parameter, which is a list of dictionaries each with
an 'x' key and a 'y' key. The for loop on line 196 loops through each of the dictionary values
in wormCoords.
Because the grid coordinates take up the entire window and also begin a 0, 0 pixel, it is fairly
coord['x'] and coord['y'] coordinate by the CELLSIZE.
Line 199 creates a Rect object for the pygame.draw.rect() function on line 200. Remember that each cell in the grid is
CELLSIZE in width and height, so that’s what the size of the segment’s Rect object should be.
Line 200 draws a dark green rectangle for the segment. Then on top of this, a smaller
The inner
205. def drawApple(coord): 206. x = coord['x'] * CELLSIZE 207. y = coord['y'] * CELLSIZE 208. appleRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE) 209. pygame.draw.rect(DISPLAYSURF, RED, appleRect)
The drawApple() function is very similar to drawWorm(), except since the red apple is just
a single rectangle that fills up the cell, all the function needs to do is convert to pixel coordinates
(which is what lines 206 and 207 do), create the Rect object with the location and size of the
apple (line 208), and then pass this Rect object to the pygame.draw.rect() function.
212. def drawGrid(): 213. for x in range(0, WINDOWWIDTH, CELLSIZE): # draw vertical lines 214. pygame.draw.line(DISPLAYSURF, DARKGRAY, (x, 0), (x, WINDOWHEIGHT)) 215. for y in range(0, WINDOWHEIGHT, CELLSIZE): # draw horizontal lines 216. pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, y), (WINDOWWIDTH, y))
Just to make it easier to visualize the grid of cells, we call pygame.draw.line() to draw out
each of the vertical and horizontal lines of the grid.
Normally, to draw the 32 vertical lines needed, we would need 32 calls to
pygame.draw.line() with the following coordinates:
pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, 0), (0, WINDOWHEIGHT)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (20, 0), (20, WINDOWHEIGHT)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (40, 0), (40, WINDOWHEIGHT)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (60, 0), (60, WINDOWHEIGHT)) ...skipped for brevity... pygame.draw.line(DISPLAYSURF, DARKGRAY, (560, 0), (560, WINDOWHEIGHT)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (580, 0), (580, WINDOWHEIGHT)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (600, 0), (600, WINDOWHEIGHT)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (620, 0), (620, WINDOWHEIGHT))
Instead of typing out all these lines of code, we can just have one line of code inside a for loop.
Notice that the pattern for the vertical lines is that the X coordinate of the start and end point
starts at 0 and goes up to 620, increasing by 20 each time. The Y coordinate is always 0 for the
WINDOWHEIGHT for the end point parameter. That means the for loop should
iterate over range(0, 640, 20). This is why the for loop on line 213 iterates over
range(0, WINDOWWIDTH, CELLSIZE).
For the horizontal lines, the coordinates would have to be:
pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, 0), (WINDOWWIDTH, 0)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, 20), (WINDOWWIDTH, 20)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, 40), (WINDOWWIDTH, 40)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, 60), (WINDOWWIDTH, 60)) ...skipped for brevity... pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, 400), (WINDOWWIDTH, 400)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, 420), (WINDOWWIDTH, 420)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, 440), (WINDOWWIDTH, 440)) pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, 460), (WINDOWWIDTH, 460))
The Y coordinate ranges from 0 to 460, increasing by 20 each time. The X coordinate is always
0 for the WINDOWWIDTH for the end point parameter. We can also use a for loop
here so we don’t have to type out all those pygame.draw.line() calls.
Noticing regular patterns needed by the calls and using loops is a clever programmer trick to save
us from a lot of typing. We could have typed out all 56 pygame.draw.line() calls and the
program would have worked the
219. if __name__ == '__main__': 220. main()
After all the functions and constants and main() function is called to start the game.
Take a look at a few lines of code from the drawWorm() function again:
199. wormSegmentRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE) 200. pygame.draw.rect(DISPLAYSURF, DARKGREEN, wormSegmentRect) 201. wormInnerSegmentRect = pygame.Rect(x + 4, y + 4, CELLSIZE - 8, CELLSIZE - 8) 202. pygame.draw.rect(DISPLAYSURF, GREEN, wormInnerSegmentRect)
Notice that two different Rect objects are created on lines 199 and 201. The Rect object created
on line 199 is stored in the wormSegmentRect local variable and is passed to the
pygame.draw.rect() function on line 200. The Rect object created on line 201 is stored in
the wormInnerSegmentRect local variable and is passed to the pygame.draw.rect()
function on line 202.
Every time you create a variable, it takes up a small amount of the computer’s memory. You
might think it would be clever to wormSegmentRect variable for both Rect objects,
like this:
199. wormSegmentRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE) 200. pygame.draw.rect(DISPLAYSURF, DARKGREEN, wormSegmentRect) 201. wormSegmentRect = pygame.Rect(x + 4, y + 4, CELLSIZE - 8, CELLSIZE - 8) 202. pygame.draw.rect(DISPLAYSURF, GREEN, wormInnerSegmentRect)
Because the Rect object returned by pygame.Rect() on line 199 won’t be needed after 200,
we can pygame.Rect() on line 201. Since we are now using fewer variables we are saving memory,
right?
While this is technically true, you really are only saving a few bytes. Modern computers have
memory of several billion bytes. So the savings aren’t that great. Meanwhile, reusing variables
reduces the code readability. If a programmer was reading through this code after it was written,
they would see that wormSegmentRect is passed to the pygame.draw.rect() calls on
line 200 and 202. If they tried to find the first time the wormSegmentRect variable was
assigned a value, they would see the pygame.Rect() call on line 199. They might not realize
that the Rect object returned by line 199’s pygame.Rect() call isn’t the same as the one that is
passed to the pygame.draw.rect() call on line 202.
Little things like this make it harder to understand how exactly your program works. It won’t just be other programmers looking at your code who will be confused. When you look at your own code a couple weeks after writing it, you may have a hard time remembering how exactly it works. Code readability is much more important than saving a few bytes of memory here and there.
For additional programming practice, you can download buggy versions of Wormy from http://invpy.com/buggy/wormy and try to figure out how to fix the bugs.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.