image.png
import pygame
import math
import random
import os
# --- 初始化和常量定义 ---
pygame.init()
# 屏幕设置
SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("贪吃蛇小游戏")
# 颜色
COLOR_SKYBLUE = 'skyblue'
COLOR_BLACK = 'black'
COLOR_WHITE = 'white'
COLOR_RED = 'red'
COLOR_GREEN = 'green'
COLOR_GOLD = 'gold'
# 字体
try:
# 尝试加载一个常见的中文字体
FONT = pygame.font.Font('simhei.ttf', 36)
SCORE_FONT = pygame.font.Font('simhei.ttf', 28)
LEADERBOARD_FONT = pygame.font.Font('simhei.ttf', 24)
except FileNotFoundError:
# 如果找不到,使用默认字体
FONT = pygame.font.Font(None, 48)
SCORE_FONT = pygame.font.Font(None, 36)
LEADERBOARD_FONT = pygame.font.Font(None, 30)
# 游戏时钟
clock = pygame.time.Clock()
FPS = 60
# 高分榜文件
HIGHSCORE_FILE = 'highscores.txt'
# --- 游戏核心类与函数 ---
class Snake:
"""管理蛇的属性和行为的类"""
def __init__(self):
# 蛇的基础属性
self.base_speed = 2 # 蛇的恒定速度
# 定义尾部的半径锥度,使其看起来更自然
self.tail_taper_radii = [9, 8, 7, 6, 5, 4, 3, 2, 1]
self.reset()
def reset(self):
"""重置蛇的状态到初始位置"""
# 定义初始的身体和头部半径
self.main_body_radii = [
12, 16, 14, 12, 10, 10, 10, 10, 10, 10,
10, 10, 10, 10, 10, 10
]
# 完整的半径列表 = 身体 + 尾巴
self.circle_radius_list = self.main_body_radii + self.tail_taper_radii
self.num_circles = len(self.circle_radius_list)
self.circle_radians_list = [0] * self.num_circles
# NEW: 蛇现在以恒定速度移动
self.speed = self.base_speed
self.angle = 0
self.angle_change = 2
# 初始化蛇的身体位置
self.body = []
for i in range(self.num_circles):
self.body.append([SCREEN_WIDTH // 2 - i * 6 * 1.5, SCREEN_HEIGHT // 2])
# 舌头参数
self.tongue_count = 0
def move(self):
"""根据当前方向移动蛇"""
keys = pygame.key.get_pressed()
# NEW: 移除了对W/UP键的依赖,蛇现在自动移动
# 玩家只控制方向
if keys[pygame.K_a] or keys[pygame.K_LEFT]:
self.angle -= self.angle_change
if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
self.angle += self.angle_change
self.angle %= 360
# 计算并更新头部位置
head_x, head_y = self.body[0]
dx = self.speed * math.cos(math.radians(self.angle))
dy = self.speed * math.sin(math.radians(self.angle))
self.body[0] = [head_x + dx, head_y + dy]
self.circle_radians_list[0] = math.radians(self.angle)
# 更新身体其他部分的位置
for i in range(1, self.num_circles):
prev_x, prev_y = self.body[i-1]
curr_x, curr_y = self.body[i]
distance = math.hypot(prev_x - curr_x, prev_y - curr_y)
self.circle_radians_list[i] = math.atan2(prev_y - curr_y, prev_x - curr_x)
# 保持每个身体部分之间的固定距离
target_dist = (self.circle_radius_list[i-1] + self.circle_radius_list[i]) * 0.4
if distance > target_dist:
ratio = target_dist / distance
self.body[i][0] = prev_x - (prev_x - curr_x) * ratio
self.body[i][1] = prev_y - (prev_y - curr_y) * ratio
def grow(self):
"""蛇吃掉苹果后变长,保持尾部锥度"""
# 在尾部末端添加一个新的物理分段
self.body.append(self.body[-1])
# NEW: 在尾部锥度开始前插入一个新的身体半径,使身体变长
self.circle_radius_list.insert(-len(self.tail_taper_radii), 10)
# 添加一个对应的角度占位符
self.circle_radians_list.append(self.circle_radians_list[-1])
# 更新总节数
self.num_circles = len(self.body)
def check_collision(self):
"""检查蛇是否碰到边缘或自己"""
head_x, head_y = self.body[0]
head_radius = self.circle_radius_list[0]
# 1. 检查边缘碰撞
if not (head_radius < head_x < SCREEN_WIDTH - head_radius and \
head_radius < head_y < SCREEN_HEIGHT - head_radius):
return True
# 2. 检查自身碰撞 (从第5节开始检查,避免头部刚转弯时误判)
for i in range(5, self.num_circles):
dist = math.hypot(head_x - self.body[i][0], head_y - self.body[i][1])
if dist < (self.circle_radius_list[0] + self.circle_radius_list[i]) * 0.5:
return True
return False
def draw(self):
"""在屏幕上绘制蛇"""
# --- 计算身体多边形的锚点 ---
polygon_point_pos_list = [(0, 0)] * (self.num_circles * 2)
pattern_point_1_pos_list = [(0, 0)] * self.num_circles
pattern_point_2_pos_list = [(0, 0)] * self.num_circles
for j in range(self.num_circles):
x, y = self.body[j]
radius = self.circle_radius_list[j]
angle = self.circle_radians_list[j]
anchor_1 = (x + radius * math.cos(angle - math.pi/2), y + radius * math.sin(angle - math.pi/2))
polygon_point_pos_list[j] = anchor_1
pattern_point_1_pos_list[j] = anchor_1
anchor_2 = (x + radius * math.cos(angle + math.pi/2), y + radius * math.sin(angle + math.pi/2))
polygon_point_pos_list[self.num_circles * 2 - 1 - j] = anchor_2
pattern_point_2_pos_list[j] = anchor_2
# --- 绘制 ---
# 身体
pygame.draw.polygon(screen, COLOR_BLACK, polygon_point_pos_list)
pygame.draw.polygon(screen, COLOR_WHITE, polygon_point_pos_list, 3)
# 花纹
for k in range(3, self.num_circles - len(self.tail_taper_radii), 2):
pygame.draw.line(screen, COLOR_WHITE, pattern_point_1_pos_list[k], pattern_point_2_pos_list[k], 2)
# NEW: 修复眼睛绘制逻辑,将其定位在头部 (self.body[0])
eye_offset = 4 # 眼睛离头部边缘的距离
eye_radius = 3 # 眼睛的大小
head_center = self.body[0]
head_radius = self.circle_radius_list[0]
head_angle = self.circle_radians_list[0]
# 计算眼睛相对于头部中心、半径和方向的位置
eye_dist_from_center = head_radius - eye_offset
# 眼睛1
eye_1_angle = head_angle - math.pi/4.5 # 将眼睛放在中心线40度的位置
eye_1_pos = (
head_center[0] + eye_dist_from_center * math.cos(eye_1_angle),
head_center[1] + eye_dist_from_center * math.sin(eye_1_angle)
)
pygame.draw.circle(screen, COLOR_WHITE, eye_1_pos, eye_radius)
pygame.draw.circle(screen, COLOR_BLACK, eye_1_pos, eye_radius - 1) # 瞳孔
# 眼睛2
eye_2_angle = head_angle + math.pi/4.5
eye_2_pos = (
head_center[0] + eye_dist_from_center * math.cos(eye_2_angle),
head_center[1] + eye_dist_from_center * math.sin(eye_2_angle)
)
pygame.draw.circle(screen, COLOR_WHITE, eye_2_pos, eye_radius)
pygame.draw.circle(screen, COLOR_BLACK, eye_2_pos, eye_radius - 1) # 瞳孔
# 舌头
self.tongue_count = (self.tongue_count + 1) % 120
if self.tongue_count < 15:
tongue_length = self.circle_radius_list[0] * 1.5
for i in range(int(tongue_length)):
pos = (
self.body[0][0] + i * math.cos(self.circle_radians_list[0]),
self.body[0][1] + i * math.sin(self.circle_radians_list[0])
)
if i > tongue_length - 5:
pygame.draw.circle(screen, COLOR_RED, (pos[0] + 3, pos[1] - 3), 1)
pygame.draw.circle(screen, COLOR_RED, (pos[0] - 3, pos[1] + 3), 1)
else:
pygame.draw.circle(screen, COLOR_RED, pos, 1)
class Apple:
"""管理苹果的类"""
def __init__(self):
self.radius = 10
self.spawn()
def spawn(self):
"""在随机位置生成一个新苹果"""
self.pos = [
random.randint(self.radius, SCREEN_WIDTH - self.radius),
random.randint(self.radius, SCREEN_HEIGHT - self.radius)
]
def draw(self):
"""在屏幕上绘制苹果"""
pygame.draw.circle(screen, COLOR_RED, self.pos, self.radius)
pygame.draw.circle(screen, COLOR_BLACK, self.pos, self.radius, 1)
def draw_text(text, font, color, surface, x, y, center=False):
"""通用文本绘制函数"""
textobj = font.render(text, 1, color)
textrect = textobj.get_rect()
if center:
textrect.center = (x, y)
else:
textrect.topleft = (x, y)
surface.blit(textobj, textrect)
return textrect
def load_high_scores():
"""从文件加载高分榜"""
if not os.path.exists(HIGHSCORE_FILE):
return []
with open(HIGHSCORE_FILE, 'r') as f:
scores = [int(line.strip()) for line in f if line.strip().isdigit()]
return sorted(scores, reverse=True)[:10]
def save_high_scores(score, scores_list):
"""将新分数添加到高分榜并保存"""
scores_list.append(score)
scores_list = sorted(list(set(scores_list)), reverse=True)[:10] # 去重并排序
with open(HIGHSCORE_FILE, 'w') as f:
for s in scores_list:
f.write(str(s) + '\n')
return scores_list
# --- 游戏状态管理 ---
class Game:
"""管理整个游戏流程和状态的类"""
def __init__(self):
self.game_state = 'start_screen'
self.score = 0
self.snake = Snake()
self.apple = Apple()
self.high_scores = load_high_scores()
def reset_game(self):
"""重置游戏到初始状态"""
self.score = 0
self.snake.reset()
self.apple.spawn()
self.game_state = 'playing'
def run(self):
"""游戏主循环"""
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
self.handle_mouse_click(event.pos)
screen.fill(COLOR_SKYBLUE)
if self.game_state == 'start_screen':
self.draw_start_screen()
elif self.game_state == 'playing':
self.run_game_logic()
elif self.game_state == 'game_over':
self.draw_game_over_screen()
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
quit()
def handle_mouse_click(self, pos):
"""处理不同游戏状态下的鼠标点击"""
if self.game_state == 'start_screen':
if self.start_button_rect.collidepoint(pos):
self.reset_game()
elif self.game_state == 'game_over':
if self.restart_button_rect.collidepoint(pos):
self.reset_game()
if self.quit_button_rect.collidepoint(pos):
pygame.quit()
quit()
def draw_start_screen(self):
"""绘制开始界面"""
draw_text('程序性动画贪吃蛇', FONT, COLOR_BLACK, screen, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 4, center=True)
self.start_button_rect = draw_text('开始游戏', FONT, COLOR_GREEN, screen, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, center=True)
def run_game_logic(self):
"""运行核心游戏逻辑"""
# 移动和绘制蛇
self.snake.move()
self.snake.draw()
# 绘制苹果
self.apple.draw()
# 检查蛇头和苹果的碰撞
head_pos = self.snake.body[0]
dist = math.hypot(head_pos[0] - self.apple.pos[0], head_pos[1] - self.apple.pos[1])
if dist < self.snake.circle_radius_list[0] + self.apple.radius:
self.score += 10
self.snake.grow()
self.apple.spawn()
# 检查游戏结束条件
if self.snake.check_collision():
self.high_scores = save_high_scores(self.score, self.high_scores)
self.game_state = 'game_over'
# 绘制分数
draw_text(f'分数: {self.score}', SCORE_FONT, COLOR_BLACK, screen, SCREEN_WIDTH - 150, 10)
def draw_game_over_screen(self):
"""绘制游戏结束界面"""
draw_text('游戏结束', FONT, COLOR_RED, screen, SCREEN_WIDTH / 2, 80, center=True)
draw_text(f'你的分数: {self.score}', SCORE_FONT, COLOR_BLACK, screen, SCREEN_WIDTH / 2, 150, center=True)
# 绘制排行榜
draw_text('排行榜', SCORE_FONT, COLOR_GOLD, screen, SCREEN_WIDTH / 2, 220, center=True)
y_pos = 260
for i, score in enumerate(self.high_scores):
draw_text(f'{i+1}. {score}', LEADERBOARD_FONT, COLOR_BLACK, screen, SCREEN_WIDTH / 2, y_pos, center=True)
y_pos += 30
# 绘制按钮
self.restart_button_rect = draw_text('重新开始', FONT, COLOR_GREEN, screen, SCREEN_WIDTH / 2, SCREEN_HEIGHT - 120, center=True)
self.quit_button_rect = draw_text('退出', FONT, COLOR_RED, screen, SCREEN_WIDTH / 2, SCREEN_HEIGHT - 60, center=True)
# --- 启动游戏 ---
if __name__ == '__main__':
game = Game()
game.run()
image.png