pygame AI snake 大乱斗
今年正值AI技术蓬勃发展的蛇年,趁着蛇年尾声,整一款AI贪吃蛇游戏来应景吧!
先看看游戏界面截图,感受一下氛围:
🎮 游戏概述
小游戏融合了经典贪吃蛇玩法与AI技术的策略游戏。游戏提供两种主要模式:
单人模式:玩家控制一条蛇与AI蛇对战
AI对战观战模式:观看多个AI蛇互相竞争
游戏采用数码风格设计,具有以下核心特点:
网格化游戏界面
蛇身渐变色效果
实时显示AI路径规划
数码风格字体和视觉效果
多玩家分数系统
🧠 AI路径规划系统详解
1. A寻路算法*
AI蛇使用A*算法作为核心路径规划算法,这是游戏中最智能的路径规划基础。
算法特点:
启发式搜索:使用曼哈顿距离作为启发函数
路径优化:寻找最短路径到食物
动态调整:根据游戏状态实时更新路径
算法流程:
初始化:
将蛇头位置加入开放列表
设置起点g值(实际代价)为0
计算起点f值(g值 + 启发式值)
主循环:
从开放列表取出f值最小的节点
如果该节点是食物位置,重建路径
检查四个方向(上、下、左、右)的相邻节点
对于每个相邻节点:
跳过边界外的节点
跳过不安全的位置
计算新的g值
如果新g值更小,更新节点信息
计算f值(g值 + 启发式值)
将节点加入开放列表
路径重建:
从食物位置回溯到蛇头位置
反转路径得到从蛇头到食物的路径
启发式函数:
def heuristic(self, a, b):"""启发式函数 - 曼哈顿距离"""return abs(a[0] - b[0]) + abs(a[1] - b[1])
2. 动态路径调整
AI蛇不会只计算一次路径,而是根据游戏状态动态调整路径:
定期更新:
每0.5秒重新计算路径
适应游戏状态变化
安全性检查:
移动前检查路径是否安全
路径上的位置是否会被其他蛇占据
预测其他蛇的移动位置
路径中断处理:
当路径变得不安全时,立即重新规划路径
确保AI蛇不会沿着危险路径前进
3. 安全移动策略
当A*算法无法找到路径或路径不安全时,AI蛇使用安全移动策略:
安全方向评估:
检查四个方向的安全性
判断移动后是否会碰撞
预测其他蛇的移动位置
风险最小化:
如果没有安全方向,计算每个方向的风险值
风险因素:
距离边界越近风险越高
距离其他蛇越近风险越高
位置在蛇的路径上风险增加
选择风险最小的方向移动
风险计算函数:
def calculate_risk(self, position, other_snakes):"""计算移动到指定位置的风险值"""risk = 0# 边界风险 - 增加权重risk += min(position[0], GRID_WIDTH - 1 - position[0]) * self.boundary_risk_factorrisk += min(position[1], GRID_HEIGHT - 1 - position[1]) * self.boundary_risk_factor# 特别关注靠近边界的位置if position[0] < self.boundary_safe_distance or position[0] > GRID_WIDTH - 1 - self.boundary_safe_distance:risk += 10.0if position[1] < self.boundary_safe_distance or position[1] > GRID_HEIGHT - 1 - self.boundary_safe_distance:risk += 10.0# 其他蛇的距离风险for snake in other_snakes:if snake.player_id != self.player_id and snake.alive and snake.body:head = snake.body[0]distance = math.sqrt((position[0] - head[0])**2 + (position[1] - head[1])**2)risk += max(0, 5 - distance) * 2 # 距离越近风险越高# 如果位置在蛇的路径上,增加风险if snake.path and position in snake.path:risk += 3return risk
4. 碰撞预测
AI蛇能够预测其他蛇的移动,避免碰撞:
蛇头位置预测:
def predict_head_position(self, snake):"""预测蛇的下一步位置"""head = snake.body[0].copy()if snake.direction == "UP":head[1] -= 1elif snake.direction == "DOWN":head[1] += 1elif snake.direction == "LEFT":head[0] -= 1elif snake.direction == "RIGHT":head[0] += 1return head
路径冲突检测:
检查路径上的位置是否会被其他蛇占据
预测其他蛇头可能移动到的位置
避免移动到危险位置
5. 边界处理优化
针对边界问题进行了专门优化:
边界风险评估:
提高边界风险权重
增加边界位置代价
使AI更倾向于远离边界
边界安全距离:
定义边界安全距离
位置在安全距离内时增加额外风险
接近边界时重新规划:
当蛇接近边界时强制重新规划路径
确保及时调整方向避免撞墙
🎮 游戏功能详解
1. 游戏模式
单人模式
玩家控制一条蛇(WASD键)
与2个AI蛇对战
竞争吃食物获得高分
AI对战观战模式
观看4个AI蛇互相竞争
观察AI的生存策略
无需玩家操作
2. 核心游戏机制
蛇移动系统
蛇头控制方向
吃到食物后身体增长
撞墙或撞到自身/其他蛇则死亡
食物系统
随机生成食物
吃到食物得分增加
食物不会出现在蛇身上
分数系统
吃食物得分
存活时间越长分数越高
实时显示玩家分数和状态
3. 游戏界面
主菜单
数码风格标题
三角形选择指示器
操作提示
游戏界面
网格背景
蛇身渐变色着色
蛇头显示玩家ID
AI路径可视化
暂停界面
居中显示"PAUSED"
继续游戏提示
返回主菜单选项
游戏结束界面
显示获胜玩家
重新开始选项
返回主菜单选项
4. 控制方式
菜单导航
上/下方向键:选择菜单选项
回车键/空格键:确认选择
游戏控制
空格键:暂停/继续游戏
ESC键:返回主菜单
R键:重新开始游戏
单人模式
WASD键:控制蛇移动
🚀 技术亮点
1. 智能避障系统
多层安全检测:边界检查、自身碰撞检查、其他蛇碰撞检查
实时风险评估:动态计算每个移动方向的风险值
预测性移动:预测其他蛇的移动位置
2. 高效路径规划
A*算法优化:使用最小堆提高搜索效率
路径缓存:重用部分路径减少计算量
增量式更新:只更新变化的部分路径
3. 动态适应性
游戏状态感知:根据其他蛇的位置调整策略
路径中断处理:当路径被阻挡时快速调整
多策略切换:在最优路径和安全移动之间动态切换
4. 可视化反馈
路径颜色编码:白色安全,红色危险,橙色边界危险
蛇身渐变色:视觉上区分蛇头和蛇身
分数实时显示:了解游戏进展
📊 性能优化
路径更新频率控制:
限制路径更新频率(0.5秒/次)
避免过度计算影响游戏性能
局部路径检查:
只检查路径上的前几个位置
避免全路径检查的计算开销
高效碰撞检测:
使用空间分区优化碰撞检测
只检查相关区域的对象
算法优化:
使用曼哈顿距离替代欧几里得距离
减少不必要的计算
🎯 总结
AI路径规划系统是一个复杂而高效的智能系统,结合了:
A*寻路算法的基础
动态路径调整的灵活性
安全移动策略的鲁棒性
碰撞预测的预见性
这些技术共同作用,使AI蛇能够智能地寻找食物、避开障碍物,并在复杂环境中生存。游戏不仅提供了娱乐性,还展示了路径规划算法的实际应用。
🚀 开始挑战AI蛇啦
import pygame
import random
import sys
import math
import heapq
from collections import deque# 初始化pygame
pygame.init()# 游戏设置
WIDTH, HEIGHT = 800, 600
GRID_SIZE = 20
GRID_WIDTH = WIDTH // GRID_SIZE
GRID_HEIGHT = HEIGHT // GRID_SIZE
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Digital Snake Battle - AI Enhanced")# 颜色定义
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
PURPLE = (128, 0, 128)
CYAN = (0, 255, 255)
ORANGE = (255, 165, 0)
DIGITAL_GREEN = (57, 255, 20) # 数码风格绿色
GRID_COLOR = (40, 40, 40) # 网格颜色
PATH_COLOR = (255, 255, 255) # AI路径颜色
DANGER_COLOR = (255, 100, 100) # 危险路径颜色
BOUNDARY_DANGER_COLOR = (255, 150, 0) # 边界危险颜色# 玩家颜色列表
PLAYER_COLORS = [GREEN, BLUE, YELLOW, PURPLE, CYAN, ORANGE]# 字体设置
try:digital_font = pygame.font.Font("digital.ttf", 70)title_font = pygame.font.Font("digital.ttf", 80)menu_font = pygame.font.Font("digital.ttf", 40)
except:digital_font = pygame.font.SysFont('couriernew', 70, bold=True)title_font = pygame.font.SysFont('couriernew', 80, bold=True)menu_font = pygame.font.SysFont('couriernew', 40, bold=True)font = pygame.font.SysFont('couriernew', 25, bold=True)
big_font = pygame.font.SysFont('couriernew', 50, bold=True)class Snake:def __init__(self, x, y, color, player_id, is_ai=False, control_keys=None):self.body = [[x, y]]self.direction = "RIGHT"self.next_direction = "RIGHT"self.color = colorself.score = 0self.grow = Falseself.player_id = player_idself.control_keys = control_keysself.alive = Trueself.is_ai = is_aiself.path = [] # AI路径self.last_move_time = 0self.last_path_update = -10 # 初始值设为负数,确保第一次更新self.path_update_interval = 0.5 # 路径更新间隔(秒)self.boundary_risk_factor = 1.0 # 边界风险因子(提高)self.boundary_safe_distance = 3 # 边界安全距离(增加)self.initial_path_planned = False # 初始路径是否已规划self.boundary_danger_threshold = 3 # 边界危险阈值def change_direction(self, new_direction):"""改变蛇的方向"""if (new_direction == "UP" and self.direction != "DOWN") or \(new_direction == "DOWN" and self.direction != "UP") or \(new_direction == "LEFT" and self.direction != "RIGHT") or \(new_direction == "RIGHT" and self.direction != "LEFT"):self.next_direction = new_directiondef move(self):"""移动蛇并返回是否死亡"""if not self.alive:return Trueself.direction = self.next_directionhead = self.body[0].copy()if self.direction == "UP":head[1] -= 1elif self.direction == "DOWN":head[1] += 1elif self.direction == "LEFT":head[0] -= 1elif self.direction == "RIGHT":head[0] += 1# 边界检查if (head[0] < 0 or head[0] >= GRID_WIDTH or head[1] < 0 or head[1] >= GRID_HEIGHT):self.alive = Falsereturn True# 自身碰撞检查if head in self.body[1:]:self.alive = Falsereturn Trueself.body.insert(0, head)if not self.grow:self.body.pop()else:self.grow = Falseself.score += 1return Falsedef check_collision_with_other(self, other_snakes):"""检查与其他蛇的碰撞"""if not self.alive:return Falsehead = self.body[0]for snake in other_snakes:if snake.player_id != self.player_id and snake.alive:if head in snake.body:self.alive = Falsereturn Truereturn Falsedef ai_move(self, foods, other_snakes, current_time):"""AI决策算法 - 增强版"""# 确保初始路径已规划if not self.initial_path_planned:self.find_path_to_food(foods, other_snakes)self.initial_path_planned = Trueself.last_path_update = current_time# 检查是否接近边界head = self.body[0]near_boundary = (head[0] < self.boundary_danger_threshold or head[0] > GRID_WIDTH - 1 - self.boundary_danger_threshold orhead[1] < self.boundary_danger_threshold or head[1] > GRID_HEIGHT - 1 - self.boundary_danger_threshold)# 定期更新路径或当路径不安全或接近边界时更新if (not self.path or current_time - self.last_path_update > self.path_update_interval ornot self.is_path_safe(other_snakes) ornear_boundary):self.find_path_to_food(foods, other_snakes)self.last_path_update = current_timeif self.path:next_pos = self.path[0]# 根据下一个位置决定方向head = self.body[0]if next_pos[0] > head[0]:self.change_direction("RIGHT")elif next_pos[0] < head[0]:self.change_direction("LEFT")elif next_pos[1] > head[1]:self.change_direction("DOWN")elif next_pos[1] < head[1]:self.change_direction("UP")# 移动到下一个位置后从路径中移除self.path.pop(0)else:# 如果没有路径,安全移动(避免被困)self.safe_move(other_snakes)def is_path_safe(self, other_snakes):"""检查当前路径是否安全"""if not self.path:return False# 检查路径上的每个位置是否安全for i, pos in enumerate(self.path):# 只检查前几个位置(避免过度检查)if i > 3:break# 检查是否会被其他蛇占据for snake in other_snakes:if snake.player_id != self.player_id and snake.alive:# 预测其他蛇可能移动到的位置if pos in snake.body:return False# 检查蛇头可能移动到的位置if snake.body and pos == snake.body[0]:return Falsereturn Truedef safe_move(self, other_snakes):"""安全移动算法 - 避免碰撞"""# 获取所有安全方向safe_directions = []directions = ["UP", "DOWN", "LEFT", "RIGHT"]for dir in directions:test_head = self.body[0].copy()if dir == "UP":test_head[1] -= 1elif dir == "DOWN":test_head[1] += 1elif dir == "LEFT":test_head[0] -= 1elif dir == "RIGHT":test_head[0] += 1if (0 <= test_head[0] < GRID_WIDTH and 0 <= test_head[1] < GRID_HEIGHT and test_head not in self.body andnot self.will_collide(test_head, other_snakes)):safe_directions.append(dir)# 如果有安全方向,选择远离边界的方向if safe_directions:# 优先选择远离边界的方向best_dir = Nonemax_distance = -1for dir in safe_directions:test_head = self.body[0].copy()if dir == "UP":test_head[1] -= 1elif dir == "DOWN":test_head[1] += 1elif dir == "LEFT":test_head[0] -= 1elif dir == "RIGHT":test_head[0] += 1# 计算到边界的距离distance_to_boundary = min(test_head[0], GRID_WIDTH - 1 - test_head[0],test_head[1],GRID_HEIGHT - 1 - test_head[1])if distance_to_boundary > max_distance:max_distance = distance_to_boundarybest_dir = dirif best_dir:self.change_direction(best_dir)else:# 如果没有安全方向,选择风险最小的方向min_risk = float('inf')best_dir = Nonefor dir in directions:test_head = self.body[0].copy()if dir == "UP":test_head[1] -= 1elif dir == "DOWN":test_head[1] += 1elif dir == "LEFT":test_head[0] -= 1elif dir == "RIGHT":test_head[0] += 1if not (0 <= test_head[0] < GRID_WIDTH and 0 <= test_head[1] < GRID_HEIGHT):continuerisk = self.calculate_risk(test_head, other_snakes)if risk < min_risk:min_risk = riskbest_dir = dirif best_dir:self.change_direction(best_dir)def will_collide(self, position, other_snakes):"""检查移动到指定位置是否会碰撞"""# 检查自身碰撞if position in self.body[1:]:return True# 检查其他蛇for snake in other_snakes:if snake.player_id != self.player_id and snake.alive:if position in snake.body:return True# 预测蛇头移动if snake.body and self.predict_head_position(snake) == position:return Truereturn Falsedef predict_head_position(self, snake):"""预测蛇的下一步位置"""head = snake.body[0].copy()if snake.direction == "UP":head[1] -= 1elif snake.direction == "DOWN":head[1] += 1elif snake.direction == "LEFT":head[0] -= 1elif snake.direction == "RIGHT":head[0] += 1return headdef calculate_risk(self, position, other_snakes):"""计算移动到指定位置的风险值"""risk = 0# 边界风险 - 增加权重risk += min(position[0], GRID_WIDTH - 1 - position[0]) * self.boundary_risk_factorrisk += min(position[1], GRID_HEIGHT - 1 - position[1]) * self.boundary_risk_factor# 特别关注靠近边界的位置if position[0] < self.boundary_safe_distance or position[0] > GRID_WIDTH - 1 - self.boundary_safe_distance:risk += 10.0 # 增加边界风险权重if position[1] < self.boundary_safe_distance or position[1] > GRID_HEIGHT - 1 - self.boundary_safe_distance:risk += 10.0# 其他蛇的距离风险for snake in other_snakes:if snake.player_id != self.player_id and snake.alive and snake.body:head = snake.body[0]distance = math.sqrt((position[0] - head[0])**2 + (position[1] - head[1])**2)risk += max(0, 5 - distance) * 2 # 距离越近风险越高# 如果位置在蛇的路径上,增加风险if snake.path and position in snake.path:risk += 3return riskdef find_path_to_food(self, foods, other_snakes):"""使用A*算法寻找路径 - 增强版"""start = tuple(self.body[0])# 找到最近的食物closest_food = Nonemin_distance = float('inf')for food in foods:distance = abs(start[0] - food.position[0]) + abs(start[1] - food.position[1])if distance < min_distance:min_distance = distanceclosest_food = foodif not closest_food:returngoal = tuple(closest_food.position)# 如果食物在蛇身体里,重新生成路径if goal in [tuple(segment) for segment in self.body]:self.path = []return# A*算法实现open_set = []heapq.heappush(open_set, (0, start))came_from = {start: None}g_score = {start: 0}f_score = {start: self.heuristic(start, goal)}while open_set:current = heapq.heappop(open_set)[1]if current == goal:# 重建路径self.path = []while current in came_from and came_from[current] is not None:self.path.append(list(current))current = came_from[current]self.path.reverse()return# 检查四个方向for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:neighbor = (current[0] + dx, current[1] + dy)# 检查边界if not (0 <= neighbor[0] < GRID_WIDTH and 0 <= neighbor[1] < GRID_HEIGHT):continue# 检查是否安全if not self.is_position_safe(neighbor, other_snakes):continue# 对于起始点,检查方向是否合法(不能立即掉头)if current == start:# 计算方向向量direction_vector = (dx, dy)# 检查是否与当前方向相反if self.is_opposite_direction(direction_vector):continue# 计算新的g分数 - 增加边界风险tentative_g_score = g_score[current] + 1# 增加边界位置的代价if (neighbor[0] < self.boundary_safe_distance or neighbor[0] > GRID_WIDTH - 1 - self.boundary_safe_distance orneighbor[1] < self.boundary_safe_distance or neighbor[1] > GRID_HEIGHT - 1 - self.boundary_safe_distance):tentative_g_score += 10 # 增加边界位置的代价if neighbor not in g_score or tentative_g_score < g_score[neighbor]:came_from[neighbor] = currentg_score[neighbor] = tentative_g_scoref_score[neighbor] = tentative_g_score + self.heuristic(neighbor, goal)heapq.heappush(open_set, (f_score[neighbor], neighbor))# 如果没有找到路径self.path = []def is_position_safe(self, position, other_snakes):"""检查位置是否安全"""# 检查自身碰撞 - 排除蛇尾(因为蛇尾会移动)if position in [tuple(segment) for segment in self.body[1:-1]]:return False# 检查其他蛇for snake in other_snakes:if snake.player_id != self.player_id and snake.alive:# 当前位置是否在蛇身上if position in [tuple(segment) for segment in snake.body]:return False# 预测蛇头移动if snake.body and self.predict_head_position(snake) == position:return Falsereturn Truedef is_opposite_direction(self, direction_vector):"""检查方向是否与当前方向相反"""dx, dy = direction_vectorif self.direction == "UP" and dy == 1: # 向下return Trueelif self.direction == "DOWN" and dy == -1: # 向上return Trueelif self.direction == "LEFT" and dx == 1: # 向右return Trueelif self.direction == "RIGHT" and dx == -1: # 向左return Truereturn Falsedef heuristic(self, a, b):"""启发式函数 - 曼哈顿距离"""return abs(a[0] - b[0]) + abs(a[1] - b[1])def draw(self, surface, other_snakes=None):"""绘制蛇和AI路径"""if not self.alive:returnfor i, segment in enumerate(self.body):# 蛇头用深色,蛇身用渐变色if i == 0:color = self.colorelse:r = min(255, self.color[0] + i * 3)g = min(255, self.color[1] + i * 3)b = min(255, self.color[2] + i * 3)color = (r, g, b)pygame.draw.rect(surface, color, [segment[0] * GRID_SIZE, segment[1] * GRID_SIZE, GRID_SIZE, GRID_SIZE])# 绘制玩家ID在蛇头上(居中显示)if i == 0:id_text = font.render(str(self.player_id), True, BLACK)# 计算文本居中位置text_x = segment[0] * GRID_SIZE + (GRID_SIZE - id_text.get_width()) // 2text_y = segment[1] * GRID_SIZE + (GRID_SIZE - id_text.get_height()) // 2surface.blit(id_text, [text_x, text_y])# 绘制AI路径if self.is_ai and self.path:for i, pos in enumerate(self.path):# 根据路径安全性使用不同颜色color = PATH_COLOR# 检查是否靠近边界if (pos[0] < self.boundary_danger_threshold or pos[0] > GRID_WIDTH - 1 - self.boundary_danger_threshold orpos[1] < self.boundary_danger_threshold or pos[1] > GRID_HEIGHT - 1 - self.boundary_danger_threshold):color = BOUNDARY_DANGER_COLOR# 检查是否会被其他蛇占据elif other_snakes and i < 3: # 只检查前几个位置for snake in other_snakes:if snake.body and pos == tuple(snake.body[0]):color = DANGER_COLORbreakpygame.draw.rect(surface, color, [pos[0] * GRID_SIZE + GRID_SIZE//4, pos[1] * GRID_SIZE + GRID_SIZE//4, GRID_SIZE//2, GRID_SIZE//2], 1)class Food:def __init__(self):self.position = [0, 0]self.color = REDself.randomize_position()def randomize_position(self):"""随机生成食物位置"""self.position = [random.randint(0, GRID_WIDTH - 1), random.randint(0, GRID_HEIGHT - 1)]def draw(self, surface):"""绘制食物"""pygame.draw.rect(surface, self.color, [self.position[0] * GRID_SIZE, self.position[1] * GRID_SIZE, GRID_SIZE, GRID_SIZE])class Game:def __init__(self):self.snakes = []self.foods = []self.mode = None # "single" 或 "ai_battle"self.running = Trueself.clock = pygame.time.Clock()self.game_over = Falseself.winner = Noneself.show_menu = Trueself.menu_selection = 0# 缩短菜单选项字符串self.menu_options = ["Single Player", "AI Battle", "Exit"]self.last_key_press_time = 0self.key_delay = 200self.paused = True # 游戏默认暂停状态# 初始化食物for _ in range(3):self.foods.append(Food())def init_single_player(self):"""初始化单人模式"""self.snakes.clear()self.foods.clear()# 添加本地玩家player_snake = self.add_player(1, GRID_WIDTH // 4, GRID_HEIGHT // 2, DIGITAL_GREEN, control_keys={"UP": pygame.K_w, "DOWN": pygame.K_s,"LEFT": pygame.K_a, "RIGHT": pygame.K_d})# 添加2个AI玩家for i in range(2, 4):self.add_ai_player(i)for _ in range(3):self.foods.append(Food())# 立即规划AI路径self.plan_ai_paths()def init_ai_battle(self):"""初始化AI对战模式"""self.snakes.clear()self.foods.clear()# 添加4个AI玩家for i in range(1, 5):self.add_ai_player(i)for _ in range(3):self.foods.append(Food())# 立即规划AI路径self.plan_ai_paths()def add_player(self, player_id, x, y, color, is_ai=False, control_keys=None):"""添加一个玩家"""snake = Snake(x, y, color, player_id, is_ai, control_keys)self.snakes.append(snake)return snakedef add_ai_player(self, player_id):"""添加一个AI玩家"""color = PLAYER_COLORS[(player_id - 1) % len(PLAYER_COLORS)]# 找一个不会立即碰撞的位置 - 确保不靠近边界while True:# 确保AI蛇初始位置不靠近边界x = random.randint(3, GRID_WIDTH - 4)y = random.randint(3, GRID_HEIGHT - 4)overlap = Falsefor snake in self.snakes:if [x, y] in snake.body:overlap = Truebreakif not overlap:breaksnake = self.add_player(player_id, x, y, color, is_ai=True)# 随机初始化方向(避免所有AI都向右移动)directions = ["UP", "DOWN", "LEFT", "RIGHT"]snake.direction = random.choice(directions)snake.next_direction = snake.directionreturn snakedef plan_ai_paths(self):"""立即规划所有AI玩家的路径"""for snake in self.snakes:if snake.is_ai and snake.alive:other_snakes = [s for s in self.snakes if s.player_id != snake.player_id]snake.find_path_to_food(self.foods, other_snakes)def handle_input(self):"""处理用户输入"""current_time = pygame.time.get_ticks()if current_time - self.last_key_press_time < self.key_delay:returnkeys = pygame.key.get_pressed()# 菜单界面控制if self.show_menu:if keys[pygame.K_UP]:self.menu_selection = (self.menu_selection - 1) % len(self.menu_options)self.last_key_press_time = current_timeelif keys[pygame.K_DOWN]:self.menu_selection = (self.menu_selection + 1) % len(self.menu_options)self.last_key_press_time = current_timeelif keys[pygame.K_RETURN] or keys[pygame.K_SPACE]:if self.menu_selection == 0: # 单人模式self.mode = "single"self.show_menu = Falseself.init_single_player()self.paused = True # 进入游戏后默认暂停elif self.menu_selection == 1: # AI对战模式self.mode = "ai_battle"self.show_menu = Falseself.init_ai_battle()self.paused = True # 进入游戏后默认暂停elif self.menu_selection == 2: # 退出游戏self.running = Falseself.last_key_press_time = current_timeelif keys[pygame.K_ESCAPE]:self.running = Falseself.last_key_press_time = current_timereturn# 游戏界面控制if keys[pygame.K_SPACE]:self.paused = not self.pausedself.last_key_press_time = current_timeif keys[pygame.K_ESCAPE]:self.show_menu = Trueself.paused = Falseself.last_key_press_time = current_timereturn# 单人模式控制if self.mode == "single" and not self.paused:local_snake = Nonefor snake in self.snakes:if snake.control_keys is not None:local_snake = snakebreakif local_snake and local_snake.alive:if keys[pygame.K_w]:local_snake.change_direction("UP")elif keys[pygame.K_s]:local_snake.change_direction("DOWN")elif keys[pygame.K_a]:local_snake.change_direction("LEFT")elif keys[pygame.K_d]:local_snake.change_direction("RIGHT")def update(self):"""更新游戏状态"""if self.show_menu or self.game_over or self.paused:returncurrent_time = pygame.time.get_ticks() / 1000.0 # 转换为秒# AI移动for snake in self.snakes:if snake.is_ai and snake.alive:other_snakes = [s for s in self.snakes if s.player_id != snake.player_id]snake.ai_move(self.foods, other_snakes, current_time)# 移动所有蛇for snake in self.snakes:if snake.alive:snake.move()# 检查蛇之间的碰撞for snake in self.snakes:if snake.alive:other_snakes = [s for s in self.snakes if s.player_id != snake.player_id]snake.check_collision_with_other(other_snakes)# 检查食物碰撞for snake in self.snakes:if snake.alive:for food in self.foods:if snake.body[0] == food.position:snake.grow = Truefood.randomize_position()# 确保食物不会出现在蛇身上while any(food.position in s.body for s in self.snakes if s.alive):food.randomize_position()# 检查游戏是否结束(所有蛇都死亡)alive_players = [s for s in self.snakes if s.alive]if not alive_players:self.game_over = Trueself.winner = Noneelif len(alive_players) == 1:# 只剩一个玩家时不立即结束游戏,继续直到该玩家死亡passdef draw(self, surface):"""绘制游戏界面"""surface.fill(BLACK)# 绘制网格(确保在所有其他元素之前绘制)for x in range(0, WIDTH, GRID_SIZE):pygame.draw.line(surface, GRID_COLOR, (x, 0), (x, HEIGHT))for y in range(0, HEIGHT, GRID_SIZE):pygame.draw.line(surface, GRID_COLOR, (0, y), (WIDTH, y))if self.show_menu:self.draw_menu(surface)return# 绘制食物for food in self.foods:food.draw(surface)# 绘制蛇for snake in self.snakes:other_snakes = [s for s in self.snakes if s.player_id != snake.player_id]snake.draw(surface, other_snakes)# 绘制分数self.draw_scores(surface)# 游戏暂停提示if self.paused:# 绘制暂停文字(居中显示)pause_text = big_font.render("PAUSED", True, DIGITAL_GREEN)pause_width = pause_text.get_width()surface.blit(pause_text, [WIDTH//2 - pause_width//2, HEIGHT//2 - 50])continue_text = font.render("Press SPACE to continue", True, DIGITAL_GREEN)continue_width = continue_text.get_width()surface.blit(continue_text, [WIDTH//2 - continue_width//2, HEIGHT//2 + 20])menu_text = font.render("ESC: Main Menu", True, DIGITAL_GREEN)menu_width = menu_text.get_width()surface.blit(menu_text, [WIDTH//2 - menu_width//2, HEIGHT//2 + 50])# 游戏结束画面elif self.game_over:if self.winner:winner_text = big_font.render(f"Player {self.winner.player_id} Wins!", True, DIGITAL_GREEN)winner_width = winner_text.get_width()surface.blit(winner_text, [WIDTH//2 - winner_width//2, HEIGHT//2 - 50])else:draw_text = big_font.render("Game Over!", True, DIGITAL_GREEN)draw_width = draw_text.get_width()surface.blit(draw_text, [WIDTH//2 - draw_width//2, HEIGHT//2 - 50])restart_text = font.render("R: Restart", True, DIGITAL_GREEN)restart_width = restart_text.get_width()surface.blit(restart_text, [WIDTH//2 - restart_width//2, HEIGHT//2 + 20])menu_text = font.render("ESC: Main Menu", True, DIGITAL_GREEN)menu_width = menu_text.get_width()surface.blit(menu_text, [WIDTH//2 - menu_width//2, HEIGHT//2 + 50])def draw_menu(self, surface):"""绘制开始菜单"""# 绘制背景网格for x in range(0, WIDTH, GRID_SIZE * 2):pygame.draw.line(surface, (20, 40, 20), (x, 0), (x, HEIGHT))for y in range(0, HEIGHT, GRID_SIZE * 2):pygame.draw.line(surface, (20, 40, 20), (0, y), (WIDTH, y))# 绘制标题(居中显示)title_text = title_font.render("DIGITAL SNAKE", True, DIGITAL_GREEN)title_shadow = title_font.render("DIGITAL SNAKE", True, (0, 80, 0))title_width = title_text.get_width()surface.blit(title_shadow, [WIDTH//2 - title_width//2 + 3, 100 + 3])surface.blit(title_text, [WIDTH//2 - title_width//2, 100])# 绘制菜单选项for i, option in enumerate(self.menu_options):color = DIGITAL_GREEN if i == self.menu_selection else (0, 180, 0)text = menu_font.render(option, True, color)text_shadow = menu_font.render(option, True, (0, 40, 0))# 计算文本位置(居中)text_width, text_height = text.get_size()x = WIDTH//2 - text_width//2y = 250 + i * 70surface.blit(text_shadow, [x + 2, y + 2])surface.blit(text, [x, y])# 绘制三角形选择指示器if i == self.menu_selection:# 左侧三角形pygame.draw.polygon(surface, DIGITAL_GREEN, [(x - 30, y + text_height//2 - 10),(x - 10, y + text_height//2),(x - 30, y + text_height//2 + 10)])# 右侧三角形pygame.draw.polygon(surface, DIGITAL_GREEN, [(x + text_width + 30, y + text_height//2 - 10),(x + text_width + 10, y + text_height//2),(x + text_width + 30, y + text_height//2 + 10)])# 绘制操作提示hint_text = font.render("UP/DOWN: Select ENTER/SPACE: Confirm", True, (0, 180, 0))hint_width = hint_text.get_width()surface.blit(hint_text, [WIDTH//2 - hint_width//2, HEIGHT - 50])# 绘制动画小蛇for i in range(3):x = 150 + i * 30y = 500segment_color = (0, random.randint(100, 200), 0)pygame.draw.rect(surface, segment_color, [x, y, GRID_SIZE, GRID_SIZE])def draw_scores(self, surface):"""绘制分数"""y_offset = 10for snake in sorted(self.snakes, key=lambda x: x.score, reverse=True):color = snake.color if snake.alive else (0, 80, 0)status = "ALIVE" if snake.alive else "DEAD"score_text = font.render(f"P{snake.player_id}: {snake.score} {status}", True, color)surface.blit(score_text, [10, y_offset])y_offset += 30def reset_game(self):"""重置游戏"""self.snakes.clear()self.foods.clear()self.game_over = Falseself.winner = Noneself.paused = True # 重置后默认暂停for _ in range(3):self.foods.append(Food())if self.mode == "single":self.init_single_player()elif self.mode == "ai_battle":self.init_ai_battle()def main():"""主游戏循环"""game = Game()while game.running:for event in pygame.event.get():if event.type == pygame.QUIT:game.running = Falseelif event.type == pygame.KEYDOWN:if event.key == pygame.K_r and not game.show_menu:game.reset_game()game.handle_input()game.update()game.draw(SCREEN)pygame.display.update()game.clock.tick(10)pygame.quit()sys.exit()if __name__ == "__main__":main()