Animating Squares Around the Edge of a Window in Python


##Back Story

Previously, I wrote about how I like to get students to try to create Processing code to emulate a simple animation of a square moving clockwise around the edges of a screen. In this post, I’ll do the same thing, but using Python Turtle graphics, as well as the PyGame Zero module.

Garth Flint was playing around with using state variables, and was nice enough to write about the process. He first had the students create the following animation in Small Basic:

State Variables GIF

He then worked on making the same animation in Python, which did not go quite a smoothly. I had a few minutes to spare this evening, so I thought I’d try it as well. In the end, I created versions using two different modules.

##Python Turtle Graphics Every Python installation comes with the turtle module built in, which I like to use with my students to tackle some classic drawing challenges. So, naturally, I thought this might provide a reasonable way to replicate the animation. I ended up with this:

import turtle

#initialize world

theWindow = turtle.Screen()
#theWindow.setup(width=400, height=400)

bob = turtle.Turtle()
bob.shape("square")
bob.penup()
bob.speed(0)

#determine edges

edgeBuffer = 20
rightEdge = theWindow.window_width()/2 - edgeBuffer
leftEdge = theWindow.window_width()/-2 + edgeBuffer - 10  #the 10 is due to the way that python turtles

topEdge = theWindow.window_height()/2 - edgeBuffer + 10   #draw the square shape...

bottomEdge = theWindow.window_height()/-2 + edgeBuffer

#move to starting location

bob.setpos(leftEdge,topEdge)
bob.setheading(0) #facing east

speed = 5

while True:
  bob.forward(speed)
  
  if bob.xcor() > rightEdge:
    bob.right(90)
    bob.setx(rightEdge)
  if bob.ycor() < bottomEdge:
    bob.right(90)
    bob.sety(bottomEdge)
  if bob.xcor() < leftEdge:
    bob.right(90)
    bob.setx(leftEdge)
  if bob.ycor() > topEdge:
    bob.right(90)
    bob.sety(topEdge)

theWindow.mainloop()

Although this does work, and idiomatically follows the spirit of turtle graphics, it does completely remove the need for a state variable. I wrote another turtle graphics version that shoehorns in state variables, though it’s pretty ugly.

import turtle

#initialize world

theWindow = turtle.Screen()
#theWindow.setup(width=400, height=400)

bob = turtle.Turtle()
bob.shape("square")
bob.penup()
bob.speed(0)

#determine edges

edgeBuffer = 20
rightEdge = theWindow.window_width()/2 - edgeBuffer
leftEdge = theWindow.window_width()/-2 + edgeBuffer - 10  #the 10 is due to the way that python turtles

topEdge = theWindow.window_height()/2 - edgeBuffer + 10   #draw the square shape...

bottomEdge = theWindow.window_height()/-2 + edgeBuffer

#move to starting location and set original state

bob.setpos(leftEdge,topEdge)
bob.setheading(0) #facing east

speed = 5
state = 0

while True:
  if state == 0:
    bob.setx( bob.xcor() + speed )
    if bob.xcor() >= rightEdge:
      state = 1
  elif state == 1:
    bob.sety( bob.ycor() - speed )
    if bob.ycor() <= bottomEdge:
      state = 2
  elif state == 2:
    bob.setx( bob.xcor() - speed )
    if bob.xcor() <= leftEdge:
      state = 3
  elif state == 3:
    bob.sety( bob.ycor() + speed )
    if bob.ycor() >= topEdge:
      state = 0

theWindow.mainloop()

Although these worked, and have the advantage of not requiring any additional modules to be installed on top of the stock Python installation, I thought I’d give PyGame Zero a go as well.

##PyGame Zero

I was messing around with PyGame Zero earlier today, wondering if it might be suitable to introduce slightly more complicated GUI interfaces that what is possible with turtle graphics, but less complicated than pyGame. I thought this would be a good time to see if it is up to the task of some simple animations.

Installing the module could be a bit involved, depending on your setup. Since I already had pyGame installed (on both Mac and PC), all I had to do was call pip3 install pgzero from the terminal. After messing about for awhile, trying to wrap my head around the API, I came up with this (note that for this to work, I created a 20x20 png image that was simply a black box. That is what gets loaded in the first line of code):

theBox = Actor("box")

WIDTH = 300
HEIGHT = 300
state = 0

theBox.topleft = 0, 0

def draw():
    screen.fill((255, 255, 255))
    theBox.draw()

def update():
  determineState()
  moveAccordingToState()

def determineState():
  global state
  if theBox.right > WIDTH:
    theBox.right = WIDTH
    state = 1
  elif theBox.bottom > HEIGHT:
    theBox.bottom = HEIGHT
    state = 2
  elif theBox.left < 0:
    theBox.left = 0
    state = 3
  elif theBox.top < 0:
    theBox.top = 0
    state = 0

def moveAccordingToState():
  global state
  if state == 0:
    theBox.left += 5
  elif state == 1:
    theBox.top += 5
  elif state == 2:
    theBox.left -= 5
  elif state == 3:
    theBox.top -= 5