Python Pygame贪吃蛇游戏的实现详解(下)

本节我们就对上一节讲到 runGame() 函数中用到的其他的自定义函数进行具体介绍。

drawFood() 函数

drawFood() 函数用来绘制食物,它接受的参数是窗口的 pygame.Surface 对象和表示坐标的字典对象。

drawFood() 函数的代码如下所示。
def drawFood(screen, food):
    x = food["x"] * cellSize
    y = food["y"] * cellSize
    pygame.draw.rect(screen, YELLOW, (x, y, cellSize, cellSize))
将字典 food 的键“x”对应的值乘以变量 cellSize 的结果赋值给变量 x,将字典 food 的键“y”对应的值乘以变量 cellSize 的结果赋值给变量 y。因为 food 的坐标是相对于地图上的坐标,而不是真正窗口的坐标,只有在乘以 cellSize 后才能够得到窗口上对应的像素位置。

然后,调用 pygame.draw.rect() 函数绘制用黄色填充的一个小方块。

drawSnake() 函数

drawSnake() 函数用来绘制贪吃蛇,它接受的参数是窗口的 pygame.Surface 对象和表示贪吃蛇的列表。

drawSnake()函数的代码如下所示。
def drawSnake(screen, snakeCoords):
    for coord in snakeCoords:
        x = coord["x"] * cellSize
        y = coord["y"] * cellSize
        pygame.draw.rect(screen, DARKGREEN, (x, y, cellSize, cellSize))
        pygame.draw.rect(screen, GREEN,(x + 4, y + 4, cellSize - 8, cellSize - 8))

用一个for循环来遍历 snakeCoords 列表中所有的元素,把每个元素赋值给变量 coord。

在每个循环体中,将字典coord的键“x”对应的值乘以变量 cellSize,再把结果赋值给变量 x,将字典 coord 的键“y”对应的值乘以变量 cellSize,再把结果赋值给变量 y。变量 x 和 y 对应的是窗口上的像素位置。

然后调用 pygame.draw.rect() 函数绘制一个用深绿色填充的小方块,再次调用 pygame.draw.rect()函 数在深绿色方块中绘制一个浅绿色的小方块。一个大方块和一个小方块,一起构成了蛇的一节身体。

drawScore() 函数

drawScore() 函数用来绘制分数,它接受的参数是窗口的 pygame.Surface 对象和表示分数的变量。

drawScore() 函数的代码如下所示。
def drawScore(screen,score):
    font = pygame.font.SysFont("SimHei", 30)
    scoreSurf = font.render("得分: " + str(scoer), True, WHITE)
    scoreRect = scoreSurf.get_rect()
    scoreRect.topleft = (windowsWidth - 200, 50)
    screen.blit(scoreSurf, scoreRect)

调用 pygame.font.SysFont() 函数,把字符串"得分:"以及变量 score 的值绘制到界面上,以抗锯齿方式绘制,文本颜色为白色,将生成的这个 Font 对象赋值给变量 scoreSurf。

然后获取 scoreSurf 的矩形对象并将其赋值给变量 scoreRect。指定 scoreRect 的左上角的坐标为 (windowsWidth - 200, 50)。然后,调用 blit() 函数,把 scoreSurf 对象复制到 screen 这个 Surface 上。

moveSnake() 函数

moveSnake() 函数用来移动贪吃蛇,它接受的参数是表示方向的变量和表示贪吃蛇的列表。moveSnake() 函数会根据方向,来增加一个蛇头的元素到列表中。

moveSnake() 函数的代码如下所示。
def moveSnake(direction, snakeCoords):
    if direction == UP:
        newHead = {"x": snakeCoords[HEAD][ "x"], "y": snakeCoords[HEAD][ "y"] - 1}
    elif direction == DOWN:
        newHead = {"x": snakeCoords[HEAD][ "x"], "y": snakeCoords[HEAD][ "y"] + 1}
    elif direction == LEFT:
        newHead = {"x": snakeCoords[HEAD][ "x"] - 1, "y": snakeCoords[HEAD][ "y"]}
    elif direction == RIGHT:
        newHead = {"x": snakeCoords[HEAD][ "x"] + 1, "y": snakeCoords[HEAD][ "y"]}
    snakeCoords.insert(0, newHead)

如果变量direction等于UP,表示贪吃蛇的方向是向上,那么创建一个新的蛇头元素,“x”键的值是原来蛇头的“x”键的值不变,“y”键的值是原来蛇头的“y”键的值减去1个单位。

否则,如果变量direction等于DOWN,表示贪吃蛇的方向是向下,那么创建一个新的蛇头元素,“x”键的值是原来蛇头的“x”键的值不变,“y”键的值是原来蛇头的“y”键的值加上1个单位。

否则,如果变量direction等于LEFT,表示贪吃蛇的方向是向左,那么创建一个新的蛇头元素,“x”键的值是原来蛇头的“x”键的值减去1个单位,“y”键的值是原来蛇头的“y”键的值不变。

否则,如果变量direction等于RIGHT,表示贪吃蛇的方向是向右,那么创建一个新的蛇头元素,“x”键的值是原来蛇头的“x”键的值加上1个单位,“y”键的值是原来蛇头的“y”键的值不变。

然后,把这个新创建的字典元素插入到贪吃蛇列表的第一个位置。

isEattingFood() 函数

isEattingFood() 函数用来判断贪吃蛇是否吃到了食物,它接受的参数是表示贪吃蛇的列表和表示食物位置的变量。

isEattingFood() 函数的代码如下所示。
def isEattingFood(snakeCoords, food):
    if snakeCoords[HEAD]["x"] == food["x"] and snakeCoords[HEAD]["y"] == food["y"]:
        food["x"] = random.randint(0, mapWidth - 1)
        food["y"] = random.randint(0, mapHeight - 1)
    else:
        del snakeCoords[-1]

首先判断列表 snakeCoords 的第一个元素的“x”键和“y”键的值是否等于变量 food 的“x”键和“y”键的值。我们在之前介绍过,变量 HEAD 等于 0。

如果相等,表示蛇头碰到了食物。那么重新设置变量 food 的“x”键和“y”键的值。请注意,对于列表或字典,在函数内修改参数的内容,会影响到函数之外的对象。

如果不相等,删除 snakeCoords 列表中最后一个元素。在介绍 moveSnake() 函数的时候提到过,移动贪吃蛇,其实就是增加一个新的元素。例如,最初是 3 个元素,向右移动一步,就变成了 4 个元素。如果这个时候没有吃到食物,那么为了保证元素数量不变,就要删除最后一个元素,这样才能确保 snakeCoords 列表中的元素数量没有变化,仍然是 3 个元素。

isAlive() 函数

isAive() 函数用来判断贪吃蛇是否死亡,它接受的参数是表示贪吃蛇的列表。

isAive() 函数的代码如下所示。
def isAlive(snakeCoords):
    tag = True
    if snakeCoords[HEAD]["x"] == -1 or snakeCoords[HEAD]["x"] == mapWidth or snakeCoords[HEAD]["y"] == -1 or snakeCoords[HEAD]["y"] == mapHeight:
        tag = False        # 贪吃蛇碰壁
    for snake_body in snakeCoords[1:]:
        if snake_body["x"] == snakeCoords[HEAD]["x"] and snake_body["y"] == snakeCoords[HEAD]["y"]:
            tag = False   # 贪吃蛇碰到自己身体
    return tag

首先,将变量 tag 设置为 True。然后,判断在地图上的蛇头的 x 坐标是否等于 −1,或者蛇头的 x 坐标是否等于 mapWidth,或者蛇头的 y 坐标是否等于 −1,或者蛇头的 y 坐标是否等于 mapHeight,只要满足其中的任何一个条件,就表示蛇头碰到了墙壁,那么就将变量 tag 设置为 False。

然后用一个 for 循环,来遍历 snakeCoords 列表中的第 2 个元素以及之后的元素,把每个元素赋值给变量 snake_body,表示蛇的身体。

在循环体内,判断字典 snake_body 的键“x”和“y”对应的值是否等于 snakeCoords 列表第一个元素,也就是蛇头的键“x”和“y”对应的值,如果相等,表示蛇头碰到了蛇的身体,那么就将变量 tag 设置为 False。

最后,该函数返回了变量 tag。如果 tag 是 True,表示蛇还活着;如果 tag 是 False,表示蛇死掉了。

gameOver() 函数

gameOver() 函数控制整个程序的结束,它接受的参数是窗口的 pygame.Surface 对象。

gameOver() 函数的代码如下所示。
def gameOver(screen):
    #加载游戏结束图片
    screen.fill(WHITE)
    gameOver = pygame.image.load("gameover.png")
    screen.blit(gameOver, (0, 0))
    #加载游戏结束提示信息
    font = pygame.font.SysFont("SimHei", 36)
    tip = font.render("按Q或者ESC退出游戏, 按其他键重新开始游戏", True, (65, 105, 225))
    screen.blit(tip, (30, 500))
   #显示Surface对象上的内容
    pygame.display.update()

    while True: 
        for event in pygame.event.get(): 
            if event.type == pygame.QUIT:
                terminate()    
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE or event.key == pygame.K_q: 
                    terminate()
                else:
                    return #结束此函数, 重新开始游戏
首先调用 screen.fill() 函数,用白色填充窗口。

然后调用 image() 函数在 Pygame 窗口中加载“gameover.png”图片,并创建一个名为 gameOver 的 Surface 对象。然后我们调用 blit() 函数,把 gameOver 对象复制到 screen 这个 Surface 上。通过 blit() 将 gameOver 复制到指定左上角位置(0, 0)。

然后调用 pygame.font.SysFont 函数,把字符串"按 Q 或者 ESC 退出游戏,按其他键重新开始游戏"绘制到界面上。然后我们调用 blit() 函数把 tip 对象复制到 screen 这个 Surface 上。

调用 pygame .display.update() 函数,把绘制到 Surface 对象上的所有内容,都显示到窗口上。

接下来,使用 while 循环监听键盘事件。在事件循环中,判断事件类型,如果是 QUIT,那么调用 terminate 函数终止程序。否则,如果事件类型是 KEYDOWN,那么事件对象将有一个 key 属性来识别按下的是哪个键。

如果 key 属性等于 K_ESCAPE 或 K_q,表示用户按下的是 Esc 或 Q 键,意味着玩家希望结束程序,那么处理方式和点击关闭窗口是一样的,调用 terminate() 函数终止程序。否则,表示用户按下的是其他键,结束这个函数,重新开始游戏。

当这个函数执行后,会出现如图 1 所示的游戏界面,这个时候,可以按 Q 键和 Esc 键结束程序,也可以按任意键重新开始一局游戏。

python gameOver() 函数
图 1

terminate() 函数

terminate() 函数终止程序。我们来看一下该函数的代码。
def terminate():   
    pygame.quit()
    sys.exit()
调用 pygame.quit() 函数,它是和 init() 相对应的一个函数。在退出程序之前,需要调用它。然后才会退出 Pygame。调用 sys.exit() 函数,退出主程序退。

调用入口函数

最后,我们只要调用入口函数 main(),程序就可以开始运行了。
main()
到这里,我们的贪吃蛇游戏就完成了。这是一个真正意义上的完整游戏,有开始界面和结束界面,有游戏背景,还有背景音乐。尝试着玩一玩,然后再回过头来看看游戏的程序代码,这样会更有助于对代码的理解。