Animation, and mouse and keyboard input

Python executes code quickly; programs that show animated graphics or take mouse or keyboard input spend quite a lot of time waiting for the human. For example, here is a Python program that counts from 1 to 1000; you can see that Python printing is much quicker than the eye.

A clock

What if you wanted to have the counting program pause one second in between each number? Here’s one way to do it. The browser version of cs1lib.py provides a function set_timeout that calls a function after a certain amount of time has expired:

The parameters are the name of the function, the number of milliseconds to wait between calls to that function, and the number of times to call the function. We call the function count a callback function, and we say that set_timeout registers a callback, because set_timeout records count as a function that should be called (back) later under some conditions.

You can think of the way that the program is being run is that somewhere, Python is running a while loop. That loop waits a long enough time, and then calls the specified function.

The call to clear_timeouts is needed because, in the browser, timeouts are set globally, for the entire browser; if you created new timeouts without first getting rid of the old timeouts, you’d have two sets of timeouts, causing some very strange behavior.

Exercise: alarm

Objective: Use a callback to trigger a delayed call to a function you write.

In the browser version of Python, you can import a function called alert from the module browser. Alert takes a single string as a parameters. Try calling alert now with a sample string.

Now use set_timeout to display an alert with some text after 10 seconds. You should call set_timeout with only two parameters, since that’s the proper way to schedule a single event.

Hint. Notice alert takes a parameter, but set_timeout only works properly with functions that do not take any parameters. So you can’t call alert directly. Maybe you could define another function that didn’t take any parameters…

Here is a solution.

Animation

Callbacks are are very useful whenever you want to do something over and over again, with some delay between the calls. One example is a graphical animation. Here’s a flip book animation on YouTube. Think of how a flip-book animation works:

  1. Go to the first page of the book.
  2. There’s a page with a frame of the animation. Display it.
  3. Move to the next page.
  4. Go back to step 2.

You can use a similar approach to draw an animation using Python:

  1. Create some variables that describe some properties of a frame of the animation. Call these the state variables.
  2. Draw a frame of the animation, using the state variables.
  3. Wait a brief time so that the human watching the animation can see the currently displayed frame.
  4. Change the values of the state variables to describe the next frame.
  5. Go back to step 2 (unless the state variables tell us that we’re done).

Here’s an example, an animation of a growing circle.

There’s just one state variable, radius. It keeps track of how large the circle is in the current frame. At the end of each frame, the radius is increased by 1. There’s also a second parameter passed to start_graphics, the number of frames of the animation to draw. By default, the function to draw a frame will be called about 40 times per second (the frame rate).

Code pattern for animations

We’ll often see a similar pattern to that in the growing_circle function when we create animations. There’s a loop to draw each frame of the animation, and there are state variables that indicate how to draw each frame (such as the radius of the circle in animate_circle). Before entering the loop, we give the state variables initial values. Within the body of the loop, we do five things to produce each frame of the animation:

  1. Call clear() to clear the graphics window, erasing the previous frame.
  2. Draw the graphics objects, based on the current values of the state variables.
  3. Update the variables that indicate the state so that they’re ready for the next frame, which will be accomplished by the next iteration of the loop.

Example animation: pulsing circles

Here’s a larger example:

Before continuing, go through this code and make sure that you understand how it works.

Example animation: psychboxes

Here’s one of Tom Cormen’s favorite animations. Psychedelic!

Try figuring out why it does what it does.

Exercise: moving ball

Objective: Create a simple animation using a global variable to represent state.

Write an animation of a ball of radius 10 that moves from the left side of the screen to the right side of the screen at a speed of 1 pixel per frame.

Mouse and keyboard input

It’s often necessary for graphics programs to get mouse and/or keyboard input from the user. In start_graphics, you can give a named parameter mouse_press that gives the name of a function to be called when the mouse button is pressed, like this:

We’ll see more about named parameters soon, but for now it’s enough to know that instead of placing the value for the named parameter in the correct location in the function call, we can use the name of the formal parameter and = to assign the desired value to a particular formal parameter.

In the example above, clicked is the callback function. The header for a callback function for the mouse press event should take two parameters that represent the x and y coordinates of the mouse, and one parameter that is a reference to an object holding some data. When clicked is called by cs1lib, the values of the mouse coordinates will be copied into these formal parameters.
You should not call drawing functions from within mouse or keyboard callbacks. Instead, you should change state variables within the callbacks, in such a way that the drawing function changes its behavior appropriately. There’s a good reason for this; in software that allows the window to be resized, the drawing code might be called to redisplay the window at a larger size, and that drawing process might overwrite anything not drawn using that function.

The drawing approach described here is a simple example of what is sometimes called a model-view-controller design pattern. The global variables contain a model of the world or system that we are interested in. The drawing function takes that model, and draws, or views that information. The callbacks provide control by allowing the model (the state) to be changed. Separating the drawing and control code from the model allows the model to be saved to disc or transmitted over the network: it’s just data.

Mouse movement

The mouse_press function is only called once when a mouse button is pressed. Unless you release the button and press it again, the callback won’t get called again. We need a callback that triggers whenever the mouse is moved, whether or not the button is pressed. Here’s an example:

Notice that the mouse_move callback function that you write takes three parameters: the x and y coordinates of the mouse location.

Computing the distance of the mouse from some location

To compute the distance d between two points with coordinates (x1, y1) and (x2, y2), we use the Pythagorean theorem: $d =\sqrt{(x_2 - x_1) ^2 + (y_2 - y_1)^2}$. In Python, how do you square a number? We use a special technique, known to only a few highly trained mathematicians. The technique is called “multiply the number by itself.”

The following example shows how to drag out a circle as you would if you were writing a graphical editor.

Getting input from the keyboard

cs1lib.py also provides a mechanism to set callback functions for key press events. You can set a function that will be run when a key is pushed down (key_down), or when the key is released (key_up).

Key press

Here’s an example of using a key_press function.

Your key_press function may fire multiple times if you keep holding the key; this is called key repeat and it is why you can hold the spacebar to get multiple spaces in a text editor.

Key release

If you’d like to keep track of when a key is pressed, you can watch for both key down and up events. For example, if you’d like a character on the screen to move upwards while the ‘w’ key is pressed, you could record when the ‘w’ was first pressed, and keep moving the character upwards as long as the ‘w’ key has not been released.

Exercise: smiley game

Objective: Edit complex code to add behavior that depends on keyboard input.

Imagine you would like to write a game where a smiley face chases a bouncing ball. Let’s write the keyboard control for the smiley face. Pressing ‘w’ should cause the smiley to move upwards, ‘a’ to the left, ‘s’ downwards, ‘d’ to the right.

I’ve written much of the code, but you’ll still need to do a few things. You’ll need to set keyup as a callback to be run when the key is released. Once you do that, the left and right controls should work; add the up and down controls by editing keydown, keyup, and draw_frame.