```python
import pygame
import random
import json
import os
from datetime import datetime
# 初始化
pygame.init()
pygame.font.init()
# 颜色定义
white = (255, 255, 255)
black = (0, 0, 0)
red = (213, 50, 80)
green_head = (0, 100, 0)
green_body = (0, 200, 0)
blue = (50, 153, 213)
gray = (200, 200, 200)
dark_gray = (100, 100, 100)
gold = (255, 215, 0)
# bg_color = (70, 130, 180) # 钢蓝色
# bg_color = (240, 248, 255) # 爱丽丝蓝
# bg_color = (220, 245, 220) # 淡绿色
bg_color = (255, 248, 220) # 柯林斯黄(米白)
# bg_color = (30, 30, 30) # 深灰(暗色模式)
# 屏幕尺寸
width = 800
height = 600
# 格子类型标识
EMPTY = 0
SNAKE_BODY = 1
WALL = 2
FOOD = 3
# 字体
font_title = pygame.font.SysFont("simhei", 72)
font_small = pygame.font.SysFont("simhei", 28)
font_tiny = pygame.font.SysFont("simhei", 22)
# 记录文件
RECORD_FILE = "snake_records.json"
class Snake:
def __init__(self, block_size=20):
self.block_size = block_size
self.direction = 'RIGHT'
self.change_to = 'RIGHT'
# 初始位置
self.head_x = width // 2 // block_size * block_size
self.head_y = height // 2 // block_size * block_size
self.body = [[self.head_x, self.head_y]]
self.length = 1
# 初始基础速度
self.base_speed = 5
self.max_speed = 15 # 防止太快无法操作
@property
def speed(self):
"""动态计算当前速度:基于长度增长"""
added_speed = (self.length - 1) // 3 # 每3段加1速
return min(self.base_speed + added_speed, self.max_speed)
def update_direction(self):
"""根据输入更新方向(防止反向)"""
if self.change_to == 'UP' and self.direction != 'DOWN':
self.direction = 'UP'
elif self.change_to == 'DOWN' and self.direction != 'UP':
self.direction = 'DOWN'
elif self.change_to == 'LEFT' and self.direction != 'RIGHT':
self.direction = 'LEFT'
elif self.change_to == 'RIGHT' and self.direction != 'LEFT':
self.direction = 'RIGHT'
def move(self):
"""移动蛇头并更新身体"""
x_change = y_change = 0
if self.direction == 'UP':
y_change = -self.block_size
elif self.direction == 'DOWN':
y_change = self.block_size
elif self.direction == 'LEFT':
x_change = -self.block_size
elif self.direction == 'RIGHT':
x_change = self.block_size
new_head = [self.head_x + x_change, self.head_y + y_change]
self.body.append(new_head)
if len(self.body) > self.length:
del self.body[0]
self.head_x, self.head_y = new_head
def grow(self):
"""增加长度"""
self.length += 1
def check_collision(self):
head_x, head_y = self.head_x, self.head_y
grid_x, grid_y = head_x // self.block_size, head_y // self.block_size
# 检查是否撞墙 or 自己
if self.game_map.is_blocked(grid_x, grid_y):
return True
# 可选:检查超出屏幕(但上面 is_blocked 已包括边界)
return False
def draw(self, screen):
"""绘制蛇"""
for part in self.body[:-1]:
pygame.draw.rect(screen, green_body, [*part, self.block_size, self.block_size])
# 头部
head = self.body[-1]
pygame.draw.rect(screen, green_head, [*head, self.block_size, self.block_size])
class Food:
def __init__(self, block_size=20):
self.block_size = block_size
self.x = 0
self.y = 0
self.color = red
def spawn(self, game_map):
"""让地图分配一个新的食物位置"""
pos = game_map.place_food()
if pos:
self.x, self.y = pos
def draw(self, screen):
rect = pygame.Rect(self.x, self.y, self.block_size, self.block_size)
pygame.draw.rect(screen, self.color, rect)
class Leaderboard:
"""排行榜类:加载、保存、排序记录"""
def __init__(self, filename=RECORD_FILE):
self.filename = filename
self.records = self.load()
def load(self):
if not os.path.exists(self.filename):
return []
try:
with open(self.filename, 'r', encoding='utf-8') as f:
data = json.load(f)
for item in data:
if "name" not in item:
item["name"] = "无名"
return sorted(data, key=lambda x: x["score"], reverse=True)
except Exception as e:
print("加载排行榜失败:", e)
return []
def save(self, score, length, name="无名"):
new_record = {
"name": name,
"score": score,
"length": length,
"timestamp": datetime.now().isoformat()
}
self.records.append(new_record)
self.records = sorted(self.records, key=lambda x: x["score"], reverse=True)[:50]
with open(self.filename, 'w', encoding='utf-8') as f:
json.dump(self.records, f, ensure_ascii=False, indent=2)
def top(self, n=5):
return self.records[:n]
def best(self):
return self.records[0] if self.records else None
class GameMap:
def __init__(self, width_in_blocks, height_in_blocks):
self.width = width_in_blocks
self.height = height_in_blocks
self.block_size = 20 # 与 Snake 和 Food 一致
self.grid = [[EMPTY for _ in range(self.width)] for _ in range(self.height)]
# 墙体列表(保存 (x, y) 坐标)
self.walls = []
self._create_walls()
def _create_walls(self):
"""创建边界墙 + 中央装饰性墙体"""
# 边界墙(四周)
for x in range(self.width):
self._add_wall(x, 0)
self._add_wall(x, self.height - 1)
for y in range(self.height):
self._add_wall(0, y)
self._add_wall(self.width - 1, y)
# 可选:中间加一些障碍
for i in range(5, 10):
self._add_wall(10, i)
for i in range(15, 20):
self._add_wall(i, 10)
def _add_wall(self, x, y):
"""内部添加墙并更新 grid"""
if 0 <= x < self.width and 0 <= y < self.height:
self.walls.append((x, y))
self.grid[y][x] = WALL
def is_blocked(self, x, y):
"""
判断某个格子是否被占用(不能移动到)
:param x: 格子列(以 block 为单位)
:param y: 格子行
:return: True 表示无法进入
"""
if not (0 <= x < self.width and 0 <= y < self.height):
return True # 超出边界
return self.grid[y][x] in [WALL, SNAKE_BODY]
def place_food(self):
"""
在空地上随机放置食物
:return: (x, y) 食物位置(像素级)
"""
available = []
for y in range(self.height):
for x in range(self.width):
if self.grid[y][x] == EMPTY:
available.append((x, y))
if not available:
return None
fx, fy = random.choice(available)
self.grid[fy][fx] = FOOD
return fx * self.block_size, fy * self.block_size
def update_snake_position(self, snake_body):
"""
用当前蛇身更新地图标记
:param snake_body: 蛇身体列表 [(x1,y1), ...](像素坐标)
"""
# 先清除旧的蛇身标记
for y in range(self.height):
for x in range(self.width):
if self.grid[y][x] == SNAKE_BODY:
self.grid[y][x] = EMPTY
# 重新标记蛇身(跳过头部?或包含全部)
for part in snake_body[:-1]: # 排除尾部?这里可以调整策略
sx, sy = part[0] // self.block_size, part[1] // self.block_size
if 0 <= sx < self.width and 0 <= sy < self.height:
self.grid[sy][sx] = SNAKE_BODY
def draw(self, screen):
"""绘制墙体"""
wall_color = (80, 80, 80) # 灰色墙体
for x, y in self.walls:
rect = pygame.Rect(x * self.block_size, y * self.block_size,
self.block_size, self.block_size)
pygame.draw.rect(screen, wall_color, rect)
# 可选:加边框
pygame.draw.rect(screen, (50, 50, 50), rect, border_width=2)
class UI:
"""用户界面类:负责所有文字渲染"""
def __init__(self, screen):
self.screen = screen
self.font_title = font_title
self.font_small = font_small
self.font_tiny = font_tiny
def message_center(self, msg, color, y_offset=0, font=None):
font = font or self.font_small
text = font.render(msg, True, color)
rect = text.get_rect(center=(width // 2, height // 2 + y_offset))
self.screen.blit(text, rect)
def your_score(self, score, length):
text = self.font_small.render(f"得分: {score} | 长度: {length}", True, black)
self.screen.blit(text, (10, 10))
def show_start_screen(self, leaderboard):
self.screen.fill(bg_color)
# 标题
title = self.font_title.render("🐍 贪吃蛇", True, green_head)
title_rect = title.get_rect(center=(width // 2, 180))
self.screen.blit(title, title_rect)
# 开始提示
start_msg = self.font_small.render("按 【空格键】 开始游戏", True, black)
start_rect = start_msg.get_rect(center=(width // 2, height // 2))
self.screen.blit(start_msg, start_rect)
# 最高分展示
best = leaderboard.best()
if best:
best_text = self.font_small.render(f"🏆 最高分: {best['score']} ({best['name']})", True, gold)
best_rect = best_text.get_rect(center=(width // 2, height - 250))
self.screen.blit(best_text, best_rect)
# 排行榜标题
leader_title = self.font_tiny.render("🎮 近期高手榜", True, black)
leader_rect = leader_title.get_rect(center=(width // 2, height - 210))
self.screen.blit(leader_title, leader_rect)
# Top 5 列表
y_start = height - 180
line_height = 28
for i, game in enumerate(leaderboard.top(5)):
color = gold if i == 0 else (blue if i == 1 else dark_gray)
name_display = game["name"][:6] + "..." if len(game["name"]) > 6 else game["name"]
text = self.font_tiny.render(f"{i + 1}. {name_display:<7} — {game['score']:>3}分", True, color)
rect = text.get_rect(center=(width // 2, y_start + i * line_height))
if rect.bottom <= height - 10:
self.screen.blit(text, rect)
def game_over_screen(self, score, length, leaderboard, on_get_name):
name = on_get_name() # 回调获取名字
leaderboard.save(score, length, name)
records = leaderboard.records
while True:
self.screen.fill(bg_color)
self.message_center("游戏结束!", red, -180, self.font_title)
self.your_score(score, length)
current_text = self.font_small.render(f"🎮 {name},你得了 {score} 分!", True, green_head)
self.screen.blit(current_text, (width // 2 - current_text.get_width() // 2, height // 2 - 60))
if records:
best_entry = records[0]
best_line = self.font_small.render(f"🏆 最高分: {best_entry['score']} ({best_entry['name']})", True, gold)
self.screen.blit(best_line, (width // 2 - best_line.get_width() // 2, height // 2 - 20))
y_offset = height // 2 + 25
for i, game in enumerate(records[:5]):
color = gold if i == 0 else (blue if game["name"] == name else dark_gray)
text = self.font_tiny.render(
f"{i+1:2d}. {game['name']:<8} — {game['score']:>3}分 (长度{game['length']})",
True, color)
rect = text.get_rect(center=(width // 2, y_offset))
self.screen.blit(text, rect)
y_offset += 30
self.message_center("按【C键】重新开始", black, 140)
self.message_center("按【Q键】退出游戏", black, 170)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_c:
return True
elif event.key == pygame.K_q:
return False
class GameController:
def __init__(self):
self.width = width
self.height = height
self.screen = pygame.display.set_mode((width, height))
pygame.display.set_caption('🐍 贪吃蛇')
self.block_size = 20
cols = self.width // self.block_size
rows = self.height // self.block_size
self.game_map = GameMap(cols, rows)
self.food = Food(self.block_size)
self.snake = Snake(self.block_size, self.game_map) # 传入 map
self.clock = pygame.time.Clock()
self.ui = UI(self.screen)
# self.leaderboard = LeaderBoard("scores.json")
def reset_game(self):
self.snake = Snake()
self.food = Food()
self.game_over = False
def show_start_screen(self):
"""✅ 新增:显示主菜单界面"""
while True:
self.ui.show_start_screen(self.leaderboard)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
return # 进入游戏
def get_player_name(self):
name = ""
max_length = 10
input_active = True
while input_active:
self.screen.fill(bg_color) # ✅ 每次清屏
# 提示文字
prompt = font_small.render("请输入你的名字:", True, black)
self.screen.blit(prompt, (width // 2 - prompt.get_width() // 2, height // 2 - 100))
# 输入框
input_area = pygame.Rect(width // 2 - 150, height // 2 - 30, 300, 60)
pygame.draw.rect(self.screen, gray, input_area, border_radius=8)
pygame.draw.rect(self.screen, black, input_area, width=2, border_radius=8)
# 当前输入内容
display_text = name if name else "点击这里输入..."
text_surface = font_small.render(display_text, True, black if name else dark_gray)
self.screen.blit(text_surface, (input_area.x + 10, input_area.y + 10))
# 提示信息
hint1 = font_tiny.render("支持中文/英文 | 最多10字符", True, dark_gray)
hint2 = font_tiny.render("回车确认,退格删除", True, dark_gray)
self.screen.blit(hint1, (width // 2 - hint1.get_width() // 2, height // 2 + 60))
self.screen.blit(hint2, (width // 2 - hint2.get_width() // 2, height // 2 + 90))
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key in [pygame.K_RETURN, pygame.K_KP_ENTER]:
input_active = False
elif event.key == pygame.K_BACKSPACE:
name = name[:-1]
else:
char = event.unicode
if char.isprintable() and not char.isspace() and len(name) < max_length:
name += char
return name.strip() or "无名"
def run(self):
while True:
self.ui.show_start_screen(self.leaderboard)
self.reset_game()
self.food.spawn(self.game_map) # 第一次生成食物
while not self.game_over:
keys = pygame.key.get_pressed()
# 方向控制...
if keys[pygame.K_UP]: self.snake.change_to = 'UP'
if keys[pygame.K_DOWN]: self.snake.change_to = 'DOWN'
if keys[pygame.K_LEFT]: self.snake.change_to = 'LEFT'
if keys[pygame.K_RIGHT]: self.snake.change_to = 'RIGHT'
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
self.snake.update_direction()
self.snake.move()
# 更新地图中的蛇身标记
self.game_map.update_snake_position(self.snake.body)
# 吃食物
if self.snake.head_x == self.food.x and self.snake.head_y == self.food.y:
self.snake.grow()
self.food.spawn(self.game_map) # 通知地图重新生成
if self.snake.check_collision():
self.game_over = True
# 绘制
self.screen.fill(bg_color)
self.game_map.draw(self.screen)
self.food.draw(self.screen)
self.snake.draw(self.screen)
self.ui.your_score(self.snake.length - 1, self.snake.length)
pygame.display.update()
self.clock.tick(self.snake.speed)
# 启动游戏
if __name__ == "__main__":
game = GameController()
game.run()
```
帮我修改程序当中的错误