贪吃蛇游戏玩法 在贪吃蛇游戏中,玩家将控制一只不断在屏幕上四处行进的小蛇。玩家不能让小蛇减速,只能够控制小蛇的转向。每隔一段时间,屏幕上将出现一个红苹果,苹果的位置是随机的,玩家的目标是让小蛇吃到苹果。游戏开始的时候,蛇的长度很短,之后每一次吃到苹果,小蛇都会变长一点。当小蛇撞到屏幕的边缘时,游戏就结束了。 下面,让我们一起用Python一步步制作贪吃蛇游戏吧! (最终效果) 游戏网格 如果你之前玩过贪吃蛇游戏,你会发现苹果和小蛇的位置其实都是由网格线确定的。 这些由网格线确定的小方格有它们自己的坐标系,如上图,最左上角的小方格坐标为(0,0),最右下角的坐标为(31,23)。 初始代码 1. # 贪吃蛇游戏 2. # 关注码趣学院 3. # 预约免费试听课添加yangkesi001 4. # 发送自己代码给我们赢取价值2000元夏令营 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) 上面的代码设定了游戏的常量(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 26. 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。第一个“Wormy!”文本框由白色文字和绿色背景组成,第二个由绿色文字和透明(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()的不是一回事。
这些小细节会使得你程序的可读性降低。不只是读你程序的程序员会感到疑惑,几个星期以后,当你自己回头看自己写的程序时,你很可能也不记得当初这个程序到底是怎么运行的了。我们要记住的是,程序的可读性比节约几个字节要重要得多。 |
|