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 should add the global variables that you need to keep track of the state of the game;
- You should decide whether you want the interaction to use the mouse or the keyboard, and you should fill in either doKey() or doMouseDown() to make that happen; you should do that by changing the global variables in response to the user action. (I doubt that you will need to use both functions; one kind of interaction is enough.);
- And you should fill in the drawFrame() function. This function can update any global variables that change automatically, without user action. For example, if you have a moving object, you need to store the position of that object in some global variables, and you need to make the object move by changing its position in each frame. Of course, the drawFrame() function also has to draw the current state of the game, based on the values of all the global variables.
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):
- An object move rather quickly across the screen. If the user clicks on it, the object disappears and the user gets some points. If it goes off the screen before the user can click it, the user loses some points. As soon as one object is gone, another appears. Maybe they get faster as the game progresses. Maybe there are different size objects, and smaller ones count for more points. Maybe several objects appear at the center of the screen and move away in different random directions.
- The screen is divided (conceptually) into 100-by-100 or 50-by-50 squares. In some frames a square is selected at random and filled. If the user selects a filled square, it is cleared. Can the user keep the number of filled squares to a minimum?
- Objects appear at the top of the screen and fall towards the bottom. The user presses arrow keys to move a "catcher" left and right along the bottom of the screen, but it moves rather slowly. The user tries to position the catcher under the object in time to catch it. If it falls off the screen before the user catches it, they lose. If they catch it, it disappears and another object appears at the top of the screen.
- For a more ambitious version of the previous game, the user has a "paddle" instead of a "catcher". An object appears with some random velocity. It can bounce off the paddle and off the top and sides of the canvas. However, it will not bounce off the bottom; if it misses the bottom, it will be lost. How long can the use keep it going?
- The user can move a circle with the arrow keys, like in the keyboard example above, but without a trail. Squares appear at random locations. The user tries to move the circle onto the square. If they do, they get a point. But the square only stays around for a certain number of frames, and then disappears (or moves to another location). How may points can the user earn?
- There are two users. One moves a square around in the canvas using the arrow keys, one moves a circle using the A, W, S, and D keys. Maybe the square has to catch the canvas, by moving on top of it?
- Similar to the previous game, but in this case, the objects move continuously either up, down, left, or right. Pressing a key simply changes the direction. (For each object, you need a global variable to keep track of which direction it is moving. For example, the value could be "u", "d", "l", or "r". When the user hits a key, the value of the variable changes. The variable is used in drawFrame() to decide which direction to move the object.)
- The game is a little like "helicopter". A circle tends to fall to the bottom of the screen and stays there, but clicking the mouse (or hitting some key) makes it jump up by some small amount. By clicking fast, you can make it stay up. Big squares appear and move across the canvas, with some room on the top and/or bottom. The circle has to avoid the square. If it gets hit by the square, it dies. (Maybe it also dies if it hits the bottom, instead of just lying there?)
- A very ambitious game would be something like Tron: Two squares move on the canvas. Two players change their direction by hitting keys. Each square leaves a trail as it moves. The trails are differently colored. As soon as one player hits a location that is already colored, that player loses. They also lose if they move off the screen. In this game, drawFrame() does not clear the screen. It simply draw a new piece of the trail.
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; drawFrame();
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.