上篇文章Python实现贪吃蛇一,实现了一个贪吃蛇的基础版本。后面第二篇文章Python实现贪吃蛇二修改了一些不足,但最近发现还有两点需要优化:
1、生成食物的时候有概率和记分牌重合
2、游戏缺少暂停功能
先看生成食物的时候有概率和记分牌重合的问题。在游戏过程中,有时吃掉一个食物后,发现“没有”生成新的食物。实际上是食物生成的位置和记分牌重合了,被挡住了。这种情况很影响游戏体验,并且尝试去吃掉记分牌下面的食物时很容易撞墙。针对上面问题,在生成新的食物的时候,增加是否与记分牌重合的校验,如果重合,重新生成食物,直到符合要求。修改后的代码片段如下:
def _check_food(self):
""" 检查新生成的食物是否不与蛇身及记分牌重合 """
food = self.food
food.rect.x = round(
random.randrange(20, self.settings.screen_width - self.settings.snake_width * 2) / 20.0) * 20.0
food.rect.y = round(
random.randrange(20, self.settings.screen_height - self.settings.snake_height * 2) / 20.0) * 20.0
for snake in self.snakes:
if snake.rect.colliderect(food.rect) or self.sb.score_rect.colliderect(food.rect):
return False
return True
再看游戏暂停功能。 有时正在游戏过程中,尤其是得分比较高的时候,有事需要离开,这时候没有游戏暂停功能的话,只能结束游戏,体验不太好。如何增加暂停功能,思路其实比较简单:在游戏状态类里增加一个游戏暂停状态的属性,当按下“空格”键的时候,将这个属性值取反。同时游戏主循环里增加游戏暂停状态的判断,如果是暂停状态游戏不再刷新,如果不是暂停状态,游戏正常刷新。这样,就实现了游戏暂停功能。相关代码片段:
class GameStats:
""" 跟踪游戏的统计信息 """
def __init__(self, ai_game):
""" 初始化统计信息 """
self.settings = ai_game.settings
self.reset_stats()
# 游戏刚启动时处于非活动状态
self.game_active = False
# 游戏暂停状态
self.game_pause = False
def reset_stats(self):
""" 初始化在游戏运行期间可能变化的统计信息 """
self.score = 0
def run_game(self):
""" 开始游戏的主循环 """
while True:
self._check_events()
if self.stats.game_active and not self.stats.game_pause:
if self.settings.update_count > 500: #控制游戏速度
self._update_snakes()
self._check_edges()
self.settings.update_count = 0
self.settings.update_count += self.settings.game_speed
self._update_screen()
修改后主程序类(gluttonous_snake.py)的完整代码:
import sys
import time
import pygame
import random
from settings import Settings
from snake import Snake
from game_stats import GameStats
from button import Button
from food import Food
from scoreboard import Scoreboard
from game_sound import GameSound
class GluttonousSnake:
""" 管理游戏资源和行为的类 """
def __init__(self):
""" 初始化游戏并创建游戏资源 """
pygame.init()
# 初始化音频混合器
pygame.mixer.init()
# 初始化游戏声音
self.snake_eat_food_sound = GameSound('snake_eat_food.mp3')
self.snake_game_over_sound = GameSound('snake_game_over.mp3')
self.background_sound = GameSound('snake_background_sound.mp3')
self.cheer_sound = GameSound('snake_cheer_sound.mp3')
self.settings = Settings()
self.screen = pygame.display.set_mode((self.settings.screen_width, self.settings.screen_height))
pygame.display.set_caption("贪吃蛇")
# 创建一个用于存储游戏统计信息的实例
self.stats = GameStats(self)
# 创建记分牌
self.sb = Scoreboard(self)
self.food = Food(self)
self.snakes = []
self._create_snakes()
# 创建Play按钮
self.play_button = Button(self, "Play")
def _create_snakes(self):
""" 初始化创建长度为3的蛇 """
for snake_number in range(3):
self._create_snake(snake_number)
def _create_snake(self, snake_number):
""" 创建一段蛇身 """
snake = Snake(self)
self.screen_rect = self.screen.get_rect()
snake.x = self.settings.screen_width / 2
snake.y = self.settings.screen_height / 2 + snake_number * self.settings.snake_height
snake.rect.x = snake.x
snake.rect.y = snake.y
self.snakes.append(snake)
def _check_events(self):
# 监视键盘和鼠标的事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
self._check_keydown_events(event)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
self._check_play_button(mouse_pos)
def _check_play_button(self, mouse_pos):
""" 在玩家单击Play按钮时开始新游戏 """
button_clicked = self.play_button.rect.collidepoint(mouse_pos)
if button_clicked and not self.stats.game_active:
# 重置游戏设置
self.stats.game_active = True
# 播放背景音乐
self.background_sound.play(0)
# 隐藏鼠标光标
pygame.mouse.set_visible(False)
self.stats.score = 0
self.sb.prep_score()
self.settings.snake_direction = 'up'
self.settings.update_count = 0
self.settings.game_speed = 1
# 清空余下的蛇身
self.snakes.clear()
# 重新创建蛇身
self._create_snakes()
def _check_keydown_events(self, event):
# 响应按键
if event.key == pygame.K_RIGHT:
if self.settings.snake_direction == 'right':
self._change_speed(1)
elif self.settings.snake_direction == 'left':
self._change_speed(-1)
else:
self.settings.snake_direction = 'right'
elif event.key == pygame.K_LEFT:
if self.settings.snake_direction == 'left':
self._change_speed(1)
elif self.settings.snake_direction == 'right':
self._change_speed(-1)
else:
self.settings.snake_direction = 'left'
elif event.key == pygame.K_UP:
if self.settings.snake_direction == 'up':
self._change_speed(1)
elif self.settings.snake_direction == 'down':
self._change_speed(-1)
else:
self.settings.snake_direction = 'up'
elif event.key == pygame.K_DOWN:
if self.settings.snake_direction == 'down':
self._change_speed(1)
elif self.settings.snake_direction == 'up':
self._change_speed(-1)
else:
self.settings.snake_direction = 'down'
elif event.key == pygame.K_SPACE:
self.stats.game_pause = not self.stats.game_pause
elif event.key == pygame.K_q:
sys.exit()
def _change_speed(self, add):
# 改变蛇的移动速度
if (self.settings.game_speed + add) > 0:
self.settings.game_speed += add
def _update_snakes(self):
""" 更新蛇 """
snake_head = self.snakes[0]
self._create_snake_head(snake_head.rect.x, snake_head.rect.y)
""" 检查是否吃到食物 """
eat_food = self._check_eat_food()
if not eat_food:
self.snakes.pop()
def _check_edges(self):
""" 蛇碰到边缘时采取相应的措施 """
snake_head = self.snakes[0]
if snake_head.check_edges():
self._game_over()
def _check_eat_self(self, snake_head):
""" 是否碰到自己 """
for snake in self.snakes:
if snake.rect.colliderect(snake_head.rect):
self._game_over()
break
def _game_over(self):
# 播放音乐
self.snake_game_over_sound.play(1)
self.stats.game_active = False
# 显示鼠标光标
pygame.mouse.set_visible(True)
def _check_eat_food(self):
""" 检测蛇吃到食物 """
snake_head = self.snakes[0]
food = self.food
if snake_head.rect.colliderect(food.rect):
self.stats.score += self.settings.food_score
# 播放声音
if self.stats.score % 100 == 0:
self.cheer_sound.play(1)
else:
self.snake_eat_food_sound.play(1)
self.sb.prep_score()
self._update_food()
return True
else:
return False
def _update_food(self):
""" 更新食物 """
while True:
if self._check_food():
return
def _check_food(self):
""" 检查新生成的食物是否不与蛇身及记分牌重合 """
food = self.food
food.rect.x = round(
random.randrange(20, self.settings.screen_width - self.settings.snake_width * 2) / 20.0) * 20.0
food.rect.y = round(
random.randrange(20, self.settings.screen_height - self.settings.snake_height * 2) / 20.0) * 20.0
for snake in self.snakes:
if snake.rect.colliderect(food.rect) or self.sb.score_rect.colliderect(food.rect):
return False
return True
def _create_snake_head(self, x, y):
""" 创建蛇头 """
snake = Snake(self)
if self.settings.snake_direction == 'up':
snake.x = x
snake.y = y - self.settings.snake_height
elif self.settings.snake_direction == 'down':
snake.x = x
snake.y = y + self.settings.snake_height
elif self.settings.snake_direction == 'right':
snake.x = x + self.settings.snake_width
snake.y = y
elif self.settings.snake_direction == 'left':
snake.x = x - self.settings.snake_width
snake.y = y
snake.rect.x = snake.x
snake.rect.y = snake.y
self._check_eat_self(snake)
self.snakes.insert(0, snake)
def run_game(self):
""" 开始游戏的主循环 """
while True:
self._check_events()
if self.stats.game_active and not self.stats.game_pause:
if self.settings.update_count > 500: #控制游戏速度
self._update_snakes()
self._check_edges()
self.settings.update_count = 0
self.settings.update_count += self.settings.game_speed
self._update_screen()
def _update_screen(self):
# 每次循环时都会重绘屏幕
self.screen.fill(self.settings.bg_color)
self.food.draw_food()
for snake in self.snakes:
snake.draw_snake()
# 如果游戏处于非活动状态,就绘制Play按钮
if not self.stats.game_active:
self.play_button.draw_button()
# 显示得分
self.sb.show_score()
# 让最近绘制的屏幕可见
pygame.display.flip()
if __name__ == '__main__':
# 创建实例并运行游戏
ai = GluttonousSnake()
ai.run_game()