Rotating arrows exercise

This is by no means an original idea, but it's a fun thing to program.
Triggered by Mike Duggan: https://twitter.com/mickduggan/status/1090577885067386880
# triggered by Mike Duggan: # https://twitter.com/mickduggan/status/1090577885067386880 def arrow(center, size, rotation, barThickness, arrowhead): with savedState(): translate(*center) scale(size) rotate(rotation) points = [ (0.5, barThickness/2), (0.5  arrowhead, barThickness/2), (0.5  arrowhead, 0.5), (0.5, 0), (0.5  arrowhead, 0.5), (0.5  arrowhead, barThickness/2), (0.5, barThickness/2), ] polygon(*points) def arrowGrid(numArrows, arrowSize, barThickness, arrowhead, rotation): for i in range(numArrows): x = i * arrowSize for j in range(numArrows): y = j * arrowSize arrow((x, y), arrowSize, rotation, barThickness, arrowhead) def easeInOut(t): assert 0 <= t <= 1 return (1  cos(t * pi)) / 2 canvasSize = 400 numArrows = 4 arrowSize = canvasSize / numArrows barThickness = 0.5 arrowhead = 0.5 rotation = 180 duration = 4 framesPerSecond = 15 numFrames = duration * framesPerSecond for frame in range(numFrames): t = 4 * frame / numFrames quadrant = floor(t) t = easeInOut(t % 1) angle = ((quadrant + t) * 90) % 380 flip = quadrant % 2 angle = angle newPage(canvasSize, canvasSize) frameDuration(1/framesPerSecond) if flip: fill(1) rect(0, 0, canvasSize, canvasSize) fill(0) arrowGrid(numArrows + 1, arrowSize, barThickness, arrowhead, angle) else: fill(0) rect(0, 0, canvasSize, canvasSize) fill(1) translate(arrowSize/2, arrowSize/2) arrowGrid(numArrows + 2, arrowSize, 1  barThickness, arrowhead, angle + 180) saveImage("Arrows.gif")