๐ŸŽ“ A1Learner
๐Ÿ”
0 of 18 lessons completed

Python Snake Biting Yourself

Self collision is the rule that ends the run when the head runs into the snake body. The check uses the same distance rule as eating.

The self check

The self check loops over every segment in the body. It compares each segment with the head.

    # Self: the head ran into the body, so the run ends
    for segment in segments:
        if segment.distance(head) < 20:
            reset_game()
            break

The same distance rule

Eating uses a distance under 20 pixels. The self check uses that same rule.

    for segment in segments:
        if segment.distance(head) < 20:
            reset_game()
            break

Walls first, then self

The wall check runs before the self check. Both checks run before the eat check.

    # Walls: the head left the board, so the run ends
    if head.xcor() > 290 or head.xcor() < -290 or head.ycor() > 290 or head.ycor() < -290:
        reset_game()

    # Self: the head ran into the body, so the run ends
    for segment in segments:
        if segment.distance(head) < 20:
            reset_game()
            break

Leaving the loop early

break leaves the loop as soon as one segment matches. The run is already over at that point.

        if segment.distance(head) < 20:
            reset_game()
            break

Example

import turtle
import time
import random

# The game window
screen = turtle.Screen()
screen.setup(width=600, height=600)
screen.bgcolor("black")
screen.title("Snake")
screen.tracer(0)

# The snake head
head = turtle.Turtle()
head.speed(0)
head.shape("square")
head.color("white")
head.penup()
head.goto(0, 0)
head.direction = "right"

# The food: one red circle on the board
food = turtle.Turtle()
food.speed(0)
food.shape("circle")
food.color("red")
food.penup()

# The body starts empty; the score starts at zero
segments = []
score = 0


def move():
    if head.direction == "up":
        head.sety(head.ycor() + 20)
    if head.direction == "down":
        head.sety(head.ycor() - 20)
    if head.direction == "left":
        head.setx(head.xcor() - 20)
    if head.direction == "right":
        head.setx(head.xcor() + 20)


def go_up():
    if head.direction != "down":
        head.direction = "up"


def go_down():
    if head.direction != "up":
        head.direction = "down"


def go_left():
    if head.direction != "right":
        head.direction = "left"


def go_right():
    if head.direction != "left":
        head.direction = "right"


def new_food_spot():
    x = random.randint(-14, 14) * 20
    y = random.randint(-14, 14) * 20
    food.goto(x, y)


def reset_game():
    global score
    time.sleep(0.5)
    head.goto(0, 0)
    head.direction = "stop"
    for segment in segments:
        segment.hideturtle()
    segments.clear()
    score = 0


new_food_spot()

# The keyboard steers the snake
screen.listen()
screen.onkey(go_up, "Up")
screen.onkey(go_down, "Down")
screen.onkey(go_left, "Left")
screen.onkey(go_right, "Right")

# The game loop: redraw, follow, move, check the rules, eat, wait
while True:
    screen.update()

    # The body follows: each segment takes the place of the one ahead
    for index in range(len(segments) - 1, 0, -1):
        x = segments[index - 1].xcor()
        y = segments[index - 1].ycor()
        segments[index].goto(x, y)
    if len(segments) > 0:
        segments[0].goto(head.xcor(), head.ycor())

    move()

    # Walls: the head left the board, so the run ends
    if head.xcor() > 290 or head.xcor() < -290 or head.ycor() > 290 or head.ycor() < -290:
        reset_game()

    # Self: the head ran into the body, so the run ends
    for segment in segments:
        if segment.distance(head) < 20:
            reset_game()
            break

    # Eat: the head reached the food
    if head.distance(food) < 20:
        new_food_spot()
        new_segment = turtle.Turtle()
        new_segment.speed(0)
        new_segment.shape("square")
        new_segment.color("grey")
        new_segment.penup()
        segments.append(new_segment)
        score = score + 10

    time.sleep(0.1)

Key points

  • The self check uses a distance under 20 pixels.
  • The loop checks every segment against the head.
  • The self check runs after the wall check.
  • The self check runs before the eat check.
  • break leaves the loop as soon as one segment matches.
  • The same reset_game() ends the run after a self bite.
  • A segment born from a bite is first checked on the next tick.

In this example, the loop follows with the body and then moves the head. The loop checks the walls first and ends the run at the edge. The loop then checks each segment with a distance under 20 pixels. A matching segment calls reset_game() and break leaves the loop at once. The eat check runs last, so a new segment is never checked on the tick it appears.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

When does the self check stop looking at more segments?