Upload files to "/"

This commit is contained in:
2026-06-23 07:24:54 +00:00
commit a326108bfb

384
snake.py Normal file
View File

@@ -0,0 +1,384 @@
import random
import pgzrun
# ====================== 全局基础常量 ======================
WIDTH = 600
HEIGHT = 600
GRID = 20
GRID_NUM_X = WIDTH // GRID
GRID_NUM_Y = HEIGHT // GRID
FORBID_RANGE = 10
# 难度配置Lv1=40Lv2=30Lv3=20
LEVEL_CFG = {
1: (8, 40),
2: (4, 30),
3: (2, 20)
}
DEFAULT_LEVEL = 2
# 色块颜色
BG_COLOR = (0, 0, 0)
OBSTACLE_COLOR = (139, 69, 19)
FOOD_FALLBACK_COLOR = (255, 0, 0)
SNAKE_BODY_COLOR = (0, 0, 255)
SNAKE_HEAD_COLOR = (0, 255, 0)
# ====================== 全局状态 ======================
game_state = "menu"
current_level = DEFAULT_LEVEL
move_frame_gap = LEVEL_CFG[current_level][0]
win_length = LEVEL_CFG[current_level][1]
snake = []
dir_x, dir_y = 1, 0
next_dir_x, next_dir_y = 1, 0
frame_count = 0
score = 0
food_eaten_count = 0
food_pos = (0, 0)
food_actor = None
obstacles = []
has_revived = False
bgm_obj = None # 背景音乐对象
bgm_playing = False # BGM播放标记
# 全部音效变量bgm并入sounds
sound_start = None
sound_eat = None
sound_revive = None
sound_gameover = None
sound_win = None
# ====================== 素材加载 ======================
def load_resources():
global food_actor, bgm_obj, sound_start, sound_eat, sound_revive, sound_gameover, sound_win
# 食物图片
try:
food_actor = Actor("food")
except:
food_actor = None
# 所有音效统一从sounds加载
try:
bgm_obj = sounds.bgm
except Exception as e:
print("bgm.wav 加载失败:", e)
bgm_obj = None
try:
sound_start = sounds.start
except:
sound_start = None
try:
sound_eat = sounds.eat
except:
sound_eat = None
try:
sound_revive = sounds.revive
except:
sound_revive = None
try:
sound_gameover = sounds.game_over
except:
sound_gameover = None
try:
sound_win = sounds.win
except:
sound_win = None
# ====================== 统一停止背景音乐函数(修复重叠核心) ======================
def stop_bgm():
global bgm_playing
if bgm_obj is not None:
bgm_obj.stop()
bgm_playing = False
# ====================== 禁区判断 ======================
def is_in_head_forbid_area(pos):
x, y = pos
hx, hy = snake[0]
if dir_x == 1:
if x > hx and (x - hx) <= FORBID_RANGE and abs(y - hy) <= 1:
return True
elif dir_x == -1:
if x < hx and (hx - x) <= FORBID_RANGE and abs(y - hy) <= 1:
return True
elif dir_y == 1:
if y > hy and (y - hy) <= FORBID_RANGE and abs(x - hx) <= 1:
return True
elif dir_y == -1:
if y < hy and (hy - y) <= FORBID_RANGE and abs(x - hx) <= 1:
return True
return False
# ====================== 游戏工具函数 ======================
def reset_game():
global snake, dir_x, dir_y, next_dir_x, next_dir_y
global score, food_eaten_count, obstacles, frame_count, has_revived
center_x = GRID_NUM_X // 2
center_y = GRID_NUM_Y // 2
snake = [(center_x, center_y), (center_x - 1, center_y), (center_x - 2, center_y)]
dir_x, dir_y = 1, 0
next_dir_x, next_dir_y = 1, 0
frame_count = 0
score = 0
food_eaten_count = 0
obstacles.clear()
has_revived = False
spawn_food()
def revive_player():
global snake, dir_x, dir_y, next_dir_x, next_dir_y, frame_count, obstacles, has_revived
if sound_revive:
sound_revive.play()
center_x = GRID_NUM_X // 2
center_y = GRID_NUM_Y // 2
length = len(snake)
snake = []
for i in range(length):
snake.append((center_x - i, center_y))
dir_x, dir_y = 1, 0
next_dir_x, next_dir_y = 1, 0
frame_count = 0
obstacles.clear()
has_revived = True
spawn_food()
def get_empty_positions():
empty = []
snake_set = set(snake)
obs_set = set(obstacles)
for x in range(GRID_NUM_X):
for y in range(GRID_NUM_Y):
pos = (x, y)
if pos not in snake_set and pos not in obs_set and not is_in_head_forbid_area(pos):
empty.append(pos)
return empty
def spawn_food():
global food_pos
empty = get_empty_positions()
food_pos = random.choice(empty)
def spawn_obstacle_group():
empty = get_empty_positions()
if len(empty) < 3:
return
start = random.choice(empty)
sx, sy = start
shapes = [
[(sx, sy), (sx+1, sy), (sx+2, sy)],
[(sx, sy), (sx, sy+1), (sx, sy+2)],
[(sx, sy), (sx+1, sy), (sx, sy+1)]
]
shape = random.choice(shapes)
fixed_shape = []
for (x, y) in shape:
fx = x % GRID_NUM_X
fy = y % GRID_NUM_Y
fixed_shape.append((fx, fy))
snake_set = set(snake)
obs_set = set(obstacles)
food_set = {food_pos}
valid = True
for p in fixed_shape:
if p in snake_set or p in obs_set or p in food_set or is_in_head_forbid_area(p):
valid = False
break
if valid:
obstacles.extend(fixed_shape)
def check_win():
return len(snake) >= win_length
# ====================== 游戏更新 ======================
def update():
global game_state, frame_count, dir_x, dir_y, next_dir_x, next_dir_y
global score, food_eaten_count, snake, obstacles, bgm_playing
# 进入游戏界面且未播放BGM时启动play(-1) = 无限循环
if game_state == "play" and not bgm_playing and bgm_obj is not None:
bgm_obj.play(-1)
bgm_playing = True
print("BGM启动成功")
# 非对局界面跳过逻辑
if game_state == "menu" or game_state in ("death_revive", "gameover", "win"):
return
frame_count += 1
# 禁止反向掉头
if not (next_dir_x == -dir_x and next_dir_y == -dir_y):
dir_x, dir_y = next_dir_x, next_dir_y
if frame_count % move_frame_gap != 0:
return
head_x, head_y = snake[0]
new_x = (head_x + dir_x) % GRID_NUM_X
new_y = (head_y + dir_y) % GRID_NUM_Y
new_head = (new_x, new_y)
# 撞障碍/自身死亡
if new_head in snake or new_head in obstacles:
if sound_gameover:
sound_gameover.play()
if not has_revived:
game_state = "death_revive"
else:
game_state = "gameover"
return
# 吃到食物
if new_head == food_pos:
snake.insert(0, new_head)
score += 1
food_eaten_count += 1
if food_eaten_count % 2 == 0:
spawn_obstacle_group()
spawn_food()
if sound_eat:
sound_eat.play()
if check_win():
game_state = "win"
if sound_win:
sound_win.play()
return
snake.insert(0, new_head)
snake.pop()
# ====================== 按键控制 ======================
def on_key_down(key):
global game_state, current_level, move_frame_gap, win_length
global next_dir_x, next_dir_y
# ESC退出先停止BGM再关闭程序
if key == keys.ESCAPE:
stop_bgm()
exit()
# 难度菜单选关进入游戏前停止旧BGM
if game_state == "menu":
if key == keys.K_1:
current_level = 1
elif key == keys.K_2:
current_level = 2
elif key == keys.K_3:
current_level = 3
else:
return
move_frame_gap, win_length = LEVEL_CFG[current_level]
stop_bgm()
reset_game()
if sound_start:
sound_start.play()
game_state = "play"
return
# 死亡复活界面
if game_state == "death_revive":
if key == keys.Q:
revive_player()
game_state = "play"
elif key == keys.R:
# 按R重开先停止BGM再重置
stop_bgm()
reset_game()
if sound_start:
sound_start.play()
game_state = "play"
return
# 胜利/彻底失败界面按R重开
if game_state in ("gameover", "win"):
if key == keys.R:
# 按R重开先停止BGM再重置
stop_bgm()
reset_game()
if sound_start:
sound_start.play()
game_state = "play"
return
# WASD移动
if game_state == "play":
if key == keys.W:
next_dir_x, next_dir_y = 0, -1
elif key == keys.S:
next_dir_x, next_dir_y = 0, 1
elif key == keys.A:
next_dir_x, next_dir_y = -1, 0
elif key == keys.D:
next_dir_x, next_dir_y = 1, 0
# ====================== 绘制 ======================
def draw():
screen.fill(BG_COLOR)
# 难度菜单文字 40 / 30 / 20
if game_state == "menu":
screen.draw.text("SNAKE GAME", center=(WIDTH//2, 120), fontsize=48, color="white")
screen.draw.text("Press 1 / 2 / 3 to select level", center=(WIDTH//2, 180), fontsize=24, color="gray")
screen.draw.text("Level 1: Slow | Target Length 40", center=(WIDTH//2, 230), fontsize=20, color="green")
screen.draw.text("Level 2: Normal | Target Length 30 (Default)", center=(WIDTH//2, 270), fontsize=20, color="blue")
screen.draw.text("Level 3: Fast | Target Length 20", center=(WIDTH//2, 310), fontsize=20, color="red")
screen.draw.text("WASD Move | ESC Quit", center=(WIDTH//2, 400), fontsize=22, color="lightgray")
return
# 绘制障碍物
for (x, y) in obstacles:
rect = Rect(x * GRID, y * GRID, GRID, GRID)
screen.draw.filled_rect(rect, OBSTACLE_COLOR)
# 绘制食物
fx, fy = food_pos
food_center_x = fx * GRID + GRID / 2
food_center_y = fy * GRID + GRID / 2
if food_actor:
food_actor.pos = (food_center_x, food_center_y)
food_actor.draw()
else:
food_rect = Rect(fx * GRID, fy * GRID, GRID, GRID)
screen.draw.filled_rect(food_rect, FOOD_FALLBACK_COLOR)
# 绘制蛇身体
for idx in range(1, len(snake)):
x, y = snake[idx]
body_rect = Rect(x * GRID, y * GRID, GRID, GRID)
screen.draw.filled_rect(body_rect, SNAKE_BODY_COLOR)
# 蛇头
hx, hy = snake[0]
head_rect = Rect(hx * GRID, hy * GRID, GRID, GRID)
screen.draw.filled_rect(head_rect, SNAKE_HEAD_COLOR)
# 左上角文字信息
screen.draw.text(f"Score: {score}", (10, 10), fontsize=20, color="white")
screen.draw.text(f"Level: {current_level}", (10, 35), fontsize=20, color="white")
screen.draw.text(f"Obstacle Blocks: {len(obstacles)}", (10, 60), fontsize=20, color="lightgray")
# 死亡复活遮罩
if game_state == "death_revive":
screen.draw.filled_rect(Rect(0, 0, WIDTH, HEIGHT), (0, 0, 0, 180))
screen.draw.text("YOU DIED", center=(WIDTH//2, HEIGHT//2 - 60), fontsize=60, color="orange")
screen.draw.text("Press Q to Revive (Keep score & length)", center=(WIDTH//2, HEIGHT//2 - 10), fontsize=22, color="white")
screen.draw.text("Press R to Restart Full Game", center=(WIDTH//2, HEIGHT//2 + 30), fontsize=22, color="white")
screen.draw.text("ESC to Quit", center=(WIDTH//2, HEIGHT//2 + 70), fontsize=20, color="lightgray")
# Game Over遮罩
if game_state == "gameover":
screen.draw.filled_rect(Rect(0, 0, WIDTH, HEIGHT), (0, 0, 0, 180))
screen.draw.text("GAME OVER", center=(WIDTH//2, HEIGHT//2 - 40), fontsize=60, color="red")
screen.draw.text("R Restart | ESC Quit", center=(WIDTH//2, HEIGHT//2 + 30), fontsize=24, color="white")
# 胜利遮罩
if game_state == "win":
screen.draw.filled_rect(Rect(0, 0, WIDTH, HEIGHT), (0, 0, 0, 180))
screen.draw.text("YOU WIN!", center=(WIDTH//2, HEIGHT//2 - 50), fontsize=60, color="green")
screen.draw.text("All space filled completely!", center=(WIDTH//2, HEIGHT//2 + 10), fontsize=22, color="white")
screen.draw.text("R Restart | ESC Quit", center=(WIDTH//2, HEIGHT//2 + 50), fontsize=24, color="white")
# 加载素材
load_resources()
# 启动游戏
pgzrun.go()