
image.png
import pygame
import math
import random
import os
# --- 初始化和常量定義 ---
pygame.init()
# 屏幕設(shè)置
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'
# --- 游戲核心類與函數(shù) ---
class Snake:
"""管理蛇的屬性和行為的類"""
def __init__(self):
# 蛇的基礎(chǔ)屬性
self.base_speed = 2 # 蛇的恒定速度
# 定義尾部的半徑錐度,使其看起來更自然
self.tail_taper_radii = [9, 8, 7, 6, 5, 4, 3, 2, 1]
self.reset()
def reset(self):
"""重置蛇的狀態(tài)到初始位置"""
# 定義初始的身體和頭部半徑
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: 蛇現(xiàn)在以恒定速度移動
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])
# 舌頭參數(shù)
self.tongue_count = 0
def move(self):
"""根據(jù)當(dāng)前方向移動蛇"""
keys = pygame.key.get_pressed()
# NEW: 移除了對W/UP鍵的依賴,蛇現(xiàn)在自動移動
# 玩家只控制方向
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)
# 添加一個對應(yīng)的角度占位符
self.circle_radians_list.append(self.circle_radians_list[-1])
# 更新總節(jié)數(shù)
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節(jié)開始檢查,避免頭部剛轉(zhuǎn)彎時誤判)
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: 修復(fù)眼睛繪制邏輯,將其定位在頭部 (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):
"""通用文本繪制函數(shù)"""
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):
"""將新分?jǐn)?shù)添加到高分榜并保存"""
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
# --- 游戲狀態(tài)管理 ---
class Game:
"""管理整個游戲流程和狀態(tài)的類"""
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):
"""重置游戲到初始狀態(tài)"""
self.score = 0
self.snake.reset()
self.apple.spawn()
self.game_state = 'playing'
def run(self):
"""游戲主循環(huán)"""
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):
"""處理不同游戲狀態(tài)下的鼠標(biāo)點擊"""
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()
# 檢查游戲結(jié)束條件
if self.snake.check_collision():
self.high_scores = save_high_scores(self.score, self.high_scores)
self.game_state = 'game_over'
# 繪制分?jǐn)?shù)
draw_text(f'分?jǐn)?shù): {self.score}', SCORE_FONT, COLOR_BLACK, screen, SCREEN_WIDTH - 150, 10)
def draw_game_over_screen(self):
"""繪制游戲結(jié)束界面"""
draw_text('游戲結(jié)束', FONT, COLOR_RED, screen, SCREEN_WIDTH / 2, 80, center=True)
draw_text(f'你的分?jǐn)?shù): {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