贪吃蛇 · 穿墙版 运行: python snake.py 依赖: pip install pygame 操作: 方向键/WASD 移动 | 空格 暂停 | R 重开 | ESC 退出 import os import sys import random import math import pygame # 配置 CELL 28 COLS 22 ROWS 22 TOP_BAR 70 PAD 20 BOARD_W COLS * CELL BOARD_H ROWS * CELL W BOARD_W PAD * 2 H TOP_BAR BOARD_H PAD # 高帧率 快速移动 FPS 120 MOVE_INTERVAL 5 # 方向 UP (0, -1) DOWN (0, 1) LEFT (-1, 0) RIGHT (1, 0) # 颜色 COL_TEXT (220, 240, 255) COL_SNAKE_HEAD (110, 240, 160) COL_SNAKE_BODY (60, 200, 120) COL_SNAKE_DARK (30, 140, 80) # 食物类型: (名字, 颜色, 分数, 生长) FOOD_TYPES [ (apple, (255, 90, 120), 10, 1), (cherry, (255, 60, 90), 20, 1), (gold, (255, 210, 80), 50, 1), ] # 传送门颜色 PORTAL_COLORS { left: (255, 180, 80), right: (80, 200, 255), top: (180, 120, 255), bottom: (120, 255, 180), } # 字体 def find_font(candidates): for p in candidates: if os.path.exists(p): return p return None def get_cn_font(size): path find_font([ C:/Windows/Fonts/msyh.ttc, C:/Windows/Fonts/simhei.ttf, C:/Windows/Fonts/simsun.ttc, /System/Library/Fonts/PingFang.ttc, /usr/share/fonts/truetype/wqy/wqy-microhei.ttc, /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc, ]) if path: return pygame.font.Font(path, size) return pygame.font.Font(None, size) # 粒子 class Particle: __slots__ (x, y, vx, vy, life, color, size, gravity) def __init__(self, x, y, color, speed4, gravity0.1): angle random.random() * math.pi * 2 spd random.random() * speed 1 self.x x self.y y self.vx math.cos(angle) * spd self.vy math.sin(angle) * spd self.life 1.0 self.color color self.size random.random() * 3 2 self.gravity gravity def update(self): self.x self.vx self.y self.vy self.vy self.gravity self.vx * 0.94 self.life - 0.04 def draw(self, surf): if self.life 0: return alpha int(self.life * 255) r max(1, int(self.size * self.life)) s pygame.Surface((r * 2, r * 2), pygame.SRCALPHA) pygame.draw.circle(s, (*self.color, alpha), (r, r), r) surf.blit(s, (self.x - r, self.y - r)) # 飘字 class FloatText: __slots__ (text, x, y, vy, life, color, font) def __init__(self, text, x, y, color, font): self.text text self.x x self.y y self.vy -1.5 self.life 1.0 self.color color self.font font def update(self): self.y self.vy self.vy * 0.95 self.life - 0.03 def draw(self, surf): if self.life 0: return alpha int(self.life * 255) s self.font.render(self.text, True, self.color) s.set_alpha(alpha) rect s.get_rect(center(self.x, self.y)) surf.blit(s, rect) # 传送门 class Portal: __slots__ (x, y, life, color, is_entry) def __init__(self, x, y, color, is_entry): self.x x self.y y self.life 1.0 self.color color self.is_entry is_entry def update(self): self.life - 0.055 def draw(self, surf): if self.life 0: return # 扩散圆环 progress 1.0 - self.life alpha int(self.life * 200) r int(CELL * 0.3 progress * CELL * 0.8) if r 2: return s pygame.Surface((r * 2, r * 2), pygame.SRCALPHA) # 外环 pygame.draw.circle(s, (*self.color, alpha), (r, r), r, 3) # 内环 if r 8: pygame.draw.circle(s, (255, 255, 255, alpha // 2), (r, r), r - 6, 2) surf.blit(s, (self.x - r, self.y - r)) # 游戏 class SnakeGame: def __init__(self): pygame.init() self.screen pygame.display.set_mode((W, H)) pygame.display.set_caption(贪吃蛇 · 穿墙版) self.clock pygame.time.Clock() self.font_sm get_cn_font(14) self.font_md get_cn_font(20) self.font_lg get_cn_font(32) self.font_xl get_cn_font(48) self._build_static_bg() self.reset() def _build_static_bg(self): 预渲染棋盘背景 surf pygame.Surface((W, H)) # 背景渐变 for y in range(H): ratio y / H r int(12 6 * ratio) g int(16 8 * ratio) b int(30 10 * ratio) pygame.draw.line(surf, (r, g, b), (0, y), (W, y)) # 棋盘底 发光边框强调穿墙 board_rect pygame.Rect(PAD - 2, TOP_BAR - 2, BOARD_W 4, BOARD_H 4) pygame.draw.rect(surf, (22, 28, 48), board_rect, border_radius14) pygame.draw.rect(surf, (80, 160, 255), board_rect, 3, border_radius14) pygame.draw.rect(surf, (150, 200, 255), board_rect.inflate(-6, -6), 1, border_radius12) # 格子 for r in range(ROWS): for c in range(COLS): gx c * CELL PAD gy r * CELL TOP_BAR color (26, 32, 52) if (c r) % 2 0 else (30, 36, 58) pygame.draw.rect(surf, color, (gx, gy, CELL, CELL)) self._static_bg surf # ---------- 初始化 ---------- def reset(self): cx, cy COLS // 2, ROWS // 2 self.snake [(cx, cy), (cx - 1, cy), (cx - 2, cy)] self.direction RIGHT self.next_direction RIGHT self.food None self.food_type 0 self.score 0 self.best_score getattr(self, best_score, 0) self.game_over False self.paused False self.move_timer 0 self.particles [] self.floats [] self.portals [] self.grow_pending 0 self.frame 0 self.speed_level 1 self.wrap_count 0 self.spawn_food() def spawn_food(self): 在空白格随机生成食物 empty [] for r in range(ROWS): for c in range(COLS): if (c, r) not in self.snake: empty.append((c, r)) if not empty: return self.food random.choice(empty) r random.random() if r 0.08: self.food_type 2 elif r 0.30: self.food_type 1 else: self.food_type 0 # ---------- 输入 ---------- def change_direction(self, new_dir): 立即改变方向更灵敏 # 不能 180° 掉头 if (new_dir[0] self.direction[0], new_dir[1] self.direction[1]) (0, 0): return self.direction new_dir self.next_direction new_dir # ---------- 更新 ---------- def update(self): self.frame 1 # 特效更新无论什么状态都跑 self._update_effects() if self.game_over or self.paused: return self.move_timer 1 interval max(2, MOVE_INTERVAL - (self.speed_level - 1) // 2) if self.move_timer interval: return self.move_timer 0 self.direction self.next_direction hx, hy self.snake[0] nx hx self.direction[0] ny hy self.direction[1] # ⚡ 穿墙逻辑 wrapped False wrap_side None if nx 0: nx COLS - 1 wrapped True wrap_side left elif nx COLS: nx 0 wrapped True wrap_side right elif ny 0: ny ROWS - 1 wrapped True wrap_side top elif ny ROWS: ny 0 wrapped True wrap_side bottom if wrapped: self.wrap_count 1 self._spawn_portal(hx, hy, nx, ny, wrap_side) # 撞自己 if (nx, ny) in self.snake[:-1]: self.die() return self.snake.insert(0, (nx, ny)) # 吃食物 if (nx, ny) self.food: name, color, pts, grow FOOD_TYPES[self.food_type] self.score pts self.grow_pending grow # 粒子 fx nx * CELL CELL // 2 PAD fy ny * CELL CELL // 2 TOP_BAR for _ in range(14): self.particles.append(Particle(fx, fy, color, speed5)) # 飘字 self.floats.append(FloatText( f{pts}, fx, fy - 20, color, self.font_md )) # 速度提升 if self.score // 50 self.speed_level - 1: self.speed_level 1 self.floats.append(FloatText( 加速!, W // 2, TOP_BAR 30, (255, 220, 100), self.font_md )) self.spawn_food() else: if self.grow_pending 0: self.grow_pending - 1 else: self.snake.pop() def _spawn_portal(self, from_x, from_y, to_x, to_y, side): 穿墙时在出入口各生成传送门特效 fx from_x * CELL CELL // 2 PAD fy from_y * CELL CELL // 2 TOP_BAR tx to_x * CELL CELL // 2 PAD ty to_y * CELL CELL // 2 TOP_BAR color PORTAL_COLORS.get(side, (255, 255, 255)) self.portals.append(Portal(fx, fy, color, True)) self.portals.append(Portal(tx, ty, color, False)) # 粒子爆发 for _ in range(10): self.particles.append(Particle(fx, fy, color, speed5, gravity0)) self.particles.append(Particle(tx, ty, color, speed5, gravity0)) def _update_effects(self): for p in self.particles[:]: p.update() if p.life 0: self.particles.remove(p) for f in self.floats[:]: f.update() if f.life 0: self.floats.remove(f) for po in self.portals[:]: po.update() if po.life 0: self.portals.remove(po) def die(self): self.game_over True if self.score self.best_score: self.best_score self.score for (sx, sy) in self.snake: px sx * CELL CELL // 2 PAD py sy * CELL CELL // 2 TOP_BAR for _ in range(4): self.particles.append(Particle( px, py, random.choice([COL_SNAKE_HEAD, COL_SNAKE_BODY, COL_SNAKE_DARK]), speed5 )) # ---------- 绘制 ---------- def draw(self): self.screen.blit(self._static_bg, (0, 0)) # 传送门 for po in self.portals: po.draw(self.screen) # 食物 if self.food and not self.game_over: self._draw_food() # 蛇 self._draw_snake() # 粒子 for p in self.particles: p.draw(self.screen) # 飘字 for f in self.floats: f.draw(self.screen) # UI self._draw_ui() if self.paused and not self.game_over: self._draw_center_text(已暂停, 按空格继续) if self.game_over: self._draw_center_text(游戏结束, f最终得分: {self.score}) def _draw_food(self): name, color, pts, grow FOOD_TYPES[self.food_type] fx self.food[0] * CELL CELL // 2 PAD fy self.food[1] * CELL CELL // 2 TOP_BAR pulse 1.0 0.12 * math.sin(self.frame * 0.15) r int(CELL * 0.35 * pulse) # 光晕 glow_r r 10 glow pygame.Surface((glow_r * 2, glow_r * 2), pygame.SRCALPHA) pygame.draw.circle(glow, (*color, 70), (glow_r, glow_r), glow_r) self.screen.blit(glow, (fx - glow_r, fy - glow_r)) # 主体 pygame.draw.circle(self.screen, color, (int(fx), int(fy)), r) # 高光 pygame.draw.circle(self.screen, (255, 255, 255), (int(fx - r * 0.3), int(fy - r * 0.3)), max(1, r // 3)) # 叶子 pygame.draw.circle(self.screen, (120, 220, 120), (int(fx), int(fy - r - 2)), 4) # 金星环绕 if self.food_type 2: for i in range(4): ang self.frame * 0.08 i * math.pi / 2 star_x fx math.cos(ang) * (r 8) star_y fy math.sin(ang) * (r 8) pygame.draw.circle(self.screen, (255, 240, 150), (int(star_x), int(star_y)), 3) def _draw_snake(self): n len(self.snake) for i, (sx, sy) in enumerate(self.snake): px sx * CELL PAD py sy * CELL TOP_BAR if i 0: # 蛇头光晕 glow pygame.Surface((CELL 12, CELL 12), pygame.SRCALPHA) pygame.draw.circle(glow, (110, 240, 160, 70), (CELL // 2 6, CELL // 2 6), CELL // 2 6) self.screen.blit(glow, (px - 6, py - 6)) # 头部 rect pygame.Rect(px 1, py 1, CELL - 2, CELL - 2) pygame.draw.rect(self.screen, COL_SNAKE_HEAD, rect, border_radius8) pygame.draw.rect(self.screen, (200, 255, 220), rect, 2, border_radius8) # 眼睛 eye_size 3 cx px CELL // 2 cy py CELL // 2 dx, dy self.direction if dx ! 0: ex1 cx dx * 4 ey1 cy - 5 ey2 cy 5 pygame.draw.circle(self.screen, (20, 20, 30), (ex1, ey1), eye_size) pygame.draw.circle(self.screen, (20, 20, 30), (ex1, ey2), eye_size) else: ex1 cx - 5 ex2 cx 5 ey cy dy * 4 pygame.draw.circle(self.screen, (20, 20, 30), (ex1, ey), eye_size) pygame.draw.circle(self.screen, (20, 20, 30), (ex2, ey), eye_size) else: # 身体渐变 ratio i / n r int(60 30 * (1 - ratio)) g int(200 - 40 * ratio) b int(120 - 30 * ratio) rect pygame.Rect(px 2, py 2, CELL - 4, CELL - 4) pygame.draw.rect(self.screen, (r, g, b), rect, border_radius6) # 高光 hl pygame.Surface((CELL - 8, 4), pygame.SRCALPHA) hl.fill((255, 255, 255, 60)) self.screen.blit(hl, (px 4, py 4)) def _draw_ui(self): # 标题 title self.font_lg.render(贪吃蛇 · 穿墙, True, (110, 240, 160)) self.screen.blit(title, (PAD, 12)) # 分数 score_str f分数: {self.score} s self.font_md.render(score_str, True, COL_TEXT) self.screen.blit(s, (W - s.get_width() - PAD, 12)) # 最高分 best_str f最高: {self.best_score} bs self.font_sm.render(best_str, True, (180, 200, 230)) self.screen.blit(bs, (W - bs.get_width() - PAD, 42)) # 长度 len_str f长度: {len(self.snake)} ls self.font_sm.render(len_str, True, (180, 200, 230)) self.screen.blit(ls, (PAD 150, 22)) # 速度 spd_str f速度: {self.speed_level} ss self.font_sm.render(spd_str, True, (255, 200, 100)) self.screen.blit(ss, (PAD 150, 44)) # 穿墙次数 wrap_str f穿墙: {self.wrap_count} ws self.font_sm.render(wrap_str, True, (150, 220, 255)) self.screen.blit(ws, (PAD 270, 22)) # FPS fps_str fFPS: {int(self.clock.get_fps())} fs self.font_sm.render(fps_str, True, (120, 140, 180)) self.screen.blit(fs, (PAD 270, 44)) def _draw_center_text(self, title_text, sub_text): overlay pygame.Surface((W, H), pygame.SRCALPHA) overlay.fill((0, 0, 0, 160)) self.screen.blit(overlay, (0, 0)) title self.font_xl.render(title_text, True, (255, 100, 130)) self.screen.blit(title, (W // 2 - title.get_width() // 2, H // 2 - 80)) sub self.font_md.render(sub_text, True, (200, 220, 255)) self.screen.blit(sub, (W // 2 - sub.get_width() // 2, H // 2)) hint self.font_sm.render(按 R 重新开始 | ESC 退出, True, (150, 170, 200)) self.screen.blit(hint, (W // 2 - hint.get_width() // 2, H // 2 60)) # ---------- 主循环 ---------- def run(self): running True while running: for e in pygame.event.get(): if e.type pygame.QUIT: running False elif e.type pygame.KEYDOWN: if e.key pygame.K_ESCAPE: running False elif e.key pygame.K_r: self.reset() elif e.key pygame.K_SPACE: if not self.game_over: self.paused not self.paused elif e.key in (pygame.K_UP, pygame.K_w): self.change_direction(UP) elif e.key in (pygame.K_DOWN, pygame.K_s): self.change_direction(DOWN) elif e.key in (pygame.K_LEFT, pygame.K_a): self.change_direction(LEFT) elif e.key in (pygame.K_RIGHT, pygame.K_d): self.change_direction(RIGHT) self.update() self.draw() pygame.display.flip() self.clock.tick(FPS) pygame.quit() sys.exit() if __name__ __main__: print( * 50) print( 贪吃蛇 · 穿墙版) print( * 50) print( 方向键 / WASD 控制移动可穿墙) print( 空格 暂停 | R 重开 | ESC 退出) print( * 50) SnakeGame().run()