PencilCoder

Teacher's Guide: Keyboard Events

Overview

Events provides students a means to make their programs interactive. They therefore open up a range of new program design opportunities.

Along with the basic concept of events, the lesson introduces a range of events-related concepts and terminology, including event listeners, event binding functions, and event handlers. This introductory lesson focuses on keyboard-related events because they can be accessed without making direct reference to event objects. Event objects, which provide additional options for reacting to keyboard events and facilitate processing other system events—in particular, mouse-related events—will be the focus of the following two lessons.

More about the lesson

Any operating system that supports GUIs constantly monitors events such as keystrokes or mouse clicks. The operating environment reports these events to the programs that are running. Each program decides what, if anything, to do in response to these events.

Web browsers programs, such as Safari and Chrome, pass a subset of system event info on to JavaScript programs currently running in the browser. The list of accessible events is quite extensive, including things such as:

  • key presses
  • mouse movements, mouse clicks, mouse hovers
  • completing the loading of the web page
  • submission of a form
  • changes to the browser, such as closing a tab or resizing the window

This and subsequent lessons introduce coding tools to harness this event information, which enables us to make our programs more interactive. Our fucus will be on the subset of events that can be accessed using jQuery methods; we begin with the keystroke-related event keydown. Students access this event using the conveniently-named jQuery event-binding method, keydown.

In practice, use of keydown is quite simple. For example, the statement

keydown "a", doSomething

binds any press of the a key to the callback function doSomething. While the program is running, whenever a is pressed, doSomething is executed.

Admittedly, this is a very simple example of working with events. Additional details are provided below, in the Beyond The Lesson section of these notes.

Key events and animation queues

Key events are not synced with animation queues. As a consequence, even very simple programs that incorporate sprite movements or other animations can result in significant lags between button presses and resulting sprite behavior on screen. This can be illustrated with a simple "random mover" script, such as the following, which binds the space key to animations:

doOnSpace = ()->
  rt -30 + random(61)
  fd 10 + random(20)
keydown "space", doOnSpace

The most straightforward means to eliminate such lags is to set speed to Infinity for programs using events, presumably also adjusting sprite movements to smaller amounts to compensate for the increased rate of execution. An alternate approach is to embed a boolean flag in the event handler callback to prevent repeated keystrokes from resulting in a backlog of animations. In the following script, the boolean variably busy effectively causes subsequent presses of the space key to be ignored until the animations are complete. Note the use of the plan function, which is used to ensure the statement busy=false is indeed called at the right time:

busy = false

resetBusyFalse = ()->
  busy = false

doOnSpace = ()->
  if not busy
    busy = true
    rt -30 + random(61)
    fd 10 + random(20)
    plan resetBusyFalse
      
keydown "space", doOnSpace

A more sophisticated approach to prevent key presses and animations from getting out of sync involves the use event polling. This is the topic of a later lesson.

Notes to activities

The lesson activities are intentionally limited to simple, straightforward use of keyboard events. The goal here is to gain familiarity with events and comfort using keybinding funtions and callbacks to incorporate events into programs. Even thus limited, though, this lesson opens up broad new frontiers and should be much fun.

Students may be tempted to extend the TurtleCannon program to include one or more targets. For example, one could add a series of fixed targets, or, alternatively, a single moving target. As noted in the lesson, that extension will be easier to accomplish after the introduction of the forever function and the use of frame-based animation.

In the meantime, the TurtleWarperGame activity, below, presents a similar coding challenge, and provides suggestions for solving it using the queue-based animation students have worked with thus far in the course. The TankBattle activity provides a similar set of challenges and some additional related tips for working through the animation-queue-related aspects of the script.

Additional activities

  • Create a TurtleWarper program that has the turtle move around the screen in response to arrow presses, leaving dots as it goes. When you press the space bar, it should fade out, then reappear in a location somewhere ahead of where it was. Animate the "warp", perhaps making it look like a black hole appears under the turtle.
  • Simon
  • TurtleWarperGame: Turn the code from the previous activity into a game, in which the turtle tries to earn points as it moves around the screen, and perhaps ends when it is is encounters a predator or some other demise. Be aware, however, that this is a pretty tricky extension: including a predator in the program is almost certain to lead to animation-queue-related complications.

    In a few lessons, students will be introduced to the forever function, and event polling, which arguably provides the optimal solution. In the meantime, students have little option but to use a combination of iteration, i.e., a for loop with a very high number of iterations, and await done defer(). The embedded calls to await done defer() will prevent the large number of iterations from causing the program to hang, and will also make calls to touches function appropriately.

    An alternative solution to await done defer() is to embed the code in each iteration of the loop in a callback function passed to plan. As described in the notes to the Callbacks lesson, plan is a sprite-specific function used to insert code into the animation queue, such as calls to touches.

  • Simon: At long last it's time to transform our Simon games (introduced in additional activities section of the Notes to the Arrays! lesson and continued in Musical Objects Interlude! and Push Pop!) into a working game. Copy your previous work to a new program in this folder. Then pick four keys to correspond to the "buttons" on the screen, and bind them to an appropriate action for each keydown event.

    Simon

    This code can quickly become very complicated, so think through the logic first, perhaps writing it down on a piece of paper. Because "game play" gets passed back and forth between the game and the player, consider using boolean flags (e.g., playersTurn, gameOver) along with logic statements to effectively "turn off" the keybindings at the appropriate time. You should not need to use await done defer(), but calls to sync will likely help a lot. Finally, be creative in your use of functions to simplify your code, most importantly by reducing redundant code (which will certainly become a maintenance headache). For example, you might encapsulate the logic for "the game's turn" in a function showNextChallenge, which you can first call to kick off the game, and subsequently call again each time the player successfully completes a round.

  • MazeEscape: Code a script that generates a random map, such as by filling in each square of a grid one of two colors. This can be done efficiently using calls to box as you iterate over a two-dimensional array (i.e., for rows and columns). Choose one color to designate a safe path and the other to denote a game-over-inducing hazard (perhaps lava? quicksand?). Position the sprite on one side of the map, and challenge it to reach the other side. Use keybindings for the arrow keys to control turtle movements. Use conditional logic after each move to determine whether gameplay continues or ends—either in victory or else an early demise for the turtle!
  • MazeEscape Blank space MazeEscape2
  • TankBattle: use custom sprites (or images) to create tanks that two different players can control simultaneously in a two-player game. Use keybindings as suggested in the image below.

    By now, defining keybindings should seem relatively easy. The tougher challenge here is the code that controls what happens when a player fires the cannon. An effective approach is to model the projectile using a sprite. When the projectile is fired, that sprite should move from the firing tank in the direction that tank is pointing. Now comes the conditional logic: the projectile should continue moving until it either strikes the other tank (test this using touches) or exits the screen (test this using inside). Of course, these boolean functions won't give you the desired output unless you set speed to Infinity or use calls to await done defer(). If you code your program using the latter option, note that sprite-specific calls to done can be helpful, e.g., await projectile.done defer(). The benefit of using sprite-specific calls is that it will not block the code associated with the other sprites.

    MazeEscape

    This program will take some time to work through. Even then, you will likely run into disappointment: players may get frustrated that their keypresses won't work whenever another player is pressing and holding a key. The next lesson, Event Objects! , provides workarounds for this. We'll continue developing this program there.

  • PianoKeyup: Turn your keyboard into a piano! Use keybindings to make the keys A through K function as the "ivory" keys from middle C to the C one octave above. (Later on, after you have completed the basic exercise, you might also bind the corresponding keys in the row above to function as as the corresponding "ebony" keys.) Make this program function more like a real piano by making the note play longer if you continue to hold the key down. To do this, you will need to make use the keyup keybinding function. keyup is analagous to keydown, except that it binds to the keyup event, which fires when a key is released.
  • ControlledDot: Write a script that draws a randomly colored dot when you press the space key. The dot should continue to grow so long as you continue to hold down the key. A subsequent press of the key should start a new dot at a new location. You will need to define variables outside of your callback, i.e., global variables, to track the color and the dot size. You will also need to make use of keyup events.
  • MazeEscape
  • The Tangram is a puzzle that challenges you to arrange seven geometrical pieces—a square, a parallelogram, and five triangles—into a range of different shapes. Code a tangram board so that a player can manipulate blocks on screen. Bind arrow keys so that they control movement. You will also need to come up with key bindings to toggle between the pieces, to rotate the currently selected piece, and to flip it (by calling the mirror method). Once you have coded the blocks, see if you can meet the classic challenge: rearrange them, without overlapping, as a square!

Beyond the lesson

The desciptions of JavaScript events provided in the lesson and in the notes above are significantly abridged. A more complete description follows:

keydown is an example of a jQuery event binding function. An event binding function binds an HTML page element to a specific event source by registering an event listener with that event source. While the JavaScript program is running, whenever the event associated with that particular event source fires, that registered listener invokes the callback function that was passed to it when the listener was first set up.

When used in this type of event-related context, a callback that gets executed each time the event is triggered is formally referred to as an event handler—because it provides the instructions necessary for the program to appropriately react to (i.e., handle) that particular system event.

Auto-refiring of key events and associated coding challenges

keydown events automatically refire if a key is pressed and held rather than immediately released. There are two aspects of these refirings that are likely to impact student work, potentially in an undesired or frustrating way.

First, there is a significant lag following the initial event (about 400ms) before the first event refiring. Subsequent refirings occur with a much shorter lag (about 80ms). This can lead to rather clunky movements.

Second, The lesson activities and additional activities in these notes (with the exception of TankBattle) are designed to minimize the impact of these limitations on student work, though students will certainly encounter them. Students who explore use of keydown events on their own, such as attempting to build a two-player game (sharing the same keyboard), will likely find the limitations more problematic.

A second, and often more frustrating limitation is that subsequent key presses cancel the refiring of a previous keydown event for a pressed and held key. For now, the primary practical solution is to simply have the user repeatedly press keys rather than press and hold. While there are some workarounds that could improve this behavior using a combination of the keydown and keyup events (as explored in this program), the ideal solution is to use event polling, a technique addressed in the Event Polling! lesson.

A note on terminology

The event binding functions introduced in this lesson simplify the code required to bind callbacks to the underlying events when using pure JavaScript. The use of function names matching the underlying events is convenient and intuitive. The downside is that the dual use of the terms obscures the actual mechanics at work.

Be sure to make a clear distinction between an event (such as a keydown event, which fires and produces an event object when a key is pressed) and the corresponding event binding function (e.g., keydown) which is used to bind the event-handler callback functions to the specific event or class of events.

Anonymous functions

In this lesson, students will need to define the callback prior to binding the keys. An alternative is to define the callback in the same statement as the call to the keybinding function, as an anonymous function. The coding snippet in the Pencil Code coding palette provides an example which students will invariably encounter:

Typing Test

This functionality will be introduced to students in a subsequent lesson. In the meantime, it is recommended to avoid the use of anonymous functions as it can cause students to lose sight of the fact that they are working with callbacks.

Event objects

The keydown function syntax introduced in this section is in fact a convenience form added by Pencil Code for novice coders. The underlying jQuery keydown method does not accept an argument specifying the specific key. Rather, it only accepts the callback, though that callback can be written to accept a single argument that gets passed to it by the event listener. The argument passed by the event listener is a JavaScript object that provides a wealth of information about the event, including not only the key, but a timestamp, a record of concurrently-pressed meta-keys (such as shift or control), and more. This more general use of the keydown function is the focus of the Event Objects! lesson.

What can go wrong

delete (on Mac) or backspace← (on PC) doesn't work?!

keydown events fire for most, but not all buttons on a standard keyboard. The most notable exception are deletion-related keys, delete (on Mac) and backspace← (on PC). The next lesson will introduce two additional keyboard-related events, keypress and keyup, the latter of which can be used to track the use of the delete and backspace← keys.

focus

For keyboard input to register in your running Pencil Code program, you have to have the focus on the screen. In "output" mode (i.e., when the URL to your Pencil Code program contains /home/), this is not an issue, but in "development" mode (when the URL contains /edit/), you have to click on the graphical output part of the screen (though not on the test panel).

Blocking keybinding functions via await done defer()

In programs that make use of events, event bindings must be assigned prior to calls to await done defer(). Otherwise, calls to await done defer() (such as within extensive for loops, to prevent the browser from hanging and/or to make calls to functions such as touches accurate) will block that the execution of the keybinding functions and thus prevent those bindings from being assigned.

Technicalities

Most of the technical notes related to key events, including a discussion of the underlying JavaScript addEventListener method, are consolidated in the Technicalities section of the notes to the Event Objects lesson.

Pencil Code variants of jQuery functions

As noted above, the keydown method available in Pencil Code is actually a slight variation of the underlying jQuery method by the same name. The differences between the two will be discussed in depth in the notes to the Event Objects lesson. In short, Pencil Code's variant of keydown facilitates binding a key event for a specific key. It accepts two arguments, the first being a key and the second being the callback. The jQuery variant of keydown establishes a listener that listens to all keys. It only accepts a callback, in which one can place code that checks if the desired key has been pressed, and if so, how to respond.

HTML page elements and event listeners

Recall that events such as key presses or mouse clicks are created by the underlying operating system, which subequently passes them to running programs, including the browser. Technically, the browser processes these as HTML Events. We use JavaScript listeners to connect these events to our scripts.

Consequently, each event listener (and hence the associated callback) is always associated with a specific HTML page element. In Pencil Code, use of keydown involves an implicit reference to the underlying HTML <document> element, as if $('document').keydown had been called. Recall that the jQuery selector function (jQuery or, more commonly, $) wraps an HTML element in a jQuery object. Thus, we see that keydown is actually a method of a jQuery object.

The upshot of the preceding is that listeners can be attached to page elements other than <document>. For example, a mouseclick event listener can be added to just about any page element, to make that element clickable. Keystroke-related events are a bit trickier, as those elements must be focusable. By default, only a handful of HTML elements have this property by default. However, this is not a significant limitation, as elements that are not inherently focusable, such as <div>, can be made focusable by adding the tabindex attribute, as described mdn web docs entry.