import pygame
import random
import time
import heapq
from collections import defaultdict
from pygame.locals import *

初始化Pygame

pygame.init()
fpsClock = pygame.time.Clock()

游戏常量

WIDTH, HEIGHT = 800, 600
GRID_SIZE = 20
FPS = 12

颜色定义

COLORS = {
"white": (255, 255, 255),
"blue": (0, 255, 255),
"green": (0, 255, 0),
"red": (255, 0, 0),
"black": (0, 0, 0),
"gray": (128, 128, 128),
"yellow": (255, 255, 0)
}

方向控制

DIRECTIONS = {
"W": (0, -1), # 上
"S": (0, 1), # 下
"A": (-1, 0), # 左
"D": (1, 0) # 右
}

class Snake:
def init(self, color, start_pos):
self.body = [start_pos]
self.direction = (1, 0)
self.color = color
self.normal_speed = 5
self.speed = self.normal_speed
self.score = 0
self.boost_type = None
self.boost_items = 0
self.length_cost_timer = 0
self.original_body = []
self.growth_queue = 0

def move(self):
    if not self.body:
        return

    new_head = (
        self.body[0][0] + self.direction[0],
        self.body[0][1] + self.direction[1]
    )

    if (new_head[0] < 0 or new_head[0] >= WIDTH//GRID_SIZE or
        new_head[1] < 0 or new_head[1] >= HEIGHT//GRID_SIZE):
        self.body = []
        return

    self.body.insert(0, new_head)

    if self.growth_queue > 0:
        self.growth_queue -= 1
    else:
        if len(self.body) > 1:
            self.body.pop()

def eat_food(self, food_type):
    if food_type == "normal":
        self.score += 1
        self.growth_queue += 1
    elif food_type == "diamond":
        self.score += 3
        self.growth_queue += 3
    elif food_type == "speed_boost":
        self.score += 1
        self.boost_items += 1

def update_boost(self):
    current_time = time.time()
    if self.boost_type == 'item':
        if current_time > self.boost_time:
            self.speed = self.normal_speed
            self.boost_type = None
    elif self.boost_type == 'length':
        if current_time - self.length_cost_timer > 2.0:
            if len(self.body) > 3:
                self.body.pop()
                self.length_cost_timer = current_time
            else:
                self.cancel_boost()

def activate_boost(self):
    if self.boost_items > 0:
        self.boost_items -= 1
        self.speed = self.normal_speed * 2
        self.boost_time = time.time() + 5
        self.boost_type = 'item'
    elif not self.boost_type and len(self.body) > 3:
        self.original_body = self.body.copy()
        self.speed = self.normal_speed * 2
        self.boost_type = 'length'
        self.length_cost_timer = time.time()

def cancel_boost(self):
    if self.boost_type == 'length':
        self.speed = self.normal_speed
        min_length = min(len(self.original_body), 3)
        self.body = self.body[:min_length]
        self.boost_type = None

def check_collision(self):
    return self.body[0] in self.body[1:] if self.body else False

def draw(self, surface):
    for i, segment in enumerate(self.body):
        alpha = 255 - int(i * (200/len(self.body))) if self.body else 255
        color = (*self.color[:3], alpha) if len(self.color) == 4 else self.color
        pygame.draw.rect(
            surface, color,
            (segment[0]*GRID_SIZE, segment[1]*GRID_SIZE, GRID_SIZE-1, GRID_SIZE-1)
        )

class AISnake(Snake):
def init(self, color, start_pos):
super().init(color, start_pos)
self.body = [start_pos, (start_pos[0]-1, start_pos[1]), (start_pos[0]-2, start_pos[1])]
self.last_turn = time.time()
self.turn_interval = 0.3
self.game = None
self.current_target = None
self.target_lock_time = 0
self.path = []
self.position_history = []
self.obstacle_map = defaultdict(bool)
self.wall_avoidance_aggressiveness = random.uniform(0.5, 1.5)
self.safe_margin = max(3, int(len(self.body)*0.3))

def update_obstacle_map(self):
    self.obstacle_map.clear()
    for snake in self.game.ai_snakes + [self.game.player_snake]:
        for seg in snake.body:
            self.obstacle_map[seg] = True

def get_safe_directions(self):
    current_pos = self.body[0]
    self.safe_margin = max(3, int(len(self.body)*0.3 * self.wall_avoidance_aggressiveness))
    safe_directions = []
    
    if current_pos[0] > self.safe_margin:
        safe_directions.append((-1, 0))
    if current_pos[0] < (WIDTH//GRID_SIZE - self.safe_margin - 1):
        safe_directions.append((1, 0))
    if current_pos[1] > self.safe_margin:
        safe_directions.append((0, -1))
    if current_pos[1] < (HEIGHT//GRID_SIZE - self.safe_margin - 1):
        safe_directions.append((0, 1))
    return safe_directions

def select_target(self, foods):
    attraction = {"normal":1.0, "diamond":1.5, "speed_boost":1.2}
    valid_targets = []
    
    for food in foods:
        path = self.find_path(food)
        if not path:
            continue
            
        danger_zones = sum(
            1 for pos in path 
            if self.obstacle_map[pos] or 
               any(s.body and s.body[0] == pos for s in self.game.ai_snakes if s is not self)
        )
        
        score = (
            attraction[food.type] * 100 -
            len(path) * 2 -
            danger_zones * 50
        )
        valid_targets.append((food, score))
    
    return max(valid_targets, key=lambda x: x[1], default=(None, 0))[0]

def find_path(self, target):
    if not target:
        return []
        
    start = self.body[0]
    end = target.position
    HEURISTIC_WEIGHT = 1.5
    open_set = []
    heapq.heappush(open_set, (0, start))
    came_from = {}
    g_score = defaultdict(lambda: float('inf'))
    g_score[start] = 0
    
    while open_set:
        current = heapq.heappop(open_set)[1]
        if current == end:
            path = []
            while current in came_from:
                path.append(current)
                current = came_from[current]
            path.reverse()
            return self.convert_path_to_directions(path[:5])
        
        for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
            neighbor = (current[0]+dx, current[1]+dy)
            if (0 <= neighbor[0] < WIDTH//GRID_SIZE 
                and 0 <= neighbor[1] < HEIGHT//GRID_SIZE 
                and not self.obstacle_map[neighbor]
                and neighbor[0] > 1 
                and neighbor[0] < (WIDTH//GRID_SIZE - 2)
                and neighbor[1] > 1 
                and neighbor[1] < (HEIGHT//GRID_SIZE - 2)):
                
                tentative_g = g_score[current] + 1
                if (dx, dy) == self.direction:
                    tentative_g -= 0.5
                    
                if tentative_g < g_score[neighbor]:
                    came_from[neighbor] = current
                    g_score[neighbor] = tentative_g
                    h_score = HEURISTIC_WEIGHT * (abs(neighbor[0]-end[0]) + abs(neighbor[1]-end[1]))
                    f_score = tentative_g + h_score
                    heapq.heappush(open_set, (f_score, neighbor))
    
    return self.emergency_escape()

def emergency_escape(self):
    safe_directions = []
    current_head = self.body[0]
    
    for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
        if (dx, dy) == (-self.direction[0], -self.direction[1]):
            continue
        
        new_pos = (current_head[0]+dx, current_head[1]+dy)
        if (0 <= new_pos[0] < WIDTH//GRID_SIZE 
            and 0 <= new_pos[1] < HEIGHT//GRID_SIZE 
            and not self.obstacle_map[new_pos]):
            safe_directions.append((dx, dy))
    
    if safe_directions:
        scored = [(d, self.evaluate_emergency_direction(d)) for d in safe_directions]
        best_dir = max(scored, key=lambda x: x[1])[0]
        return [best_dir]
    
    return [random.choice([d for d in [(1,0), (-1,0), (0,1), (0,-1)] if d != (-self.direction[0], -self.direction[1])])]

def evaluate_emergency_direction(self, direction):
    score = 0
    new_pos = (self.body[0][0]+direction[0], self.body[0][1]+direction[1])
    
    for snake in self.game.ai_snakes + [self.game.player_snake]:
        if snake is self or not snake.body:
            continue
        head = snake.body[0]
        distance = abs(new_pos[0]-head[0]) + abs(new_pos[1]-head[1])
        score += distance * 2
    
    center = (WIDTH//GRID_SIZE//2, HEIGHT//GRID_SIZE//2)
    score -= abs(new_pos[0]-center[0]) + abs(new_pos[1]-center[1])
    return score

def convert_path_to_directions(self, path):
    directions = []
    current = self.body[0]
    for node in path:
        dx = node[0] - current[0]
        dy = node[1] - current[1]
        if dx == 1: directions.append((1,0))
        elif dx == -1: directions.append((-1,0))
        elif dy == 1: directions.append((0,1))
        elif dy == -1: directions.append((0,-1))
        current = node
    return directions

def evaluate_direction(self, direction, foods):
    new_pos = (self.body[0][0]+direction[0], self.body[0][1]+direction[1])
    
    if self.obstacle_map[new_pos]:
        return -float('inf')
    
    # 边界距离惩罚
    border_penalty = 0
    map_width = WIDTH//GRID_SIZE
    map_height = HEIGHT//GRID_SIZE
    left_dist = new_pos[0]
    right_dist = map_width - new_pos[0] - 1
    top_dist = new_pos[1]
    bottom_dist = map_height - new_pos[1] - 1
    
    if min(left_dist, right_dist, top_dist, bottom_dist) < 3:
        border_penalty = 50 * (3 - min(left_dist, right_dist, top_dist, bottom_dist))
    
    danger_score = 0
    for i in range(1, 4):
        future_pos = (
            new_pos[0] + direction[0]*i,
            new_pos[1] + direction[1]*i
        )
        if not (0 <= future_pos[0] < map_width and 0 <= future_pos[1] < map_height):
            danger_score += 50/i
        elif self.obstacle_map[future_pos]:
            danger_score += 30/i
            
    neighbor_penalty = 0
    for snake in self.game.ai_snakes + [self.game.player_snake]:
        if snake is self or not snake.body:
            continue
        dx = abs(snake.body[0][0] - new_pos[0])
        dy = abs(snake.body[0][1] - new_pos[1])
        if dx + dy < 3:
            neighbor_penalty += 50
    
    distance_to_target = 0
    if self.current_target:
        dx = abs(new_pos[0]-self.current_target.position[0])
        dy = abs(new_pos[1]-self.current_target.position[1])
        distance_to_target = dx + dy
        
    return (
        30 if direction in self.get_safe_directions() else 0 +
        50/(distance_to_target + 1) if self.current_target else 0 +
        20 if direction == self.direction else 0 -
        danger_score -
        neighbor_penalty -
        border_penalty
    )

def emergency_wall_avoidance(self):
    head = self.body[0]
    safe_directions = []
    
    for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
        if dx == -self.direction[0] and dy == -self.direction[1]:
            continue
        
        new_x = head[0] + dx
        new_y = head[1] + dy
        if 0 <= new_x < WIDTH//GRID_SIZE and 0 <= new_y < HEIGHT//GRID_SIZE:
            margin = min(new_x, WIDTH//GRID_SIZE - new_x - 1, new_y, HEIGHT//GRID_SIZE - new_y - 1)
            safe_directions.append((dx, dy, margin))
    
    if safe_directions:
        safe_directions.sort(key=lambda x: -x[2])
        return (safe_directions[0][0], safe_directions[0][1])
    return self.direction

def auto_move(self, foods):
    if not self.body or not foods:
        return

    self.update_obstacle_map()
    if time.time() - self.last_turn < self.turn_interval:
        return
    self.last_turn = time.time()

    # 紧急墙规避
    head = self.body[0]
    if min(head[0], WIDTH//GRID_SIZE-head[0]-1, head[1], HEIGHT//GRID_SIZE-head[1]-1) < 2:
        self.direction = self.emergency_wall_avoidance()
        return

    if (not self.path or 
        time.time() - self.target_lock_time > 3 or
        (self.current_target and self.obstacle_map[self.current_target.position])):
        self.current_target = self.select_target(foods)
        self.path = self.find_path(self.current_target)
        self.target_lock_time = time.time()

    if self.path:
        next_step = self.path[0]
        next_pos = (self.body[0][0] + next_step[0], self.body[0][1] + next_step[1])
        if self.obstacle_map[next_pos]:
            self.path = self.find_path(self.current_target)
            return

    if self.path:
        self.direction = self.path.pop(0)
    else:
        possible = [d for d in [(1,0), (-1,0), (0,1), (0,-1)] 
                   if d != (-self.direction[0], -self.direction[1])]
        scored = [(d, self.evaluate_direction(d, foods)) for d in possible]
        best_dir = max(scored, key=lambda x: x[1])[0]
        self.direction = best_dir

    self.position_history = self.position_history[-9:] + [self.body[0]]
    if len(set(self.position_history)) < 5:
        self.current_target = None
        self.path = []

class Food:
def init(self, game):
self.game = game
self.type = random.choices(
["normal", "diamond", "speed_boost"],
weights=[0.7, 0.2, 0.1],
k=1
)[0]
self.set_color()
self.spawn()

def set_color(self):
    self.color = {
        "normal": COLORS["white"],
        "diamond": COLORS["blue"],
        "speed_boost": COLORS["yellow"]
    }[self.type]

@classmethod
def from_snake_body(cls, game, position):
    food = cls(game)
    food.position = position
    food.type = "normal"
    food.color = COLORS["white"]
    return food

def spawn(self):
    safe_zones = []
    for x in range(5, (self.game.width//GRID_SIZE)-5):
        for y in range(5, (self.game.height//GRID_SIZE)-5):
            if not self.is_position_occupied((x, y)):
                safe_zones.append((x, y))
    
    if safe_zones:
        self.position = random.choice(safe_zones)
    else:
        max_attempts = 100
        for _ in range(max_attempts):
            pos = (
                random.randint(0, (self.game.width//GRID_SIZE)-1),
                random.randint(0, (self.game.height//GRID_SIZE)-1)
            )
            if not self.is_position_occupied(pos):
                self.position = pos
                return
        self.position = (
            random.randint(0, (self.game.width//GRID_SIZE)-1),
            random.randint(0, (self.game.height//GRID_SIZE)-1)
        )

def is_position_occupied(self, pos):
    if any(food.position == pos for food in self.game.foods if food is not self):
        return True
    for snake in self.game.ai_snakes + [self.game.player_snake]:
        if pos in snake.body:
            return True
    return False

def draw(self, surface):
    pygame.draw.rect(
        surface, self.color,
        (self.position[0]*GRID_SIZE, self.position[1]*GRID_SIZE, GRID_SIZE-1, GRID_SIZE-1)
    )

class Game:
def init(self):
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
self.width = WIDTH
self.height = HEIGHT
pygame.display.set_caption("Snake Game")
self.clock = pygame.time.Clock()
self.foods = []
self.ai_spawn_interval = 10
self.last_ai_spawn_time = time.time()
self.max_ai_snakes = 5
self.reset_game()
self.debug_mode = False
self.j_pressed = False

def reset_game(self):
    self.running = True
    self.game_over = False
    self.player_snake = Snake(COLORS["red"], (5, 5))
    self.player_snake.body = [(5,5), (4,5), (3,5)]
    self.ai_snakes = [AISnake(COLORS["gray"], (15, 10))]
    for ai in self.ai_snakes:
        ai.game = self
    self.foods = [Food(self) for _ in range(3)]
    self.last_ai_spawn_time = time.time()

def get_valid_spawn_position(self):
    max_attempts = 100
    for _ in range(max_attempts):
        x = random.randint(5, (self.width//GRID_SIZE)-6)
        y = random.randint(5, (self.height//GRID_SIZE)-6)
        if not self.is_position_occupied((x, y)):
            return (x, y)
    return (10, 10)

def is_position_occupied(self, pos):
    if any(food.position == pos for food in self.foods):
        return True
    for snake in [self.player_snake] + self.ai_snakes:
        if pos in snake.body:
            return True
    return False

def spawn_new_ai(self):
    if len(self.ai_snakes) >= self.max_ai_snakes:
        return
    spawn_pos = self.get_valid_spawn_position()
    if spawn_pos:
        new_ai = AISnake(COLORS["gray"], spawn_pos)
        new_ai.game = self
        new_ai.body = [spawn_pos, (spawn_pos[0]-1, spawn_pos[1]), (spawn_pos[0]-2, spawn_pos[1])]
        self.ai_snakes.append(new_ai)

def handle_input(self):
    keys = pygame.key.get_pressed()
    
    if keys[K_j] and self.player_snake.boost_type != 'item':
        if not self.j_pressed:
            self.player_snake.activate_boost()
            self.j_pressed = True
    elif not keys[K_j] and self.j_pressed:
        if self.player_snake.boost_type == 'length':
            self.player_snake.cancel_boost()
        self.j_pressed = False

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            self.running = False
        if event.type == pygame.KEYDOWN:
            current_dir = self.player_snake.direction
            if event.key == pygame.K_w and current_dir != DIRECTIONS["S"]:
                self.player_snake.direction = DIRECTIONS["W"]
            elif event.key == pygame.K_s and current_dir != DIRECTIONS["W"]:
                self.player_snake.direction = DIRECTIONS["S"]
            elif event.key == pygame.K_a and current_dir != DIRECTIONS["D"]:
                self.player_snake.direction = DIRECTIONS["A"]
            elif event.key == pygame.K_d and current_dir != DIRECTIONS["A"]:
                self.player_snake.direction = DIRECTIONS["D"]
            elif event.key == pygame.K_F1:
                self.debug_mode = not self.debug_mode
def check_collisions(self):
    collision_grid = defaultdict(list)
    dead_snakes = []

# 清理死亡AI蛇
    self.ai_snakes = [ai for ai in self.ai_snakes if ai.body]

# 自碰撞检测
    for snake in [self.player_snake] + self.ai_snakes:
        if snake.body and snake.body[0] in snake.body[1:]:
            dead_snakes.append(snake)

# 收集所有碰撞点
    for snake in [self.player_snake] + self.ai_snakes:
        for i, seg in enumerate(snake.body):
            collision_grid[seg].append((snake, i))

# 处理每个碰撞点
    for pos, snakes in collision_grid.items():
    # 边界碰撞检测
        if not (0 <= pos[0] < WIDTH//GRID_SIZE and 0 <= pos[1] < HEIGHT//GRID_SIZE):
            for snake, index in snakes:
                if index == 0:  # 只有头部出界才算碰撞
                    dead_snakes.append(snake)

    # 头对头碰撞
        heads = [(s, i) for s, i in snakes if i == 0]
        if len(heads) > 1:
            sorted_heads = sorted(heads, key=lambda x: len(x[0].body), reverse=True)
            max_length = len(sorted_heads[0][0].body)
            for s, _ in heads:
                if len(s.body) < max_length or (len(s.body) == max_length and s != sorted_heads[0][0]):
                    if s not in dead_snakes:
                        dead_snakes.append(s)

    # 头撞身体(关键修正)
        for snake, index in snakes:
            if index == 0:  # 当前是蛇头
                for other_snake, other_index in snakes:
                    if other_snake != snake and other_index > 0:  # 其他蛇的身体
                        if snake not in dead_snakes:
                            dead_snakes.append(snake)  # 自己死亡

# 处理死亡蛇
    for snake in dead_snakes:
        if snake == self.player_snake:
            self.game_over = True
        elif snake in self.ai_snakes:
        # 生成尸体食物
            for seg in snake.body[-3:]:
                if random.random() < 0.6:
                    self.foods.append(Food.from_snake_body(self, seg))
            self.ai_snakes.remove(snake)
    dead_snakes.clear()

def update(self):
    if self.game_over:
        return

    self.ai_snakes = [ai for ai in self.ai_snakes if ai.body]
    
    current_time = time.time()
    if (current_time - self.last_ai_spawn_time > self.ai_spawn_interval 
        and len(self.ai_snakes) < self.max_ai_snakes):
        self.spawn_new_ai()
        self.last_ai_spawn_time = current_time

    if not self.player_snake.body:
        self.game_over = True
        return

    self.player_snake.update_boost()
    self.player_snake.move()

    for ai in self.ai_snakes:
        ai.auto_move(self.foods)
        ai.move()

    current_foods = self.foods.copy()
    for food in current_foods:
        if self.player_snake.body and self.player_snake.body[0] == food.position:
            self.player_snake.eat_food(food.type)
            self.foods.remove(food)
            self.foods.append(Food(self))

        for ai in self.ai_snakes.copy():
            if ai.body and ai.body[0] == food.position:
                ai.eat_food(food.type)
                if food in self.foods:
                    self.foods.remove(food)
                self.foods.append(Food(self))
                break

    self.check_collisions()

def draw(self):
    self.screen.fill(COLORS["black"])
    
    for food in self.foods:
        food.draw(self.screen)
    
    self.player_snake.draw(self.screen)
    
    for ai in self.ai_snakes:
        ai.draw(self.screen)

    if self.debug_mode:
        self.draw_debug_info()

    font = pygame.font.SysFont("Arial", 36)
    text = font.render(f"Score: {self.player_snake.score}", True, COLORS["white"])
    self.screen.blit(text, (10, 10))

    ai_count_text = font.render(f"AI: {len(self.ai_snakes)}/{self.max_ai_snakes}", True, COLORS["white"])
    self.screen.blit(ai_count_text, (WIDTH-150, 10))

    boost_text = font.render(f"Boost: {self.player_snake.boost_items}", True, COLORS["yellow"])
    self.screen.blit(boost_text, (WIDTH//2 - 50, 10))

    if self.game_over:
        self.show_game_over()

    pygame.display.flip()

def draw_debug_info(self):
    for ai in self.ai_snakes:
        if ai.body:
            head = ai.body[0]
            dir_vec = (ai.direction[0]*GRID_SIZE, ai.direction[1]*GRID_SIZE)
            pygame.draw.line(
                self.screen, (255,0,0),
                (head[0]*GRID_SIZE + GRID_SIZE//2, head[1]*GRID_SIZE + GRID_SIZE//2),
                (head[0]*GRID_SIZE + GRID_SIZE//2 + dir_vec[0]*2, 
                 head[1]*GRID_SIZE + GRID_SIZE//2 + dir_vec[1]*2),
                3
            )
            # 绘制安全区域
            rect = pygame.Rect(
                (head[0]-ai.safe_margin)*GRID_SIZE,
                (head[1]-ai.safe_margin)*GRID_SIZE,
                (2*ai.safe_margin+1)*GRID_SIZE,
                (2*ai.safe_margin+1)*GRID_SIZE
            )
            pygame.draw.rect(self.screen, (255,0,0), rect, 1)

def show_game_over(self):
    font = pygame.font.SysFont("Arial", 72, bold=True)
    text = font.render("GAME OVER", True, COLORS["red"])
    text_rect = text.get_rect(center=(WIDTH//2, HEIGHT//2 - 50))
    self.screen.blit(text, text_rect)
    
    font = pygame.font.SysFont("Arial", 36)
    score_text = font.render(f"Final Score: {self.player_snake.score}", True, COLORS["white"])
    score_rect = score_text.get_rect(center=(WIDTH//2, HEIGHT//2 + 20))
    self.screen.blit(score_text, score_rect)
    
    restart_text = font.render("Press R to restart", True, COLORS["green"])
    restart_rect = restart_text.get_rect(center=(WIDTH//2, HEIGHT//2 + 80))
    self.screen.blit(restart_text, restart_rect)
    
    quit_text = font.render("Press Q to quit", True, COLORS["gray"])
    quit_rect = quit_text.get_rect(center=(WIDTH//2, HEIGHT//2 + 120))
    self.screen.blit(quit_text, quit_rect)

def run(self):
    while self.running:
        self.handle_input()
        self.update()
        self.draw()
        self.clock.tick(FPS)

        while self.game_over:
            self.draw()
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.running = False
                    self.game_over = False
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_r:
                        self.reset_game()
                    if event.key == pygame.K_q:
                        self.running = False
                        self.game_over = False
            self.clock.tick(FPS)

    pygame.quit()

if name == "main":
game = Game()
game.run()