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.
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.
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.
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:
You can use a similar approach to draw an animation using Python:
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).
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:
clear()
to clear the graphics window, erasing the previous frame.Here’s a larger example:
Before continuing, go through this code and make sure that you understand how it works.
Here’s one of Tom Cormen’s favorite animations. Psychedelic!
Try figuring out why it does what it does.
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.
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.
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.
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.
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).
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.
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.
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
.