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()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通