Introduction to (Web) Programming

Lab 10: Events

shortcut to main problems


General instructions


Console exercises

> function sayHi() { console.log('Hello!'); }
> typeof sayHi
> f()            // should produce an error!
> typeof f
> sayHi()
> let f = sayHi;
> typeof f
> f()

> setTimeout(sayHi, 3000);
[wait 3 seconds]

> function showKey(e) { console.log('key pressed: ' + e.key); }
> document.onkeydown = showKey;
[click on page (not console to regain focus) then try typing some keys!]
> document.onkeydown = null;
[now click on page to regain focus and try typing some keys]

Consider this little HTML canvas:

> let cvs = domGetElement('canvas');
> let bb = cvs.getBoundingClientRect();
> bb
> bb.left
> bb.top
> function showMouse(e) { console.log(e.clientX, e.clientY); }
> cvs.onmousedown = showMouse;
[click on canvas in several different places!]
> cvs.onmousedown = null;
[now click on canvas]

Main exercises

The main lab exercises are split into two parts. For the first, we revisit the "blue rain" example from the previous lab, but this time using a timer loop to make the rain fall without having to repeatedly click a button. In the second part, we revisit the "green grid" example, making it into an interactive game using a timer loop and mouse clicks on individual bricks.

For the rest of the lab, you need to download and unzip this archive. It consists of several files. You will work in events-lab.js, but will also want to look over events-lab.html. You should test your work by loading (and reloading when necessary) events-lab.html in your browser. You are encouraged to try my solution to see the tasks assigned below in action.

  1. Read over the code in events-lab.js to see what global variables (and constants) have already been defined and to see what functions have been provided for you. (Some may serve as templates for how to solve the remaining exercises.)

  2. Modify the initialize function so that it associates the function keyHandler with the keyup event for the entire page. (Assign the function as the value of document.onkeyup.) Modify keyHandler so that when the user presses c (for clear), the status area is updated to show an "all clear" message. Add an else clause to the if statement so that for any other keys it displays a message of the form:

    Key not understood: z

    Test by reloading the page and try clicking c and a non-menu key such as z.


    Blue rain (revisited)

  3. Modify keyHandler so that if the user types r then the rain function is called and modify rain so that after it draws all the drops, it schedules itself to be called again RAIN_DELAY milliseconds later. To test, reload, and press r to "make it rain".

  4. Make it so that the rain can be paused and restarted each subsequent time r is pressed. Do that by, in keyHandler, toggling the global Boolean value gRaining each time r is pressed and only call rain is that value is true after being toggled. Modify rain so that the its main code (especially the setTimeout) is only executed if gRaining is true. To test, reload and press r (several times).

  5. Complete rainMouseHandler so that when invoked it adds a drop (by pushing the corresponding x-y point onto gDrops). If the the rain is paused, it should explicitly draw the circle and call rainStatus so the user can immediately see the drop and the increased drop count. Associate rainMouseHandler as the function to be called on a mousedown event by assigning to gCanvas.onmousedown for the r case in keyHandler. Test by reloading, then clicking on the canvas when rain is both active and paused.

  6. (Time permitting.) Implement the function removeOldRain so that the global array gDrops is adjusted to only contain drops that are still on the canvas. One way to do that: create a new empty array, loop over all the points in gDrops, if the y-coordinate of a given drop is still on the canvas (less than gCanvas.height), push it onto the new array. When done, reassign gDrops to be the new array.


    Green grids (revisited): escape the dark! In this part of the lab, your task is to make a game out of the grid of "greenscale" blocks from the previous lab. Recall that the grid is represented by a two-dimensional array of numbers where each number is "how green" the block is. A slight change from last time: we define two constants, MIN_BRIGHT and MAX_BRIGHT that indicate the range of acceptable shades of green that are "in play". Blocks with a green value less than the minimum are considered "lost", blocks at least as bright as the maximum are considered "saved". The player's goal is to save more blocks than get lost. Over time, left untouched, in-play blocks become darker and darker until they become lost. Clicking on blocks save them and makes all the blocks in that row and column brighter. You need not implement the game precisely the same way as the demonstration version. In particular, feel free to adjust the values of MIN_BRIGHT, MAX_BRIGHT, BRIGHTER, DIMMER, and DIMMING_DELAY.

    Out of the box, pressing b starts a game of blocks by drawing the green grid and calling the (incomplete) darken function.

  7. Modify darken so that every cell that is not lost already (too dim) and not yet saved (at least as bright as MAX_BRIGHT) has its (green) value decreased by the constant DIMMER. Add code so that before the function concludes, it schedules itself to be called again DIMMING_DELAY milliseconds later. To test, reload, and press b and the blocks should become darker and darker shades of green over time.

  8. Add code to darken so that as each block is "dimmed", if its value goes below MIN_BRIGHT the global variable gLost is increased by 1. This should be easy to test: reloading and running the game should make the status bar indicate an increasing number of "lost" blocks.

  9. Complete blockMouseHandler so that when invoked it calls the brighten function on the corresponding column-row coordinates of the block that was clicked (not on the pixel coordinates). The trick here is to divide the relative pixel coordinates by their respective dimensions (e.g., BLOCK_WIDTH) and round down (use Math.floor). Associate blockMouseHandler as the function to be called on a mousedown event by assigning to gCanvas.onmousedown for the b case in keyHandler. Test by reloading, then clicking on a block on the canvas - it should turn bright green (and increase the "saved" count).

  10. Modify brighten so that it not only sets the specified block to be "saved" (making it brigher green), but that, assuming that the clicked block was "in play", then all the other blocks that are in play in its row and its column are made brighter (increased in their green value by the constant BRIGHTER). For any block that becomes at least as bright as MAX_BRIGHT, the gSave counter should be incremented. Test by playing.

  11. Implement an end to the game. The game is over when all blocks are either saved or lost. In other words, if the sum of gLost and gSaved is less than the total number of blocks (the product of COLUMNS and ROWS) then the game is still on. Modify blockStatus so that it indicates if the game is on or over. Modify darken so that its code (especially the setTimeout) is only executed if the game is still on. Modify keyHandler so that if b is pressed and the game was over, a new game is started (call resetBlocks). Test by playing.