Intermediate — Day 4

Body Segments & Food

Day Schedule
Online
TimeActivity
10:00Food Setup
food turtle, random position with random.randint()
10:15Body Segments
bodysegments list, head.clone(), got_food()
10:35Backward Shift Loop
Draw on whiteboard before coding; explain why backwards
11:00Break
11:10Breakout Groups
Guided coding: complete playable snake
12:20Reminders
In-Person
TimeActivity
8:00Warm-Up
Review Day 3: wall collision working for everyone?
8:20Food Setup
food turtle, random position with random.randint()
8:45Body Segments
bodysegments list, head.clone(), got_food()
9:10Backward Shift Loop
Draw on whiteboard before coding; explain why backwards
9:40Break
9:50Breakout Groups
Guided coding: complete playable snake
11:00Break
11:10Extra Activity
Body collision detection + score display with a pen turtle
11:50Open Coding
Catch up or add extra features
12:20Reminders
Learning Objectives
  1. Understand why the body shift loop iterates backwards (not forwards)
  2. Use head.clone() to create body segments
  3. Detect food collision with head.distance()
  4. Clear body segments in reset_game() using hideturtle() and clear()
# INSTRUCTOR — Day 4: Lists, For Loops & Intro to AI (Intermediate)
# -----------------------------------------------
# TEACH (first half): The body segment system is the most complex logic of the week.
# Walk through the backward shift loop carefully on a whiteboard or diagram first.
# Key insight: we go BACKWARDS through the list so each segment gets the position
# of the one AHEAD of it — if we went forwards, every segment would pile onto index 0.
#
# TEACH (second half): AI as a coding partner.
# Frame: "You've built most of this game. AI can now help you push further."
# Demo: paste the snake code into AI and ask it to add a score display.
# Have students read every line of the result before using it.

import turtle
import time
import random
# TEACH: Introduce random before using it.
# "random is a Python library for generating unpredictable values."
# random.randint(a, b) returns a random whole number between a and b (inclusive).
# Demo: run random.randint(-270, 270) several times and show different results each time.
# Ask: why -270 to 270 and not -300 to 300? (food would appear outside the playable border)

screen = turtle.Screen()
screen.title("Snake Game")
screen.bgcolor("black")
screen.setup(width=600, height=600)
screen.tracer(0)

head = turtle.Turtle()
head.shape("square")
head.color("white")
head.penup()
head.goto(0, 0)

# TEACH: bodysegments is a list of turtle objects — each one is a visible body square
bodysegments = []

def go_up():
    if head.heading() != 270:
        head.setheading(90)

def go_down():
    if head.heading() != 90:
        head.setheading(270)

def go_left():
    if head.heading() != 0:
        head.setheading(180)

def go_right():
    if head.heading() != 180:
        head.setheading(0)

screen.listen()
screen.onkeypress(go_up,    "Up")
screen.onkeypress(go_down,  "Down")
screen.onkeypress(go_left,  "Left")
screen.onkeypress(go_right, "Right")

# TEACH: the backward shift loop
# range(len-1, 0, -1) counts from last index DOWN to index 1 (not 0).
# We stop at 1 because index 0 is handled separately (it follows the head).
for i in range(len(bodysegments) - 1, 0, -1):
    bodysegments[i].goto(bodysegments[i - 1].pos())
    bodysegments[i].setheading(bodysegments[i - 1].heading())

if len(bodysegments) > 0:
    bodysegments[0].goto(head.pos())
    bodysegments[0].setheading(head.heading())

# TEACH: clone() copies the turtle's shape, color, size — no need to set those again
new_seg = head.clone()
bodysegments.append(new_seg)
# INSTRUCTOR — Day 4: Project: Snake: Body Segments & Food (Intermediate)
# -----------------------------------------------
# TEACH: Cumulative from Day 3. This is the fully playable snake game.
# New additions: food turtle, got_food() function, body shift loop, food collision.
# Check: eating food grows the snake; body follows head; wall collision still works.
# Common issue: snake "teleports" body — likely body shift loop placed AFTER head.forward().
# The shift loop must run BEFORE head moves so segments copy positions from this frame.

import turtle
import time
import random

screen = turtle.Screen()
screen.title("Snake Game")
screen.bgcolor("black")
screen.setup(width=600, height=600)
screen.tracer(0)

head = turtle.Turtle()
head.shape("square")
head.color("white")
head.penup()
head.goto(0, 0)

# --- Food ---
food = turtle.Turtle()
food.shape("circle")
food.color("green")
food.penup()
food.goto(random.randint(-270, 270), random.randint(-270, 270))

# TEACH: bodysegments grows by one each time the snake eats
bodysegments = []

def got_food():
    food.goto(random.randint(-270, 270), random.randint(-270, 270))
    new_seg = head.clone()  # clone copies shape + color from head
    bodysegments.append(new_seg)

def go_up():
    if head.heading() != 270:
        head.setheading(90)

def go_down():
    if head.heading() != 90:
        head.setheading(270)

def go_left():
    if head.heading() != 0:
        head.setheading(180)

def go_right():
    if head.heading() != 180:
        head.setheading(0)

screen.listen()
screen.onkeypress(go_up,    "Up")
screen.onkeypress(go_down,  "Down")
screen.onkeypress(go_left,  "Left")
screen.onkeypress(go_right, "Right")

halfSize = 290

def reset_game():
    head.goto(0, 0)
    head.setheading(0)
    for seg in bodysegments:
        seg.hideturtle()    # TEACH: hide so segments disappear visually
    bodysegments.clear()    # TEACH: clear empties the list — segments are gone

while True:
    screen.update()

    # TEACH: body shift must happen BEFORE head moves
    for i in range(len(bodysegments) - 1, 0, -1):
        bodysegments[i].goto(bodysegments[i - 1].pos())
        bodysegments[i].setheading(bodysegments[i - 1].heading())
    if len(bodysegments) > 0:
        bodysegments[0].goto(head.pos())
        bodysegments[0].setheading(head.heading())

    head.forward(20)

    # Food collision
    if head.distance(food) < 20:    # distance() measures pixel distance between two turtles
        got_food()

    # Wall collision
    if (head.xcor() > halfSize or head.xcor() < -halfSize or
            head.ycor() > halfSize or head.ycor() < -halfSize):
        reset_game()

    time.sleep(0.1)
# INSTRUCTOR — Day 4: Extra Activity: Body Collision (Intermediate)
# -----------------------------------------------
# TEACH: Add self-collision — the game resets if the head touches any body segment.
# This is a for loop over the bodysegments list with a distance check.
# It slots into the game loop after the body shift loop and food check.
# Threshold of 15px (not 20) avoids false triggers on adjacent segments.

# REFERENCE: the body collision block (added inside the game loop)
for seg in bodysegments:
    if head.distance(seg) < 15:
        reset_game()
        break   # TEACH: break exits the for loop immediately — no need to check remaining segments

# TEACH: why 15 and not 20? Each square is 20px wide.
# At distance 20 the squares are just touching edge-to-edge.
# At distance 15 they are overlapping — that's a real collision, not just adjacency.

# TEACH challenge: add a second food item
food2 = turtle.Turtle()
food2.shape("circle")
food2.color("yellow")  # different color so students can tell them apart
food2.penup()
food2.goto(random.randint(-270, 270), random.randint(-270, 270))

# In the game loop, add another food collision check:
if head.distance(food2) < 20:
    food2.goto(random.randint(-270, 270), random.randint(-270, 270))
    new_seg = head.clone()
    bodysegments.append(new_seg)