383 lines
13 KiB
Python
383 lines
13 KiB
Python
"""
|
||||
|
|
经典贪吃蛇游戏
|
|||
|
|
使用方向键控制蛇的移动,吃到食物后蛇会变长
|
|||
|
|
撞墙或撞到自己则游戏结束
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import pygame
|
|||
|
|
import random
|
|||
|
|
import sys
|
|||
|
|
from enum import Enum
|
|||
|
|
from typing import Tuple, List
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Direction(Enum):
|
|||
|
|
"""方向枚举"""
|
|||
|
|
UP = (0, -1)
|
|||
|
|
DOWN = (0, 1)
|
|||
|
|
LEFT = (-1, 0)
|
|||
|
|
RIGHT = (1, 0)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class SnakeGame:
|
|||
|
|
"""贪吃蛇游戏类"""
|
|||
|
|
|
|||
|
|
def __init__(self, width: int = 800, height: int = 600, cell_size: int = 20):
|
|||
|
|
"""
|
|||
|
|
初始化游戏
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
width: 游戏窗口宽度
|
|||
|
|
height: 游戏窗口高度
|
|||
|
|
cell_size: 每个格子的大小
|
|||
|
|
"""
|
|||
|
|
self.width = width
|
|||
|
|
self.height = height
|
|||
|
|
self.cell_size = cell_size
|
|||
|
|
|
|||
|
|
# 计算网格数量
|
|||
|
|
self.grid_width = width // cell_size
|
|||
|
|
self.grid_height = height // cell_size
|
|||
|
|
|
|||
|
|
# 初始化 Pygame
|
|||
|
|
pygame.init()
|
|||
|
|
self.screen = pygame.display.set_mode((width, height))
|
|||
|
|
pygame.display.set_caption("🐍 经典贪吃蛇")
|
|||
|
|
self.clock = pygame.time.Clock()
|
|||
|
|
|
|||
|
|
# 字体
|
|||
|
|
self.font_large = pygame.font.Font(None, 74)
|
|||
|
|
self.font_medium = pygame.font.Font(None, 48)
|
|||
|
|
self.font_small = pygame.font.Font(None, 36)
|
|||
|
|
|
|||
|
|
# 颜色定义
|
|||
|
|
self.colors = {
|
|||
|
|
'background': (15, 23, 42), # 深蓝色背景
|
|||
|
|
'snake_head': (34, 197, 94), # 绿色蛇头
|
|||
|
|
'snake_body': (22, 163, 74), # 深绿色蛇身
|
|||
|
|
'food': (239, 68, 68), # 红色食物
|
|||
|
|
'grid': (30, 41, 59), # 网格线
|
|||
|
|
'text': (255, 255, 255), # 白色文字
|
|||
|
|
'score_bg': (51, 65, 85) # 分数背景
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 游戏状态
|
|||
|
|
self.reset_game()
|
|||
|
|
|
|||
|
|
def reset_game(self):
|
|||
|
|
"""重置游戏状态"""
|
|||
|
|
# 蛇的初始位置(中心)
|
|||
|
|
start_x = self.grid_width // 2
|
|||
|
|
start_y = self.grid_height // 2
|
|||
|
|
|
|||
|
|
self.snake: List[Tuple[int, int]] = [
|
|||
|
|
(start_x, start_y),
|
|||
|
|
(start_x - 1, start_y),
|
|||
|
|
(start_x - 2, start_y)
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# 初始方向(向右)
|
|||
|
|
self.direction = Direction.RIGHT
|
|||
|
|
self.next_direction = Direction.RIGHT
|
|||
|
|
|
|||
|
|
# 生成第一个食物
|
|||
|
|
self.food = self._generate_food()
|
|||
|
|
|
|||
|
|
# 游戏状态
|
|||
|
|
self.score = 0
|
|||
|
|
self.game_over = False
|
|||
|
|
self.paused = False
|
|||
|
|
self.speed = 10 # 初始速度(FPS)
|
|||
|
|
|
|||
|
|
def _generate_food(self) -> Tuple[int, int]:
|
|||
|
|
"""
|
|||
|
|
生成食物位置
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
食物的坐标 (x, y)
|
|||
|
|
"""
|
|||
|
|
while True:
|
|||
|
|
food_pos = (
|
|||
|
|
random.randint(0, self.grid_width - 1),
|
|||
|
|
random.randint(0, self.grid_height - 1)
|
|||
|
|
)
|
|||
|
|
# 确保食物不会生成在蛇身上
|
|||
|
|
if food_pos not in self.snake:
|
|||
|
|
return food_pos
|
|||
|
|
|
|||
|
|
def _handle_events(self):
|
|||
|
|
"""处理事件"""
|
|||
|
|
for event in pygame.event.get():
|
|||
|
|
if event.type == pygame.QUIT:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
if event.type == pygame.KEYDOWN:
|
|||
|
|
if self.game_over:
|
|||
|
|
if event.key == pygame.K_SPACE or event.key == pygame.K_RETURN:
|
|||
|
|
self.reset_game()
|
|||
|
|
elif event.key == pygame.K_ESCAPE:
|
|||
|
|
return False
|
|||
|
|
else:
|
|||
|
|
# 暂停功能
|
|||
|
|
if event.key == pygame.K_p:
|
|||
|
|
self.paused = not self.paused
|
|||
|
|
|
|||
|
|
# 方向控制
|
|||
|
|
if event.key in [pygame.K_UP, pygame.K_w]:
|
|||
|
|
if self.direction != Direction.DOWN:
|
|||
|
|
self.next_direction = Direction.UP
|
|||
|
|
elif event.key in [pygame.K_DOWN, pygame.K_s]:
|
|||
|
|
if self.direction != Direction.UP:
|
|||
|
|
self.next_direction = Direction.DOWN
|
|||
|
|
elif event.key in [pygame.K_LEFT, pygame.K_a]:
|
|||
|
|
if self.direction != Direction.RIGHT:
|
|||
|
|
self.next_direction = Direction.LEFT
|
|||
|
|
elif event.key in [pygame.K_RIGHT, pygame.K_d]:
|
|||
|
|
if self.direction != Direction.LEFT:
|
|||
|
|
self.next_direction = Direction.RIGHT
|
|||
|
|
elif event.key == pygame.K_ESCAPE:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
def _update(self):
|
|||
|
|
"""更新游戏状态"""
|
|||
|
|
if self.game_over or self.paused:
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 更新方向
|
|||
|
|
self.direction = self.next_direction
|
|||
|
|
|
|||
|
|
# 计算新的头部位置
|
|||
|
|
head_x, head_y = self.snake[0]
|
|||
|
|
dx, dy = self.direction.value
|
|||
|
|
new_head = (head_x + dx, head_y + dy)
|
|||
|
|
|
|||
|
|
# 检查碰撞
|
|||
|
|
if self._check_collision(new_head):
|
|||
|
|
self.game_over = True
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 移动蛇
|
|||
|
|
self.snake.insert(0, new_head)
|
|||
|
|
|
|||
|
|
# 检查是否吃到食物
|
|||
|
|
if new_head == self.food:
|
|||
|
|
self.score += 10
|
|||
|
|
self.food = self._generate_food()
|
|||
|
|
|
|||
|
|
# 每吃 5 个食物加速一次
|
|||
|
|
if self.score % 50 == 0:
|
|||
|
|
self.speed = min(self.speed + 2, 25) # 最大速度 25
|
|||
|
|
else:
|
|||
|
|
# 没吃到食物,移除尾部
|
|||
|
|
self.snake.pop()
|
|||
|
|
|
|||
|
|
def _check_collision(self, position: Tuple[int, int]) -> bool:
|
|||
|
|
"""
|
|||
|
|
检查碰撞
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
position: 要检查的位置
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
是否发生碰撞
|
|||
|
|
"""
|
|||
|
|
x, y = position
|
|||
|
|
|
|||
|
|
# 撞墙
|
|||
|
|
if x < 0 or x >= self.grid_width or y < 0 or y >= self.grid_height:
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
# 撞到自己
|
|||
|
|
if position in self.snake:
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def _draw_grid(self):
|
|||
|
|
"""绘制网格背景"""
|
|||
|
|
for x in range(0, self.width, self.cell_size):
|
|||
|
|
pygame.draw.line(self.screen, self.colors['grid'],
|
|||
|
|
(x, 0), (x, self.height))
|
|||
|
|
for y in range(0, self.height, self.cell_size):
|
|||
|
|
pygame.draw.line(self.screen, self.colors['grid'],
|
|||
|
|
(0, y), (self.width, y))
|
|||
|
|
|
|||
|
|
def _draw_snake(self):
|
|||
|
|
"""绘制蛇"""
|
|||
|
|
for i, (x, y) in enumerate(self.snake):
|
|||
|
|
rect = pygame.Rect(
|
|||
|
|
x * self.cell_size,
|
|||
|
|
y * self.cell_size,
|
|||
|
|
self.cell_size - 2,
|
|||
|
|
self.cell_size - 2
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 蛇头颜色不同
|
|||
|
|
color = self.colors['snake_head'] if i == 0 else self.colors['snake_body']
|
|||
|
|
pygame.draw.rect(self.screen, color, rect, border_radius=5)
|
|||
|
|
|
|||
|
|
# 画蛇眼睛
|
|||
|
|
if i == 0:
|
|||
|
|
eye_size = 4
|
|||
|
|
if self.direction == Direction.UP:
|
|||
|
|
eye1_pos = (x * self.cell_size + 5, y * self.cell_size + 5)
|
|||
|
|
eye2_pos = (x * self.cell_size + self.cell_size - 9, y * self.cell_size + 5)
|
|||
|
|
elif self.direction == Direction.DOWN:
|
|||
|
|
eye1_pos = (x * self.cell_size + 5, y * self.cell_size + self.cell_size - 9)
|
|||
|
|
eye2_pos = (x * self.cell_size + self.cell_size - 9, y * self.cell_size + self.cell_size - 9)
|
|||
|
|
elif self.direction == Direction.LEFT:
|
|||
|
|
eye1_pos = (x * self.cell_size + 5, y * self.cell_size + 5)
|
|||
|
|
eye2_pos = (x * self.cell_size + 5, y * self.cell_size + self.cell_size - 9)
|
|||
|
|
else: # RIGHT
|
|||
|
|
eye1_pos = (x * self.cell_size + self.cell_size - 9, y * self.cell_size + 5)
|
|||
|
|
eye2_pos = (x * self.cell_size + self.cell_size - 9, y * self.cell_size + self.cell_size - 9)
|
|||
|
|
|
|||
|
|
pygame.draw.circle(self.screen, (255, 255, 255), eye1_pos, eye_size)
|
|||
|
|
pygame.draw.circle(self.screen, (255, 255, 255), eye2_pos, eye_size)
|
|||
|
|
|
|||
|
|
def _draw_food(self):
|
|||
|
|
"""绘制食物"""
|
|||
|
|
x, y = self.food
|
|||
|
|
center_x = x * self.cell_size + self.cell_size // 2
|
|||
|
|
center_y = y * self.cell_size + self.cell_size // 2
|
|||
|
|
|
|||
|
|
# 画圆形食物
|
|||
|
|
pygame.draw.circle(self.screen, self.colors['food'],
|
|||
|
|
(center_x, center_y), self.cell_size // 2 - 2)
|
|||
|
|
|
|||
|
|
# 画高光
|
|||
|
|
highlight_pos = (center_x - 3, center_y - 3)
|
|||
|
|
pygame.draw.circle(self.screen, (255, 100, 100), highlight_pos, 3)
|
|||
|
|
|
|||
|
|
def _draw_score(self):
|
|||
|
|
"""绘制分数"""
|
|||
|
|
# 分数背景
|
|||
|
|
score_rect = pygame.Rect(10, 10, 200, 50)
|
|||
|
|
pygame.draw.rect(self.screen, self.colors['score_bg'], score_rect, border_radius=10)
|
|||
|
|
|
|||
|
|
# 分数文字
|
|||
|
|
score_text = self.font_small.render(f"Score: {self.score}", True, self.colors['text'])
|
|||
|
|
self.screen.blit(score_text, (20, 25))
|
|||
|
|
|
|||
|
|
# 速度显示
|
|||
|
|
speed_text = self.font_small.render(f"Speed: {self.speed}", True, self.colors['text'])
|
|||
|
|
self.screen.blit(speed_text, (120, 25))
|
|||
|
|
|
|||
|
|
def _draw_game_over(self):
|
|||
|
|
"""绘制游戏结束画面"""
|
|||
|
|
# 半透明遮罩
|
|||
|
|
overlay = pygame.Surface((self.width, self.height))
|
|||
|
|
overlay.set_alpha(128)
|
|||
|
|
overlay.fill((0, 0, 0))
|
|||
|
|
self.screen.blit(overlay, (0, 0))
|
|||
|
|
|
|||
|
|
# 游戏结束文字
|
|||
|
|
game_over_text = self.font_large.render("GAME OVER", True, self.colors['food'])
|
|||
|
|
text_rect = game_over_text.get_rect(center=(self.width // 2, self.height // 2 - 50))
|
|||
|
|
self.screen.blit(game_over_text, text_rect)
|
|||
|
|
|
|||
|
|
# 分数
|
|||
|
|
score_text = self.font_medium.render(f"Final Score: {self.score}", True, self.colors['text'])
|
|||
|
|
score_rect = score_text.get_rect(center=(self.width // 2, self.height // 2 + 20))
|
|||
|
|
self.screen.blit(score_text, score_rect)
|
|||
|
|
|
|||
|
|
# 提示文字
|
|||
|
|
hint_text = self.font_small.render("Press SPACE to Restart", True, self.colors['text'])
|
|||
|
|
hint_rect = hint_text.get_rect(center=(self.width // 2, self.height // 2 + 80))
|
|||
|
|
self.screen.blit(hint_text, hint_rect)
|
|||
|
|
|
|||
|
|
hint_text2 = self.font_small.render("Press ESC to Quit", True, self.colors['text'])
|
|||
|
|
hint_rect2 = hint_text2.get_rect(center=(self.width // 2, self.height // 2 + 120))
|
|||
|
|
self.screen.blit(hint_text2, hint_rect2)
|
|||
|
|
|
|||
|
|
def _draw_paused(self):
|
|||
|
|
"""绘制暂停画面"""
|
|||
|
|
# 半透明遮罩
|
|||
|
|
overlay = pygame.Surface((self.width, self.height))
|
|||
|
|
overlay.set_alpha(128)
|
|||
|
|
overlay.fill((0, 0, 0))
|
|||
|
|
self.screen.blit(overlay, (0, 0))
|
|||
|
|
|
|||
|
|
# 暂停文字
|
|||
|
|
pause_text = self.font_large.render("PAUSED", True, self.colors['text'])
|
|||
|
|
text_rect = pause_text.get_rect(center=(self.width // 2, self.height // 2))
|
|||
|
|
self.screen.blit(pause_text, text_rect)
|
|||
|
|
|
|||
|
|
hint_text = self.font_small.render("Press P to Resume", True, self.colors['text'])
|
|||
|
|
hint_rect = hint_text.get_rect(center=(self.width // 2, self.height // 2 + 60))
|
|||
|
|
self.screen.blit(hint_text, hint_rect)
|
|||
|
|
|
|||
|
|
def draw(self):
|
|||
|
|
"""绘制游戏画面"""
|
|||
|
|
# 清空屏幕
|
|||
|
|
self.screen.fill(self.colors['background'])
|
|||
|
|
|
|||
|
|
# 绘制网格
|
|||
|
|
self._draw_grid()
|
|||
|
|
|
|||
|
|
# 绘制食物
|
|||
|
|
self._draw_food()
|
|||
|
|
|
|||
|
|
# 绘制蛇
|
|||
|
|
self._draw_snake()
|
|||
|
|
|
|||
|
|
# 绘制分数
|
|||
|
|
self._draw_score()
|
|||
|
|
|
|||
|
|
# 游戏结束画面
|
|||
|
|
if self.game_over:
|
|||
|
|
self._draw_game_over()
|
|||
|
|
|
|||
|
|
# 暂停画面
|
|||
|
|
if self.paused:
|
|||
|
|
self._draw_paused()
|
|||
|
|
|
|||
|
|
# 更新显示
|
|||
|
|
pygame.display.flip()
|
|||
|
|
|
|||
|
|
def run(self):
|
|||
|
|
"""运行游戏主循环"""
|
|||
|
|
running = True
|
|||
|
|
|
|||
|
|
while running:
|
|||
|
|
# 处理事件
|
|||
|
|
running = self._handle_events()
|
|||
|
|
|
|||
|
|
# 更新游戏状态
|
|||
|
|
self._update()
|
|||
|
|
|
|||
|
|
# 绘制画面
|
|||
|
|
self.draw()
|
|||
|
|
|
|||
|
|
# 控制帧率
|
|||
|
|
self.clock.tick(self.speed)
|
|||
|
|
|
|||
|
|
pygame.quit()
|
|||
|
|
sys.exit()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
"""主函数"""
|
|||
|
|
print("=" * 60)
|
|||
|
|
print("🐍 经典贪吃蛇游戏")
|
|||
|
|
print("=" * 60)
|
|||
|
|
print("\n游戏说明:")
|
|||
|
|
print(" - 使用方向键 ↑↓←→ 或 WASD 控制蛇的移动")
|
|||
|
|
print(" - 吃到红色食物可以得分并变长")
|
|||
|
|
print(" - 撞墙或撞到自己则游戏结束")
|
|||
|
|
print(" - 按 P 键暂停游戏")
|
|||
|
|
print(" - 按 ESC 键退出游戏")
|
|||
|
|
print(" - 按空格键重新开始")
|
|||
|
|
print("\n每吃 5 个食物,速度会加快哦!")
|
|||
|
|
print("=" * 60)
|
|||
|
|
print("\n正在启动游戏...")
|
|||
|
|
|
|||
|
|
game = SnakeGame()
|
|||
|
|
game.run()
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|