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
.