CS 120, Fall 2012, Lab 8:
Keyboard and Mouse

In this lab, you will program responses to mouse and keyboard events, and you will use HTML canvas graphics in your response.

To begin the lab, you should copy the folder named lab8 from /classes/cs120 into the cs120 folder in your home directory. For the lab, you will work on several files in that folder. It also contains the two sample files that we looked at in class on Monday, event-demo.html and subkiller.html. You can use those files for reference and for ideas. In addition to the sample files, there are three html files that will be used in the exercises, and there is a folder of image files that are used as part of Exercise 1.

The lab is due next Friday, as usual. Your work should be complete by 10:00 AM on that day. It will be collected from your cs120 folder at that time.

Exercise 1: Scratch Off

This is a rather easy exercise that should not take long to complete, assuming that you have read the lab in advance. However, the result is interesting.

For the first exercise, you will use the mouse in a fairly simple way. It is similar to the second mouse demo in event-demo.html. That is, you will respond to mouse drags by drawing something at the current mouse position. For this exercise, you should work with the file scratchoff.html. The goal is to create a kind of simple children's game (and if you know any small kids, it's a good bet that they will like it). In the game, a picture is hidden behind a colored rectangle that fills the entire canvas. You can "scratch off" the cover by dragging the mouse on the canvas. The illustration on the right shows an example where a large part of the picture has been revealed.

In fact, the picture is the background image for the canvas. In the current version of scratchoff.html, nothing has been drawn on the canvas, so you see the background image. (The default color of canvas pixels is transparent, so the background shows through.) The page is already programmed to set the background image when the button is clicked, but it does not cover the image with a solid rectangle, and it does not do anything when the mouse is dragged on the image. You have to complete the programming.

In addition to setting the background image, the setBackground() function has to hide the image by drawing a rectangle that fills the canvas. You should add some code to setBackground() do that. You can make the rectangle gray or, perhaps, a random color, or do something fancier if you want.

To complete the exercise, you have to program the response to the mouse by filling in the definition of doMouseDrag. This function is called over and over as the user drags the mouse on the canvas. You should clear a small rectangle centered at the current mouse position. (Recall that graphics.clearRect(x,y,width,height) will clear a rectangle by making the pixels in the rectangle transparent, allowing the background to show through again.) You should center the rectangle on the current mouse position.

The hard part is getting the current mouse postion. There is a standard way to do that. The function has a parameter named event that holds information about the event. The values of event.clientX and event.clientY contain the position of the mouse in the coordinate system of the browser window. Unfortunately, you need the postion of the mouse in the coordinate system of the canvas. To get that, you can use the "bounding rectangle" of the canvas. There is a standard way to do this:

var x,y;  // mouse postion
var rect = canvas.getBoundingClientRect();
x = event.clientX - rect.left;
y = event.clientY - rect.top;

If you add this code to doMouseDrag, then x and y will be the mouse postion that you need.

Finally, to add some interest, make the program use bigger rectangle if the user is holding the shift key down. The value of event.shiftKey is a Boolean true/false value that is true if the shift key is being held down, and you can test its value to decide whether to use a small rectangle or a bigger one.

Exercise 2: Keyboard Events

For the second exercise, work on the file keyboard.html. You will be working with keyboard events. This is also partly an exercise in understanding the use of variables. The file already has a function doKey(event) that is called when the user presses a key. Inside that function, it already tests whether the key is the space bar or one of the arrow keys, but there is no code to say what happens when each key is pressed.

The page currently draws a red square in the middle of the canvas. Your job is to make the square move up, down, left, or right each time the user presses one of the arrow keys. Furthermore, the square should leave a colored trail as it moves. The picture shows what this will look like after the square has been moved around for a bit. In the picture, the trail is yellow. A square is yellow if the red square has passed through it as the red square moved around.

The position of the red square is given by a pair of variables that remember the top-left corner of the square:

        var squareLeft = 320;
        var squareTop = 240;

To move the square, you have to draw over the current position of the red square with a different color, change the variables to the new position of the square, and draw the red square in its new position. Note that the size of the square is 20-by-20, so it has to move 20 pixels each time one of the arrow keys is pressed.

You should add code to the definition of the doKey() function to move the square up, down, left or right when the up-arrow, down-arrow, left-arrow, or right-arrow key is pressed. As the square moves, it should leave a trail. The trail should be a different color from the moving square. In the above illustration, the trail is shown in yellow. The size of the canvas is 640-by-480.

Furthermore, if the user presses the space key, the canvas should be cleared and the red square should move back to its original position at (320,240).

For full credit, you should stop the user from moving the square outside the edge of the canvas. If the new position of the square would put it outside the canvas, you should not move the square. If you want to do even more, you can add code for some other keys to the doKey() method. For example, press various letter keys to change the color that is used for the trail left by the moving square. You can use event-demo.html to find out the key codes for other keys (when you press a key, event-demo.html shows you the key code).

Note that all of the work that you have to do for this exercise is to be done inside the definition of the doKey function.

Exercise 3: Interactive Game

For the final exercise, you should work on the file game.html. Your goal is to make a simple interactive "game," with some animation and some user interaction via mouse or keyboard. (You don't have to make something as complicated as subkiller.html, but the SubKiller example will give you some idea of the type of "arcade" games that I am thinking of.)

Currently, game.html has support for mouse down events, key down events, and running an animation. However, nothing happens on screen, since the functions don't do anything. To complete the assignment:

You will need some advice about how to implement certain things. You will find some advice later on the page. But first, here are some ideas for possible games (or come up with one of your own):

Before you start any project that is more ambitious than these examples, you should talk to me about your idea and get some feedback about how you could approach it or even whether it is possible.

(By the way, a somewhat more complex game would be a nice idea for your final exercise in the course. Remember that part of your web assignment is to add one extra project that you design on your own and that was not part of a lab. It doesn't have to be a big project, but you should try to be somewhat creative.)

Here are some techniques you might find useful...

How do I tell if the user clicks an object? One way is to compare the object's coordinates to the coordinates of the point where the mouse was clicked, but that can be tricky. The file game.html defines some functions that can make the task easier. The functions getR(x,y), getG(x,y), and getB(x,y) can be used to find out the color of the pixel at position (x,y). The value of getR(x,y) is the red component of the color at (x,y), given as an integer in the range 0 to 255. Similarly, getG(x,y) is the green component and getB(x,y) is the blue component. When the user clicks the mouse, you know the coordinates (x,y) where the user clicked, and those coordinates are used as the parameters for the function. For example, if there is a red object on the screen and you want to know whether the user has clicked it, just test

if ( getR(x,y) == 255 ) {
   // The user hit the object!

Warning: The color called "green" in CSS does not have getG(x,y) == 255. It's a darker green than that. To be safe, use RGB colors like rgb(100,200,50) so you know exactly what color values to look for.

How do I tell if a position is already occupied? This is the same as the previous question. Use getR(x,y), getG(x,y), and/or getB(x,y) to test the color of the position.

How can I make the animation stop and start? First, you need to add a global variable to remember whether the animation is running, such as

var running = true;

If you want to start with the animation not running, say running = false instead. Then inside the drawFrame() function, you only want to schedule a timeout when running is true. That is, replace the line at the end that says

setTimeout( drawFrame, 1000/30 ); 

with an if statement that says,

if ( running == true ) {
    setTimeout( drawFrame, 1000/30 );

If running is false, the animation stops; if running is true, it will continue for another frame. Finally, you need the code to actually start and stop the animation. To stop it, you just have to say, running = false. To start it, you have to set running to true, and you have to call drawFrame() to get it started again. That is, the code that you need to start the animation is

running = true;

How do I make something move, when it's not just moving vertically or horizontally? In the general case, an object has a "velocity" that tells how its position changes. You need four global variables for an object that can move in an arbitrary direction. You need to keep track of its x and y position. And you need a velocity that has two parts, speedX and speedY. In every frame, the position is changed by the speed. That is, you have to do the following in drawFrame() to move the object:

x = x + speedX;
y = y + speedY;

How do I make something speed up? The easiest way is to multiply speedX and speedY by a number a little bigger than 1. For example,

x = x * 1.05;
y = y * 1.05;

How do I make something "bounce off" the edge of the canvas? If you have a moving object, you can make it bounce off the top of the canvas by reversing its speedY: Just do speedY = Math.abs(speedY). Math.abs takes the absolute value, so this instruction makes sure that the object is moving downwards. You would do this when the y coordinate of the object becomes less than zero, indicating that it has moved off the canvas. That is, test if (y < 0). To be a little more accurate, you can take the size of the object into account. For example, if y is the center of a circle of radius 10, then the top of the ball is at y−10, and you want to test if (y−10 < 0). You can do similar things to bounce off the other edges of the canvas. For example for bouncing off the bottom edge of the window, you might do

if ( (y + 10 > canvas.height) ) {
    speedY = -Math.abs(speedY);  // negative speedY makes object move upwards

Note that these tests have to be made in every frame. (For more realistic bounces, you need something a little more complicated, but this method gives adequate results for this assignment.)

How do I give something a random velocity? You will need speed variables to keep track of the speed. Giving something a random velocity just means giving random values to those variables. For example, you might want to give speedX and speedY random velocities between -5 and 5. To do that, you could say,

speedX = (10*Math.random()) - 5;
speedY = (10*Math.random()) - 5;

Unfortunately, this can give a velocity very close to zero, or one that is very close to horizontal or vertical motion. That can be undesirable. Here is some code that produces either a random value between -6 and -3 or between 3 and 6, which would probably work better for you:

speedX = 3*Math.random() + 3;    // value is between 3 and 6.
if (Math.random() < 0.5) { // This will happen half the time.
    speedX = -speedX;  // reverse the velocity, so it's now between -3 and -6

How can I have lots of objects in my game? You can't. Not until we learn about arrays.