Upload files to "/"

This commit is contained in:
2026-06-16 07:05:11 +00:00
commit 83cde175e4

478
snake.py Normal file
View File

@@ -0,0 +1,478 @@
import pygame
import random
import sys
import os
# -------------------------- 基础配置 --------------------------
WIDTH = 600
HEIGHT = 600
BLOCK_SIZE = 20
# 难度对应速度(数字越大蛇移动越快)
LEVEL_SPEEDS = {
1: 10, # Level1 最慢
2: 15, # Level2 正常速度
3: 22 # Level3 较快速度
}
# 颜色定义
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
GRAY = (180, 180, 180)
OBSTACLE_COLOR = (139, 69, 19) # 马鞍棕障碍物
# 场地总格子数量,用于胜利判定
TOTAL_BLOCKS = (WIDTH // BLOCK_SIZE) * (HEIGHT // BLOCK_SIZE)
# -------------------------- 资源加载(带容错) --------------------------
assets_dir = os.path.join(os.path.dirname(__file__), "assets")
# 四方向蛇头图片
snake_head_up = None
snake_head_down = None
snake_head_left = None
snake_head_right = None
# 蛇身、食物、障碍物图片
snake_body_img = None
food_img = None
obstacle_img = None
# 音效
eat_sound = None
game_over_sound = None
win_sound = None
def load_assets():
"""加载所有素材,加载失败不影响游戏运行"""
global snake_head_up, snake_head_down, snake_head_left, snake_head_right
global snake_body_img, food_img, obstacle_img, eat_sound, game_over_sound, win_sound
# 加载图片资源
try:
# 四方向蛇头
up_path = os.path.join(assets_dir, "snake_head_up.png")
if os.path.exists(up_path):
snake_head_up = pygame.image.load(up_path).convert_alpha()
snake_head_up = pygame.transform.scale(snake_head_up, (BLOCK_SIZE, BLOCK_SIZE))
down_path = os.path.join(assets_dir, "snake_head_down.png")
if os.path.exists(down_path):
snake_head_down = pygame.image.load(down_path).convert_alpha()
snake_head_down = pygame.transform.scale(snake_head_down, (BLOCK_SIZE, BLOCK_SIZE))
left_path = os.path.join(assets_dir, "snake_head_left.png")
if os.path.exists(left_path):
snake_head_left = pygame.image.load(left_path).convert_alpha()
snake_head_left = pygame.transform.scale(snake_head_left, (BLOCK_SIZE, BLOCK_SIZE))
right_path = os.path.join(assets_dir, "snake_head_right.png")
if os.path.exists(right_path):
snake_head_right = pygame.image.load(right_path).convert_alpha()
snake_head_right = pygame.transform.scale(snake_head_right, (BLOCK_SIZE, BLOCK_SIZE))
# 蛇身
body_path = os.path.join(assets_dir, "snake_body.png")
if os.path.exists(body_path):
snake_body_img = pygame.image.load(body_path).convert_alpha()
snake_body_img = pygame.transform.scale(snake_body_img, (BLOCK_SIZE, BLOCK_SIZE))
# 食物
food_path = os.path.join(assets_dir, "food.png")
if os.path.exists(food_path):
food_img = pygame.image.load(food_path).convert_alpha()
food_img = pygame.transform.scale(food_img, (BLOCK_SIZE, BLOCK_SIZE))
# 障碍物图片
obs_path = os.path.join(assets_dir, "obstacle.png")
if os.path.exists(obs_path):
obstacle_img = pygame.image.load(obs_path).convert_alpha()
obstacle_img = pygame.transform.scale(obstacle_img, (BLOCK_SIZE, BLOCK_SIZE))
except:
pass
# 加载音效资源
try:
pygame.mixer.init()
eat_path = os.path.join(assets_dir, "eat.wav")
if os.path.exists(eat_path):
eat_sound = pygame.mixer.Sound(eat_path)
over_path = os.path.join(assets_dir, "game_over.wav")
if os.path.exists(over_path):
game_over_sound = pygame.mixer.Sound(over_path)
win_path = os.path.join(assets_dir, "win.wav")
if os.path.exists(win_path):
win_sound = pygame.mixer.Sound(win_path)
except:
pass
# -------------------------- 游戏核心类 --------------------------
class SnakeGame:
def __init__(self):
pygame.init()
# 关闭系统自带键盘长按延迟关键优化1
pygame.key.set_repeat(0, 0)
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("贪吃蛇 - WASD控制")
self.clock = pygame.time.Clock()
# 多级字体:标题、大标题、普通文本
self.title_font = pygame.font.Font(None, 64)
self.big_font = pygame.font.Font(None, 48)
self.menu_font = pygame.font.Font(None, 32)
self.font = pygame.font.Font(None, 25)
# 加载素材
load_assets()
# 游戏状态menu(菜单选难度) / playing(游戏中) / game_over(结束) / win(胜利)
self.state = 'menu'
# 默认难度为Level2
self.current_level = 2
self.speed = LEVEL_SPEEDS[self.current_level]
# 障碍物列表、吃食物计数每2个生成一组三连障碍
self.obstacles = []
self.eat_count_for_obs = 0
# 初始化游戏数据(菜单状态下不运行逻辑)
self.reset_game()
def reset_game(self):
"""重置游戏状态,保留当前难度,清空障碍物"""
self.snake = [
(WIDTH // 2, HEIGHT // 2),
(WIDTH // 2 - BLOCK_SIZE, HEIGHT // 2),
(WIDTH // 2 - 2 * BLOCK_SIZE, HEIGHT // 2)
]
# 初始方向向右
self.dx = BLOCK_SIZE
self.dy = 0
self.obstacles = []
self.eat_count_for_obs = 0
self.spawn_food()
self.score = 0
def get_all_occupied_pos(self):
"""获取所有被占用的格子:蛇身 + 障碍物"""
occupied = set(self.snake)
occupied.update(set(self.obstacles))
return occupied
def spawn_food(self):
"""随机生成食物,避开蛇身和障碍物;无空位则触发胜利"""
occupied = self.get_all_occupied_pos()
# 所有格子都被占满 → 游戏胜利
if len(occupied) >= TOTAL_BLOCKS:
self.state = "win"
if win_sound:
win_sound.play()
return
# 正常刷新食物
while True:
x = random.randrange(0, WIDTH - BLOCK_SIZE, BLOCK_SIZE)
y = random.randrange(0, HEIGHT - BLOCK_SIZE, BLOCK_SIZE)
pos = (x, y)
if pos not in occupied:
self.food = pos
break
def spawn_triple_obstacle(self):
"""生成一组3块相连障碍物随机形状横条/竖条/L型"""
occupied = self.get_all_occupied_pos()
occupied.add(self.food)
grid_w = WIDTH // BLOCK_SIZE
grid_h = HEIGHT // BLOCK_SIZE
# 三种三连障碍物形状偏移量
shapes = [
[(0,0), (BLOCK_SIZE,0), (BLOCK_SIZE*2,0)], # 横向直线
[(0,0), (0,BLOCK_SIZE), (0,BLOCK_SIZE*2)], # 纵向直线
[(0,0), (BLOCK_SIZE,0), (0,BLOCK_SIZE)] # L型拐角
]
shape = random.choice(shapes)
# 循环找合法生成点
while True:
# 随机基础坐标(保证不贴边界)
base_x = random.randrange(BLOCK_SIZE, WIDTH - BLOCK_SIZE*2, BLOCK_SIZE)
base_y = random.randrange(BLOCK_SIZE, HEIGHT - BLOCK_SIZE*2, BLOCK_SIZE)
block_group = []
conflict = False
for dx, dy in shape:
px = base_x + dx
py = base_y + dy
pos = (px, py)
if pos in occupied:
conflict = True
break
block_group.append(pos)
if not conflict:
# 无冲突,全部加入障碍物列表
self.obstacles.extend(block_group)
break
def check_wall_collision(self, head_x, head_y):
"""穿墙逻辑:边界穿越"""
if head_x < 0:
head_x = WIDTH - BLOCK_SIZE
elif head_x >= WIDTH:
head_x = 0
if head_y < 0:
head_y = HEIGHT - BLOCK_SIZE
elif head_y >= HEIGHT:
head_y = 0
return head_x, head_y
def check_collision(self, head):
"""碰撞检测:自身、障碍物"""
# 撞到自己
if head in self.snake[1:]:
return True
# 撞到障碍物
if head in self.obstacles:
return True
return False
def handle_input(self):
"""修复无延迟输入:分事件+实时按键采样"""
event_list = pygame.event.get()
keys = pygame.key.get_pressed() # 实时读取所有按住的按键(消除长按延迟)
# 全局退出事件
for event in event_list:
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 一次性按键触发(菜单/重试用)
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
# 菜单选难度
if self.state == 'menu':
if event.key == pygame.K_1:
self.current_level = 1
self.speed = LEVEL_SPEEDS[1]
self.reset_game()
self.state = 'playing'
elif event.key == pygame.K_2:
self.current_level = 2
self.speed = LEVEL_SPEEDS[2]
self.reset_game()
self.state = 'playing'
elif event.key == pygame.K_3:
self.current_level = 3
self.speed = LEVEL_SPEEDS[3]
self.reset_game()
self.state = 'playing'
# 结束/胜利界面重试
elif self.state in ["game_over", "win"]:
if event.key == pygame.K_r:
self.reset_game()
self.state = 'playing'
# 游戏中实时WASD控制核心修复实时采样无延迟
if self.state == "playing":
# W上禁止反向向下
if keys[pygame.K_w] and self.dy != BLOCK_SIZE:
self.dy = -BLOCK_SIZE
self.dx = 0
# S下禁止反向向上
elif keys[pygame.K_s] and self.dy != -BLOCK_SIZE:
self.dy = BLOCK_SIZE
self.dx = 0
# A左禁止反向向右
elif keys[pygame.K_a] and self.dx != BLOCK_SIZE:
self.dx = -BLOCK_SIZE
self.dy = 0
# D右禁止反向向左
elif keys[pygame.K_d] and self.dx != -BLOCK_SIZE:
self.dx = BLOCK_SIZE
self.dy = 0
def update(self):
"""更新游戏逻辑,仅游戏中运行"""
if self.state != 'playing':
return
head_x, head_y = self.snake[0]
new_head = (head_x + self.dx, head_y + self.dy)
new_head = self.check_wall_collision(*new_head)
# 撞到自身/障碍物 → Game Over
if self.check_collision(new_head):
self.state = 'game_over'
if game_over_sound:
game_over_sound.play()
return
self.snake.insert(0, new_head)
# 吃到食物逻辑
if new_head == self.food:
self.score += 1
self.eat_count_for_obs += 1
if eat_sound:
eat_sound.play()
# 每吃2个食物生成一组3连块障碍物
if self.eat_count_for_obs >= 2:
self.spawn_triple_obstacle()
self.eat_count_for_obs = 0
self.spawn_food()
# 没吃到食物:删除尾部,保持长度
else:
self.snake.pop()
def draw_snake_head(self, pos):
"""根据当前方向绘制对应蛇头"""
x, y = pos
# 向右
if self.dx == BLOCK_SIZE:
if snake_head_right:
self.screen.blit(snake_head_right, (x, y))
else:
pygame.draw.rect(self.screen, GREEN, (x, y, BLOCK_SIZE, BLOCK_SIZE))
# 向左
elif self.dx == -BLOCK_SIZE:
if snake_head_left:
self.screen.blit(snake_head_left, (x, y))
else:
pygame.draw.rect(self.screen, GREEN, (x, y, BLOCK_SIZE, BLOCK_SIZE))
# 向上
elif self.dy == -BLOCK_SIZE:
if snake_head_up:
self.screen.blit(snake_head_up, (x, y))
else:
pygame.draw.rect(self.screen, GREEN, (x, y, BLOCK_SIZE, BLOCK_SIZE))
# 向下
elif self.dy == BLOCK_SIZE:
if snake_head_down:
self.screen.blit(snake_head_down, (x, y))
else:
pygame.draw.rect(self.screen, GREEN, (x, y, BLOCK_SIZE, BLOCK_SIZE))
def draw_menu(self):
"""绘制难度选择菜单"""
# 标题
title_text = self.title_font.render("SNAKE GAME", True, GREEN)
self.screen.blit(title_text, (WIDTH//2 - title_text.get_width()//2, 120))
# 副标题
sub_text = self.menu_font.render("Select Difficulty", True, WHITE)
self.screen.blit(sub_text, (WIDTH//2 - sub_text.get_width()//2, 200))
# 三个难度选项
level1_text = self.menu_font.render("Press 1 : Level 1 (Slow)", True, GRAY)
self.screen.blit(level1_text, (WIDTH//2 - level1_text.get_width()//2, 280))
level2_text = self.menu_font.render("Press 2 : Level 2 (Normal)", True, WHITE)
self.screen.blit(level2_text, (WIDTH//2 - level2_text.get_width()//2, 320))
level3_text = self.menu_font.render("Press 3 : Level 3 (Fast)", True, GRAY)
self.screen.blit(level3_text, (WIDTH//2 - level3_text.get_width()//2, 360))
# 底部规则提示
tip_text = self.font.render("WASD move | Every 2 food spawn 3 connected obstacles | ESC exit", True, GRAY)
self.screen.blit(tip_text, (WIDTH//2 - tip_text.get_width()//2, 480))
def draw(self):
"""绘制全部画面,分状态渲染"""
self.screen.fill(BLACK)
# 菜单界面
if self.state == 'menu':
self.draw_menu()
# 游戏界面(游戏中+结束+胜利都基于游戏画面)
else:
# 绘制障碍物
for obs_pos in self.obstacles:
if obstacle_img:
self.screen.blit(obstacle_img, obs_pos)
else:
pygame.draw.rect(self.screen, OBSTACLE_COLOR, (obs_pos[0], obs_pos[1], BLOCK_SIZE, BLOCK_SIZE))
# 绘制食物
if food_img:
self.screen.blit(food_img, self.food)
else:
pygame.draw.rect(self.screen, RED, (self.food[0], self.food[1], BLOCK_SIZE, BLOCK_SIZE))
# 绘制蛇身和蛇头
for i, block in enumerate(self.snake):
if i == 0:
self.draw_snake_head(block)
else:
if snake_body_img:
self.screen.blit(snake_body_img, block)
else:
pygame.draw.rect(self.screen, BLUE, (block[0], block[1], BLOCK_SIZE, BLOCK_SIZE))
# 左上角信息
score_text = self.font.render(f"Score: {self.score}", True, WHITE)
self.screen.blit(score_text, (10, 10))
level_text = self.font.render(f"Level: {self.current_level}", True, GRAY)
self.screen.blit(level_text, (10, 35))
obs_count_text = self.font.render(f"Obstacle Blocks: {len(self.obstacles)}", True, GRAY)
self.screen.blit(obs_count_text, (10, 60))
# 游戏结束遮罩界面
if self.state == 'game_over':
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180))
self.screen.blit(overlay, (0, 0))
over_title = self.big_font.render("GAME OVER", True, RED)
self.screen.blit(over_title, (WIDTH//2 - over_title.get_width()//2, HEIGHT//2 - 70))
retry_text = self.menu_font.render("R - Retry", True, WHITE)
self.screen.blit(retry_text, (WIDTH//2 - retry_text.get_width()//2, HEIGHT//2 + 10))
close_text = self.menu_font.render("ESC - Close", True, WHITE)
self.screen.blit(close_text, (WIDTH//2 - close_text.get_width()//2, HEIGHT//2 + 50))
# 游戏胜利遮罩界面
elif self.state == 'win':
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180))
self.screen.blit(overlay, (0, 0))
win_title = self.big_font.render("YOU WIN!", True, GREEN)
self.screen.blit(win_title, (WIDTH//2 - win_title.get_width()//2, HEIGHT//2 - 70))
win_tip = self.menu_font.render("All space filled completely!", True, WHITE)
self.screen.blit(win_tip, (WIDTH//2 - win_tip.get_width()//2, HEIGHT//2 - 20))
retry_text = self.menu_font.render("R - Retry", True, WHITE)
self.screen.blit(retry_text, (WIDTH//2 - retry_text.get_width()//2, HEIGHT//2 + 20))
close_text = self.menu_font.render("ESC - Close", True, WHITE)
self.screen.blit(close_text, (WIDTH//2 - close_text.get_width()//2, HEIGHT//2 + 60))
pygame.display.flip()
def run(self):
"""游戏主循环"""
while True:
self.handle_input()
self.update()
self.draw()
self.clock.tick(self.speed)
# -------------------------- 启动游戏 --------------------------
if __name__ == "__main__":
game = SnakeGame()
game.run()