Python turtle贪吃蛇游戏无法结束、墙壁碰撞检测Bug排查
问题原因
代码存在两个核心问题,对应遇到的两个故障:
- 游戏无法正常终止:
turtle.done()的作用是启动Tkinter事件循环、保持窗口不关闭,并非终止游戏逻辑的方法。碰撞触发后没有停止游戏主循环的定时器,tick函数会一直通过turtle.ontimer反复注册执行,蛇的移动、碰撞检测逻辑会持续运行,自然停不下来。 - 撞墙检测逻辑异常:用
坐标完全等于±280作为撞墙判定条件本身就不可靠。蛇初始长度时所有段的坐标都是20的整数倍,移动步长也是20,刚好能命中±280的精确值;但食物生成的随机坐标是任意整数,和20像素的移动网格不对齐,加上Turtle坐标计算的浮点误差,蛇变长后蛇头坐标要么直接越过±280没触发判定,要么身体段位置偏移误触发碰撞,就会出现要么检测不到撞墙、要么没碰墙就弹出GAME OVER的问题。
修复方法
- 增加游戏运行状态标记,碰撞触发时修改状态,不再注册下一次主循环定时器,从根源停止游戏逻辑
- 把撞墙的精确相等判定改为越界判定,只要蛇头坐标超出边界范围就判定撞墙,不需要精确匹配坐标值
- 把食物生成坐标对齐到20像素的移动网格,避免位置偏移带来的判定误差
修正后的完整代码如下:
import turtle from turtle import Turtle from random import randint class Snake: def __init__(self, grid_size, initial_x_positions): self.segments = [] self.grid_size = grid_size self.create_snake(initial_x_positions) self.head = self.segments[0] def create_snake(self, initial_x_positions): for x in initial_x_positions: self.add_segment(x) def add_segment(self, position): segment = turtle.Turtle("square") segment.color("RoyalBlue") segment.speed('slowest') segment.penup() segment.goto(position) self.segments.append(segment) def extend(self): self.add_segment(self.segments[-1].position()) def move(self): for i in range(len(self.segments) - 1, 0, -1): x_pos = self.segments[i - 1].xcor() y_pos = self.segments[i - 1].ycor() self.segments[i].goto(x_pos, y_pos) self.head.forward(self.grid_size) def up(self): if self.head.heading() != 270: self.head.setheading(90) def down(self): if self.head.heading() != 90: self.head.setheading(270) def left(self): if self.head.heading() != 0: self.head.setheading(180) def right(self): if self.head.heading() != 180: self.head.setheading(0) class Food(Turtle): def __init__(self): super().__init__() self.shape('circle') self.penup() self.color('red3') self.shapesize(stretch_len=0.5, stretch_wid=0.5) self.speed('fastest') self.reset_food_location() def reset_food_location(self): # 坐标对齐20像素网格,和蛇移动步长匹配 random_x_pos = randint(-12, 12) * 20 random_y_pos = randint(-12, 12) * 20 self.goto(random_x_pos, random_y_pos) class ScoreBoard(Turtle): def __init__(self): super().__init__() self.score = 0 self.color('snow') self.penup() self.goto(0, 250) self.hideturtle() self.increase_score() def increase_score(self): self.clear() self.write(f"Score: {self.score}", align='center', font=('Courier', 24, 'bold')) self.score += 15 def game_over(self): self.goto(0, 0) self.write('GAME OVER', align='center', font=('Courier', 24, 'bold')) def create_screen(): screen = turtle.Screen() screen.tracer(0) screen.bgcolor("black") screen.title("Snake Game") screen.setup(width=600, height=600) return screen def control_snake_movement(screen, snake): screen.listen() screen.onkey(key="Up", fun=snake.up) screen.onkey(key="Down", fun=snake.down) screen.onkey(key="Left", fun=snake.left) screen.onkey(key="Right", fun=snake.right) def main(): initial_x_position = [(0, 0), (-20, 0), (-40, 0)] grid_size = 20 frame_delay_ms = 80 # 新增游戏运行状态标记 game_is_running = True screen = create_screen() snake = Snake(grid_size, initial_x_position) control_snake_movement(screen, snake) food = Food() scoreboard = ScoreBoard() def tick(): nonlocal game_is_running snake.move() screen.update() # 食物碰撞检测 if snake.head.distance(food) < 20: food.reset_food_location() snake.extend() scoreboard.increase_score() # 墙壁碰撞检测 改为越界判定,不用精确匹配坐标 if abs(snake.head.xcor()) > 280 or abs(snake.head.ycor()) > 280: scoreboard.game_over() game_is_running = False # 尾巴碰撞检测 for segment in snake.segments[1:]: if snake.head.distance(segment) < 10: scoreboard.game_over() game_is_running = False break # 只有游戏运行中才注册下一次循环 if game_is_running: turtle.ontimer(tick, frame_delay_ms) tick() screen.exitonclick() turtle.done() if __name__ == "__main__": main()
修改后碰撞触发时会直接停止主循环,蛇不会再移动,撞墙判定也不会再出现漏判、误判的问题。
内容的提问来源于stack exchange,提问作者漣侑稀
相关产品推荐
相关产品推荐

