Advanced — Day 4

Choose Your Game

Day Schedule
Online
TimeActivity
10:00Choose Your Game
Intro the three options: Stick Man, Breakout, Space Shooter
10:15Breakout Groups
Build chosen game
11:00Break
11:10Breakout Groups
Continue building + add one custom feature
12:20Reminders
In-Person
TimeActivity
8:00Choose Your Game
Intro the three options: Stick Man, Breakout, Space Shooter
8:15Breakout Groups
Build chosen game
9:40Break
9:50Breakout Groups
Continue building + add one custom feature
11:00Break
11:10Open Coding
Polish and add extensions
12:20Reminders
Learning Objectives
  1. Build one of three game options to a playable state
  2. Add at least one custom feature or extension
# INSTRUCTOR — Day 4: Option A: Stick Man Runner (Advanced)
# -----------------------------------------------
# TEACH: Students build a runner game with a jumping stick figure.
# The stick figure is drawn with Turtle lines and circles using a helper function.
# AI helps generate the drawing code — students describe what they want, not draw it pixel by pixel.
# Jump physics use a simple velocity + gravity model: velocity decrements each frame until zero.
#
# Check: space bar triggers a jump only when on the ground (man_y <= -150).
# Obstacle scrolling is a challenge extension — not required for the base version.

import turtle
import time
import random

screen = turtle.Screen()
screen.title("Stick Man Runner")
screen.bgcolor("sky blue")
screen.setup(width=800, height=400)
screen.tracer(0)

# --- Stick figure ---
man = turtle.Turtle()
man.penup()
man.hideturtle()
man.speed(0)

man_x = -250
man_y = -100   # ground level
velocity = 0
gravity = -1.5  # TEACH: gravity pulls velocity downward each frame

def draw_man(x, y):
    """Draw stick figure centered at (x, y)."""
    man.clear()
    man.color("black")
    # Head
    man.penup()
    man.goto(x, y + 40)
    man.pendown()
    man.circle(15)
    # Body
    man.penup()
    man.goto(x, y + 25)
    man.pendown()
    man.goto(x, y - 10)
    # Arms
    man.penup()
    man.goto(x - 25, y + 10)
    man.pendown()
    man.goto(x + 25, y + 10)
    # Left leg
    man.penup()
    man.goto(x, y - 10)
    man.pendown()
    man.goto(x - 18, y - 45)
    # Right leg
    man.penup()
    man.goto(x, y - 10)
    man.pendown()
    man.goto(x + 18, y - 45)

def jump():
    global velocity
    if man_y <= -100:           # only jump if on the ground
        velocity = 18           # launch upward

screen.listen()
screen.onkeypress(jump, "space")

# --- Ground line ---
ground = turtle.Turtle()
ground.penup()
ground.hideturtle()
ground.goto(-400, -100)
ground.pendown()
ground.goto(400, -100)

# --- Obstacles ---
obstacles = []

def spawn_obstacle():
    obs = turtle.Turtle()
    obs.shape("square")
    obs.shapesize(stretch_wid=2, stretch_len=1)
    obs.color("dark red")
    obs.penup()
    obs.goto(420, -80)      # start off-screen right
    obstacles.append(obs)

spawn_obstacle()    # spawn one to start
frame = 0

while True:
    screen.update()

    # TEACH: apply gravity to velocity each frame
    velocity += gravity
    man_y += velocity

    # TEACH: clamp to ground so man doesn't fall through
    if man_y <= -100:
        man_y = -100
        velocity = 0

    draw_man(man_x, man_y)

    # Scroll obstacles left
    for obs in obstacles[:]:
        obs.setx(obs.xcor() - 5)
        # Remove obstacle when it leaves the screen
        if obs.xcor() < -450:
            obs.hideturtle()
            obstacles.remove(obs)
        # Collision detection (simple bounding box)
        if abs(obs.xcor() - man_x) < 25 and abs(obs.ycor() - man_y) < 35:
            pen = turtle.Turtle()
            pen.penup()
            pen.hideturtle()
            pen.color("red")
            pen.goto(0, 50)
            pen.write("GAME OVER", align="center", font=("Courier", 28, "bold"))
            screen.update()
            time.sleep(2)
            # Simple restart: rehide pen and respawn
            pen.clear()
            man_y = -100
            velocity = 0
            for o in obstacles:
                o.hideturtle()
            obstacles.clear()
            spawn_obstacle()

    # Spawn a new obstacle every ~80 frames
    frame += 1
    if frame % 80 == 0:
        spawn_obstacle()

    time.sleep(0.02)
# INSTRUCTOR — Day 4: Option B: Breakout (Advanced)
# -----------------------------------------------
# TEACH: Students extend their Pong work from Day 3.
# New concept: nested for loops to create the brick grid.
# Each brick is a turtle object stored in a list.
# In the game loop, a for loop checks if the ball hit any brick.
# Removing items from a list while iterating: use bricks[:] (a copy) to iterate
# so removing from the original list doesn't skip items.
#
# Check: ball bounces off bricks and removes them; game ends when all bricks are gone;
# paddle keeps the ball in play.

import turtle
import time
import random

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

# --- Paddle ---
paddle = turtle.Turtle()
paddle.shape("square")
paddle.shapesize(stretch_wid=1, stretch_len=5)
paddle.color("white")
paddle.penup()
paddle.goto(0, -250)

def paddle_left():
    x = paddle.xcor()
    if x > -270:
        paddle.setx(x - 30)

def paddle_right():
    x = paddle.xcor()
    if x < 270:
        paddle.setx(x + 30)

screen.listen()
screen.onkeypress(paddle_left,  "Left")
screen.onkeypress(paddle_right, "Right")

# --- Ball ---
ball = turtle.Turtle()
ball.shape("circle")
ball.color("white")
ball.penup()
ball.goto(0, -200)
ball.dx = random.choice([-3, 3])
ball.dy = 4

# --- Brick grid ---
# TEACH: nested for loops — outer loop = rows, inner loop = columns
ROW_COLORS = ["red", "orange", "yellow", "lime"]

bricks = []
for row in range(4):
    for col in range(8):
        brick = turtle.Turtle()
        brick.shape("square")
        brick.shapesize(stretch_wid=1, stretch_len=3)
        brick.color(ROW_COLORS[row])
        brick.penup()
        brick.goto(-210 + col * 60, 220 - row * 40)
        bricks.append(brick)

# --- Score ---
pen = turtle.Turtle()
pen.penup()
pen.hideturtle()
pen.color("white")
pen.goto(0, 270)

score = 0

def update_score():
    pen.clear()
    pen.write("Score: " + str(score), align="center", font=("Courier", 18, "bold"))

update_score()

while True:
    screen.update()

    # Move ball
    ball.setx(ball.xcor() + ball.dx)
    ball.sety(ball.ycor() + ball.dy)

    # Top wall
    if ball.ycor() > 280:
        ball.dy *= -1

    # Left/right walls
    if ball.xcor() > 310 or ball.xcor() < -310:
        ball.dx *= -1

    # Bottom wall — ball lost
    if ball.ycor() < -290:
        ball.goto(0, -200)
        ball.dx = random.choice([-3, 3])
        ball.dy = 4

    # Paddle collision
    if (ball.ycor() > -265 and ball.ycor() < -240 and
            abs(ball.xcor() - paddle.xcor()) < 55):
        ball.sety(-240)
        ball.dy *= -1
        # TEACH: slight randomness on paddle bounce to prevent infinite loops
        ball.dx += random.uniform(-0.5, 0.5)

    # Brick collision
    # TEACH: bricks[:] iterates a COPY so we can safely remove from bricks
    for brick in bricks[:]:
        if ball.distance(brick) < 40:
            ball.dy *= -1
            brick.hideturtle()
            bricks.remove(brick)
            score += 1
            update_score()

    # Win condition
    if not bricks:
        pen.clear()
        pen.write("YOU WIN!  Score: " + str(score),
                  align="center", font=("Courier", 24, "bold"))
        screen.update()
        time.sleep(3)
        break

    time.sleep(0.01)
# INSTRUCTOR — Day 4: Option C: Space Shooter (Advanced)
# -----------------------------------------------
# TEACH: Students build a top-down space shooter.
# New concepts: a ship that fires bullets, enemies that fall downward,
# two lists (bullets and enemies) and nested iteration for collision.
# TEACH: when iterating and removing from the same list, use list[:] to copy.
# TEACH: bullets[:] inside the for loop is the safe pattern — show what goes wrong without it.

import turtle
import time
import random

screen = turtle.Screen()
screen.title("Space Shooter")
screen.bgcolor("black")
screen.setup(width=600, height=700)
screen.tracer(0)

# --- Ship ---
ship = turtle.Turtle()
ship.shape("triangle")
ship.color("cyan")
ship.penup()
ship.goto(0, -280)
ship.setheading(90)     # TEACH: point the triangle upward

def ship_left():
    x = ship.xcor()
    if x > -270:
        ship.setx(x - 20)

def ship_right():
    x = ship.xcor()
    if x < 270:
        ship.setx(x + 20)

# --- Bullets ---
bullets = []

def fire():
    b = turtle.Turtle()
    b.shape("circle")
    b.shapesize(stretch_wid=0.3, stretch_len=0.8)
    b.color("yellow")
    b.penup()
    b.goto(ship.xcor(), ship.ycor() + 20)
    b.setheading(90)
    bullets.append(b)

screen.listen()
screen.onkeypress(ship_left,  "Left")
screen.onkeypress(ship_right, "Right")
screen.onkeypress(fire,       "space")

# --- Enemies ---
enemies = []

def spawn_enemy():
    e = turtle.Turtle()
    e.shape("square")
    e.color("red")
    e.penup()
    e.goto(random.randint(-270, 270), 320)  # start above screen
    enemies.append(e)

for _ in range(5):
    spawn_enemy()   # start with 5 enemies

# --- Score ---
pen = turtle.Turtle()
pen.penup()
pen.hideturtle()
pen.color("white")
pen.goto(0, 310)

score = 0
lives = 3

def update_hud():
    pen.clear()
    pen.write("Score: " + str(score) + "   Lives: " + str(lives),
              align="center", font=("Courier", 16, "bold"))

update_hud()

frame = 0

while True:
    screen.update()

    # Move bullets upward
    for b in bullets[:]:
        b.forward(15)
        if b.ycor() > 340:  # off screen
            b.hideturtle()
            bullets.remove(b)

    # Move enemies downward
    for e in enemies[:]:
        e.sety(e.ycor() - 3)
        # Enemy reaches bottom
        if e.ycor() < -340:
            e.hideturtle()
            enemies.remove(e)
            lives -= 1
            update_hud()
            spawn_enemy()
            if lives <= 0:
                pen.clear()
                pen.write("GAME OVER  Score: " + str(score),
                          align="center", font=("Courier", 22, "bold"))
                screen.update()
                time.sleep(3)
                break

    # Bullet vs enemy collision
    # TEACH: nested for loops — check every bullet against every enemy
    for b in bullets[:]:
        for e in enemies[:]:
            if b.distance(e) < 25:
                b.hideturtle()
                bullets.remove(b)
                e.hideturtle()
                enemies.remove(e)
                score += 1
                update_hud()
                spawn_enemy()   # replace the destroyed enemy
                break           # bullet is gone — stop checking other enemies

    # Spawn extra enemies every 120 frames (difficulty ramp)
    frame += 1
    if frame % 120 == 0 and len(enemies) < 12:
        spawn_enemy()

    time.sleep(0.02)