机会来了!专业好玩夏令营免费送?!【码趣学院】分享之二:手把手教你用Python写贪吃蛇小游戏

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

机会来了!专业好玩夏令营免费送?!【码趣学院】分享之二:手把手教你用Python写贪吃蛇小游戏
贪吃蛇游戏玩法
在贪吃蛇游戏中,玩家将控制一只不断在屏幕上四处行进的小蛇。

玩家不能让小蛇减速,只能够控制小蛇的转向。

每隔一段时间,屏幕上将出现一个红苹果,苹果的位置是随机的,玩家的目标是让小蛇吃到苹果。

游戏开始的时候,蛇的长度很短,之后每一次吃到苹果,小蛇都会变长一点。

当小蛇撞到屏幕的边缘时,游戏就结束了。

下面,让我们一起用Python一步步制作贪吃蛇游戏吧!
(最终效果)
游戏网格
如果你之前玩过贪吃蛇游戏,你会发现苹果和小蛇的位置其实都是由网格线确定的。

这些由网格线确定的小方格有它们自己的坐标系,如上图,最左上角的小方格坐标为(0,0),最右下角的坐标为(31,23)。

初始代码
1. # 贪吃蛇游戏
2. # 关注码趣学院
3. # 预约免费试听课添加yangkesi001
4. # 发送自己代码给我们赢取价值2000元夏令营
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)
上面的代码设定了游戏的常量(constant variables),这些量在游戏进行的过程中将不会被改变。

小方格的边长被储存在变量CELLSIZE中。

assert语句(第13,14行)确保小方格的尺寸能够和游戏窗口完美契合。

例如,如果变量CELLSIZE为10,而游戏窗口的宽WINDOWWIDTH和高WINDOWHEIGHT都被设置为15,那么整个游戏窗口只能放进1.5个小方格。

assert语句确保窗口中的小方格数量为整数。

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
27. UP = ''up''
28. DOWN = ''down''
29. LEFT = ''left''
30. RIGHT = ''right''
31.
32. HEAD = 0 #小蛇头部的索引(index)
main函数
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()
在贪吃蛇游戏程序中,我们把代码的主要部分放在一个叫做runGame的函数中。

这是因为我们只想要展示一次游戏的开始界面(一段带有不断旋转的“Wormy”字样的动画),用showStartScreen函数实现。

接着我们将调用runGame函数来正式开始贪吃蛇游戏。

这个函数将在玩家的小蛇超出窗口边缘或者撞到自身的时候返回(return)(即函数结束)。

游戏结束时,我们需要调用showGameOverScreen来展示游戏的结束界面。

当这个函数返回的时候,while循环重新进行,runGame函数再次被调用,游戏重新开始。

第44行的while循环将永远进行下去,直到程序被终止。

独立的runGame函数
49. def runGame():
50. # 为小蛇设置一个随机的出发点
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. # 把苹果放在一个随机的位置
59. apple = getRandomLocation()
在游戏的开始,我们希望小蛇能够在一个随机的位置出现(但不要离窗口的边缘太近)。

所以我们需要在变量startx和starty中分别储存一个随机的坐标值。

(注意:CELLWIDTH和CELLHEIGHT是窗口横向和竖向上小方格的数量,而不是小方格自己的宽度和高度)。

在上面这段代码中,我们确定了游戏一开始时小蛇的长度和小蛇身体各部分的位置。

小蛇的身体以字典(dictionary)的形式被储存起来。

其中,头部坐标由变量startx和starty确定,剩下的两端则被放在头部左侧的两个方格中。

小蛇每一段身体的横纵坐标被储存在字典的x、y键值(key)中。

所有代表小蛇身体的字典被储存在名为wormCoords的列表(list)中。

小蛇的头部永远都是wormCoords列表的第一个值wormCoords[0]。

为了让代码更具可读性,我们在代码的第32行设置了一个常量HEAD,它的值为0,这样以来,我们就可以用wormCoords[HEAD]来代替wormCoords[0]。

事件处理循环
61. while True: # 游戏主循环
62. for event in pygame.event.get(): # 事件处理循环
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()
从第61行开始,我们进入了游戏的主循环。

第62行的for循环用于进行事件(event)处理。

如果事件(event)是QUIT事件,我们将调用terminate()函数。

如果时间不是QUIT,而是按下键盘(KEYDOWN)的话,我们就
检测按下的的键是不是WASD中的某一个。

除此之外,我们还需要一个额外的检测,确保小蛇不会转到和当前行进方向相反的方向,因为这样它就会立马撞到自己啦!例如,如果小蛇在向左行进,而玩家按到了向右的按键,在我们代码的控制下,小蛇是不会做出反应的!
撞击检测
77. # 检测小蛇是否撞到自己或者超出窗口边缘
78. if wormCoords[HEAD][''x''] == -1 or wormCoords[HEAD][''x''] == CELLWIDTH or wormCoords[HEAD][''y''] == -1 or wormCoords[HEAD][''y''] == CELLHEIGHT:
79. return # 游戏结束
80. for wormBody in wormCoords[1:]:
81. if wormBody[''x''] == wormCoords[HEAD][''x''] and wormBody[''y''] == wormCoords[HEAD][''y'']:
82. return # 游戏结束
这段代码中,我们检测小蛇的头是否超出了游戏窗口的边缘,或者撞到了一个被自己的另一段身体占据的小方格。

那么应该如何检测小蛇的头是否超出窗口边缘呢?由于窗口内所有小方格的坐标有一定范围,我们只需要检测小蛇是否超出这个范围就可以了。

网格横坐标的范围是0到CEELWIDTH-1,纵坐标的范围是0到CELLHEIGHT-1。

因而如果小蛇头部的横坐标为-1(超出窗口左侧)或CELLWIDTH(超出窗口右侧),或者纵坐标为-1(超出窗口上沿)或CELLHEIGHT(超出窗口下沿),小蛇就超出了窗口的范围。

这时,我们的代码会让游戏结束,第79行的return让runGame 函数停止并返回到第46行该函数被调用的地方。

接着,showGameOverScreen函数被调用,游戏界面上出现了“Game Over”的字样。

第80行到82行对储存在wormCoords中小蛇身体进行循环。

wormCoords的索引从0开始,而0储存的是小蛇的头部,小蛇的身体部分从索引1开始,所以我们使用wormCoords[1:]从索引1开始循环。

如果小蛇头部的横纵坐标x、y和身体的横纵坐标x、y相等,我们的代码就将结束游戏并退出runGame函数,返回到第46行函数被调用的地方,显示游戏结束页面(和上面相似)。

吃到苹果啦!
84. # 检测小蛇是否吃到苹果
85. if wormCoords[HEAD][''x''] == apple[''x''] and wormCoords[HEAD][''y''] == apple[''y'']:
86. # 暂时不要移除小蛇的尾部
87. apple = getRandomLocation() # 在某处放一个新苹果
88. else:
89. del wormCoords[-1] # 移除小蛇的尾部
这段代码用于检测小蛇是否吃到了苹果,检测方法和上面一段检测小蛇是否撞到了自身相似:如果小蛇头部的横纵坐标x、y和苹果的横纵坐标x、y相同的话,小蛇就吃到了苹果。

如果小蛇吃掉了苹果,我们就在一个新的位置放一个新苹果,这个新位置将由getRandomLocation函数随机产生。

如果小蛇没有吃到苹果,我们将小蛇的尾部,即身体的最后一段从wormCoords列表中删去。

注意,负数索引值代表从列表的末尾开始数,-1代表列表的最后一项,-2代表倒数第二项。

为了不断更新小蛇的位置,我们需要删除小蛇的尾部并在小蛇移动的方向上画一个新的头部,这样小蛇才能不断行进并且在没吃到苹果的时候保持身体长度不变。

代码的第89行移除了小蛇的尾部。

在下
面的“移动小蛇”模块,即代码的91到100行,我们将会在小蛇移动的方向上添加一段身体作为小蛇移动后的头部。

移动小蛇
91. # 在小蛇行进的方向上添加一段身体
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)
为了移动小蛇,我们要在wormCoords列表的开头给小蛇添加一段新的身体。

因为这段身体被添加到了列表开头,所以它将成为小蛇的新头部。

新头部的坐标将和旧头部的坐标相邻。

我们将根据小蛇移动的方向对横纵坐标加1或者减1。

第100行的insert()能够将新头部添加到列表开头。

insert()与append()比较
insert()与append()有不同的功能。

append()只能在列表末尾添加一项内容,而insert 能把一项或多项内容添加到列表的任何位置。

insert括号中的第一个参数代表第一项内容将要插入的位置(用索引值表示)。

如果这个值大于原有列表的最大索引值,所有的内容都会被添加到列表的末尾。

insert的第二个变量是所要插入的内
容。

我们可以在解释器(interactive shell)输入以下代码,看看insert()到底是如何运行的:
>>> 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)
这段代码相对而言比较简单。

第101行用背景颜色BGCOLOR填充整个屏幕。

第102到105行画出格子、小蛇、苹果以及要在屏幕上显示的分数。

接着,我们调用pygame.display.update()函数,从而让游戏界面成功地显示在电脑屏幕上。

在屏幕上显示文字
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)
当显示游戏的开始动画和结束界面的时候,在游戏窗口的右下角会有一行小字显示“Press a key to play”(按下任意键开始游戏)。

上面的函数drawPressKeyMsg()就是为了在屏幕上显示这行字。

有了这个函数后,我们只需要分别在showStartScreen()和showGameOverScreen()函数中分别调用drawPressKeyMsg函数即可。

checkForKeyPress()函数
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
这段函数首先检测在事件队列中是否存在QUIT事件。

通过在第117行调用pygame.event.get()函数,我们将得到事件列表中的所有QUIT事件(因为我们把QUIT作为了函数参数)。

如果事件队列中没有QUIT事件,pygame.event.get()将返回一个空列表:[]。

如果pygame.event.get()返回一个空列表,第117行len()函数的返回值将为0。

如果pygame.event.get()返回的不是空列表,len ()函数的返回值将大于0,程序将会调用第118行的terminate()函数使得程序终止。

接着pygame.event.get()得到一个存有所有KEYUP(松开按键)事件的列表。

如果玩家松开的是esc键,那么程序也会终止。

否则,pygame.event.get()返回的的第一个键也将被checkForKeyPress()函数返回。

开始界面
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)
当贪吃蛇游戏开始运行的时候,玩家并不会自动开始游戏。

在玩
家真正开始游戏之前,会出现一个开始界面告诉玩家他们正在运行什么游戏。

开始界面也让玩家做好开始游戏的准备,否则,在他们还没准备好的时候小蛇可能就要撞死了!
代码的第130行和131行创建了两个Surf对象(object)titleSurf1和titleSurf2,两个Surf内分别是两个不同颜色的“Wormy!”文本框。

第129行的Font()函数将字体大小设定为100。

第一个“Worm y!”文本框由白色文字和绿色背景组成,第二个由绿色文字和透明(transparent)背景组成。

第135行的代码让开始动画循环播放。

让文字转起来!
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() # 清除事件列表
151. return
152. pygame.display.update()
153. FPSCLOCK.tick(FPS)
在上一段代码中,我们设定了文字的颜色和大小,现在我们要让文字转起来!
Pygame.transform.rotate()将会让写有“Wormy!”字样的Surf旋转起来。

括号内有两个参数,第一个是将被旋转的Surf对象,第二个是需要旋转的角度。

Pygame.tramsform.rotate()函数不会改变你传递(pass)的Surf参数本身,而是返回一个新的Surf对象,并把旋转过的文本框画在上面。

需要注意的是,新的Surf对象可能会比原来的大,因为最初的Surf对象只需刚好能在装下文本框,新的Surf对象则需装下旋转后的文本框。

如下图,黑色矩形代表原Surf对象/文本框,灰色矩形代表新的Surf对象。

旋转的程度是用角度制来表示的。

旋转一圈是360度,不旋转是0度。

旋转1/4圈是90度。

顺时针旋转用负数表示。

由于每旋转360度就又能得到一个一样的图像,每当得到一个大于360的角度,pygame.transform.rotate()就会减去360的倍数,直到旋转角度小于360,如下图:
我们在第140行和第145行使用blit将旋转后的“Wormy!”显示在屏幕上。

第147行的drawPressKeyMsg()函数在屏幕右下角显示“Press
a key to play”字样。

这段动画会不断循环播放,直到玩家按下任意键,这时checkForKeyPress()函数将返回一个不为None的值。

在checkForKeyPress()函数返回前,程序调用pygame.event.get()函数来清除无用的事件队列。

不完美的旋转
你可能会想,我们为什么要把旋转过的Surf储存在新的变量rotatedSurf1和rotatedSurf2中,而不是直接更新原有的变量titleSurf1和titleSurf2呢?
有两个原因:
首先,我们往往不能够完美地旋转2D图像,我们只能做近似的旋转。

如果我们把图像逆时针旋转10度再顺时针旋转10度,我们得到的图像并不能和一开始的完全重合。

这就好比复印,如果我们不断对新的复印件进行复印,图像的质量会越来越差。

(这种不完美性只有一个意外:当你将图像旋转90度的倍数,比如90,180,270,360度等,我们得到的图像将是准确的)。

第二,经过旋转的2D图像将会比原来的图像稍大,对旋转过的图像再次进行旋转,旋转过的图像又会更大一些。

如果你不断进行旋转,最后图像会变得非常大,以至于PyGame无法处理,然后你的程序就会崩溃,并提示:pygame.error: Width or height is too large.(pygame错误:宽度或高度过大)。

154. degrees1 += 3 # 每帧旋转3度
155. degrees2 += 7 # 每帧旋转7度
我们旋转两个“Wormy!”的角度被储存在degrees1和degrees2中。

循环每进行一次,我们就让degrees1增加3,让degress2增加7。

也就是每一次循环,让一个文本框旋转3度,另一个文本框旋转7度。

这就是为什么一个文本框旋转得比另一个要慢。

158. def terminate():
159. pygame.quit()
160. sys.exit()
terminate()函数调用了pygame.quit()和sys.exit(),因而游戏能够无误关闭。

这里的terminate()函数就是之前提到的terminate ()函数。

确定苹果出现的位置
163. def getRandomLocation():
164. return {''x'': random.randint(0, CELLWIDTH - 1), ''y'': random.randint(0, CELLHEIGHT - 1)}
当我们需要一个新苹果出现,我们就需要调用getRandomLocation()函数。

这个函数将返回一个存有横纵坐标x、y的字典,x、y的值是由random.randint随机产生的。

游戏结束界面
167. def showGameOverScreen():
168. gameOverFont = pygame.font.Font(''freesansbold.ttf'', 150)
169. gameSurf = gameOverFont.render(''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()
游戏结束界面和游戏开始界面相似,只不过游戏结束界面不是动画。

我们用两个Surf对象分别放置“Game”和“Over”两个单词。

180. pygame.time.wait(500)
181. checkForKeyPress() # 清除事件队列中按下键盘度事件
182.
183. while True:
184. if checkForKeyPress():
185. pygame.event.get() # 清除事件队列
186. return
“Game Over”会一直显示在屏幕上,直到玩家按下任意键。

为了防止玩家太早(很可能是不小心)按下键盘,我们在第180行使用pygame.time.wait()添加半秒的等待时间。

在这半秒内,按下键盘也不会退出游戏结束界面从而重新开始游戏。

(括号内的参数500代表500毫秒,即0.5秒)。

这是为了类似这样的情况发生:比如玩家在最后关头试着避开屏幕的边缘,但是由于按键太晚导致小蛇撞死。

如果是这样的话,玩家很可能是在showGameOverScreen()被调用后才按下的键,而这时按下的键会导致退出游戏界面并且重新开始游戏。

Drawing函数
下面的几段代码用于在屏幕上绘制分数,小蛇,苹果,和网格线。

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)
drawScore()函数设置了字体格式并将用参数score传递的文字内容显示在屏幕上。

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)
drawWorm()函数给小蛇的每段身体绘制一个绿色的小方块(用绿色填充游戏屏幕上的一个网格)。

每段身体都由wormCoords 参数来传递(wormCoords是一个包含各段身体的坐标值的字典变量)。

第196行的for循环对字典中的每个值进行循环。

由于网格的坐标占据了整个窗口并且是从(0,0)像素点开始的,我们很容易将网格坐标转化为像素坐标。

这是通过第197行和第198行的乘式来实现的。

第199行创建了一个Rect对象,用于把小蛇的某段身体参数传递到第200行的pygame.draw.rect()中。

由于所有网格小方格的边长都为CELLSIZE,小蛇的身体片段的边长也应为CELLSIZE。

第200行通过将一个小方格填充为深绿色来绘制小蛇的身体片段。

然后在深绿色小方格上面,我们叠加一个小一些的亮绿色方格,这让小蛇变得更好看一些。

亮绿色方格的横坐标比网格小方格要多4个像素(往右4个像素),纵坐标也多4个像素(向下4个像素),它的边长比网格小方格要小8个像素,因此亮绿色方格的下部和右部和网格也有4像素的距离。

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)
drawApple()函数和drawWorm()函数非常相似,只不过画红苹果时我们仅仅填充了了一个红色的方格,我们所要做的只是在206行和207行把坐标转换为像素坐标,在208行创建一个Rect对象来储存苹果的位置和大小,然后把这个Rect对象传递给pygame.draw.rect()函数。

212. def drawGrid():
213. for x in range(0, WINDOWWIDTH, CELLSIZE): # 画竖线214. pygame.draw.line(DISPLAYSURF, DARKGRAY, (x, 0), (x, WINDOWHEIGHT))
215. for y in range(0, WINDOWHEIGHT, CELLSIZE): # 画横线
216. pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, y), (WINDOWWIDTH, y))
为了让网格更明显,我们调用pygame.draw.line()函数来画出网格的横竖线。

通常,要画32条竖线,我们需要调用32次pygame.draw.line()函数:
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))
......
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))
为了简便,我们可以使用第213行到第214行的for循环来代替以上这些代码。

很多其他规律的图形也可以用循环来完成,这样我们就不需要打一大堆相似的代码。

219. if __name__ == ''__main__'':
220. main()
在所有需要的常数、函数、和全局变量都被定义和创建后,我们调用main()函数来开始游戏。

不要重复使用变量名
让我们再回顾一下drawWorm()函数中的几行代码:
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)
可以注意到,我们在第199行和201行创建了两个不同的Rect对象。

第199行的Rect对象被储存在wormSegmentRect这一局部变量中,并被传递到了第200行的pygame.draw.rect()函数中。

第201行对Rect对象被储存在wormInnerSegmentRect局部变量中,并被传递到了第202行的pygame.draw.rect()函数中。

每一次我们创建一个新变量,它都会占据电脑的一点记忆空间。

你可能觉得重复使用wormSetmentRect变量是更为经济实用的选择,像这样:
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)
你也许会认为:因为第199行中由pygame.Rect()返回的Rect对象在第200行之后不会再被用到,我们可以覆盖它原来的值,并重新利用这个变量来储存第201行中由pygame.Rect()返回的Rect对象。

这样我们就能节约一些电脑记忆空间了。

虽然这理论上是对的,但实际上重复使用变量节约的空间不过几字节。

如今电脑都有上亿字节的记忆空间,所以我们节约下来的空间其实并没有多少。

而且,重复使用变量降低了程序的可读性。

如果一个程序员读了上面的代码,他会看到wormSegmentRect被传递给了第200行和202行的pygame.draw.rect()函数。

他大概也会看到第199行的pygame.Rect()函数首次给wormSegmentRect变量赋了值。

但他可能不会发现第199行由pygame.Rect()返回的Rect对象和第202行传递给pygame.draw.rect()的不是一回事。

这些小细节会使得你程序的可读性降低。

不只是读你程序的程序员会感到疑惑,几个星期以后,当你自己回头看自己写的程序时,你很可能也不记得当初这个程序到底是怎么运行的了。

我们要记住的是,程序的可读性比节约几个字节要重要得多。

相关文档
最新文档