I am working on the famous snake game in Python and I've stumbled upon a glitch where the snake starts doing when it eats food.
The snake should keep constant speed throughout the game and the segments should be of equal distance from each other. But what happens is every time the snake eats food it increases its speed and the segments start moving away from each other.
Here is part of the code if anybody can help.
import random
import time
from turtle import Screen, Turtle
MOVE_DISTANCE = 10
POSITIONS = [(0, 0), (0, -20), (0, -40)]
class Snake:
def __init__(self):
self.segments = []
self.create_snake()
self.head = self.segments[0]
def create_snake(self):
for position in POSITIONS:
self.add_segment(position)
def add_segment(self, position):
new_segment = Turtle("square")
new_segment.color("white")
new_segment.penup()
new_segment.goto(position)
self.segments.append(new_segment)
def move(self):
for seg_num in range(len(self.segments) - 1, 0, -1):
self.head.forward(MOVE_DISTANCE) # Moving distance of head
new_x = self.segments[seg_num - 1].xcor()
new_y = self.segments[seg_num - 1].ycor()
self.segments[seg_num].goto(new_x, new_y)
def grow(self):
self.add_segment(self.segments[-1].position())
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.shapesize(stretch_len=0.5, stretch_wid=0.5)
self.color("red")
self.speed("fastest")
self.refresh_food()
def refresh_food(self):
rand_x_cor = random.randint(-480, 480)
rand_y_cor = random.randint(-260, 260)
self.goto(rand_x_cor, rand_y_cor)
my_screen = Screen()
my_screen.setup(width=1000, height=600)
my_screen.bgcolor("black")
my_screen.title("My Snake Game")
my_screen.tracer(0)
my_snake = Snake()
food = Food()
my_screen.listen()
my_screen.onkey(my_snake.up, "Up")
my_screen.onkey(my_snake.down, "Down")
my_screen.onkey(my_snake.left, "Left")
my_screen.onkey(my_snake.right, "Right")
game_over = False
while not game_over:
my_screen.update()
time.sleep(0.1)
my_snake.move()
# Detect collision with the food
if my_snake.head.distance(food) < 15:
food.refresh_food()
my_snake.grow()
# Detect collision with wall
if my_snake.head.xcor() < -480 or my_snake.head.xcor() > 480 or my_snake.head.ycor() < -280 or my_snake.head.ycor() > 280:
game_over = True
# Detect collision with tail
for segment in my_snake.segments[2:]:
if my_snake.head.distance(segment) < 10:
game_over = True
I tried reducing the moving distance of the snake head
self.head.forward(MOVE_DISTANCE) # Moving distance of head
I also tried different ways of moving the snake but it also didn't work.
The move
method looks incorrect:
def move(self):
for seg_num in range(len(self.segments) - 1, 0, -1):
self.head.forward(MOVE_DISTANCE) # Moving distance of head
new_x = self.segments[seg_num - 1].xcor()
new_y = self.segments[seg_num - 1].ycor()
self.segments[seg_num].goto(new_x, new_y)
I don't think you want to move the head for every single tail segment. Instead, you want to move the head once, and then move all the tail segments once:
def move(self):
self.head.forward(MOVE_DISTANCE) # Moving distance of head
for seg_num in range(len(self.segments) - 1, 0, -1):
new_x = self.segments[seg_num - 1].xcor()
new_y = self.segments[seg_num - 1].ycor()
self.segments[seg_num].goto(new_x, new_y)
Here's a runnable example with some adjustments:
from random import randint
from turtle import Screen, Turtle
GRID_SIZE = 20
class Snake:
POSITIONS = (0, 0), (GRID_SIZE, 0), (GRID_SIZE * 2, 0)
def __init__(self):
self.segments = []
self.create_snake()
self.head = self.segments[0]
def create_snake(self):
for position in Snake.POSITIONS:
self.add_segment(position)
def add_segment(self, position):
new_segment = Turtle("square")
new_segment.color("white")
new_segment.penup()
new_segment.goto(position)
self.segments.append(new_segment)
def move(self):
self.head.forward(GRID_SIZE)
for seg_num in range(len(self.segments) - 1, 0, -1):
new_x = self.segments[seg_num - 1].xcor()
new_y = self.segments[seg_num - 1].ycor()
self.segments[seg_num].goto(new_x, new_y)
def grow(self):
self.add_segment(self.segments[-1].position())
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)
def eats(self, food):
return self.head.distance(food.position()) < GRID_SIZE / 2
def collides_with_wall(self):
return (
self.head.xcor() < -w
or self.head.xcor() > w
or self.head.ycor() < -h
or self.head.ycor() > h
)
def collides_with_itself(self):
for segment in self.segments[3:]:
if self.head.distance(segment) < GRID_SIZE:
return True
return False
class Food:
def __init__(self):
self.pen = pen = Turtle()
pen.shape("circle")
pen.penup()
pen.shapesize(stretch_len=0.5, stretch_wid=0.5)
pen.color("red")
pen.speed("fastest")
self.reposition()
def reposition(self):
self.pen.goto(
randint(-w, w) // GRID_SIZE * GRID_SIZE,
randint(-h, h) // GRID_SIZE * GRID_SIZE,
)
def position(self):
return self.pen.pos()
def tick():
snake.move()
if snake.eats(food):
food.reposition()
snake.grow()
if snake.collides_with_wall() or snake.collides_with_itself():
screen.update()
return
screen.update()
screen.ontimer(tick, fps)
screen = Screen()
screen.setup(width=1000, height=600)
fps = 1000 // 10
w = screen.window_width() / 2 - GRID_SIZE
h = screen.window_height() / 2 - GRID_SIZE
screen.bgcolor("black")
screen.title("My Snake Game")
screen.tracer(0)
snake = Snake()
food = Food()
screen.listen()
screen.onkey(snake.up, "Up")
screen.onkey(snake.down, "Down")
screen.onkey(snake.left, "Left")
screen.onkey(snake.right, "Right")
tick()
screen.exitonclick()
There's still room for improvement, but overall this should be easier to maintain.