Rebound: an example game

With example HTML and Javascript for William Klock

(based on the tutorial at devx.com)

last updated, November 15th, 9:15 AM

The Product

(hit the left and right arrow keys to move the paddle.)
Score: 0


Work Log

Felix wrote down the changes he made to this document as he made them, so that you can first read the document as rendered to see how Felix wrote the script, and then you can look at the HTML source to learn more about how it all fits together.

Felix recommends that you, like him, take notes as you work your way through this tutorial.
Save your work into new files as you progress, and keep track of these files in your notes!
That way when you run into problems, you (and others) can retrace your steps to see how you got there.


I. First Felix followed the devx.com directions and made a text (named "rebound.html") file with the following content.

(Felix understood essentially what this was. If you look at the source for this document, you will see comments attempting to explain HTML to a novice).

After typing this into a file and saving it, Felix opened the file up in a web browser to make sure that it looked sane (as in, is there text? A couple of images? A score of 0?).


II. Then Felix blindly added the style sheet as directed by devx.com. This was a matter of adding the following style section to the head section he had written above. (Felix is totally unfamiliar with style sheets, though he thinks he should start learning more about them.)

There might be a way to rewrite this script so that you do not need the style section, but Felix is not sure how to do it, so it is safest for now to just use the style section as instructed.

(Felix did have to continually adjust the value of the top attribute for the #playingArea section below as he modified other parts of the HTML source. This probably reflects a mistake by the original author in using absolute positioning, but again, Felix does not know enough about style sheets to do any better at the moment. In step IX below, Felix learned how to do it better.)

Again, after performing this step, Felix reloaded the page in his web browser. This time he did it not as a sanity check, but rather because he was actually curious to know what sort of difference these style sheets have on the document.


III. Then Felix added the JavaScript code as directed by devx.com. This was a matter of adding a script tag in the head, below the style tag added above.

(Felix understood essentially what this was, and added more comments to make it clearer for you.)

There are four key concepts introduced in this code (and in many programming languages) that we will present now.

  1. Values can include simple things like:

  2. Expressions are commands you use to tell the browser to compute some value. If you want to add 1 and 3, you can write the expression (1 + 3). If the browser reaches that expression, it will add the two numbers to produce the value 4.

    This effort (to figure out what an expression means) is often called evaluating the expression.

    You can build up complex expressions by combining smaller expressions together. For example, you can write the expression ((1 + 3) * (7 - 2)) and the browser will work through the problem the same way that you might: by first figuring out what "(1 + 3)" is (4), then replacing "(1 + 3)" with 4 in the above expression: (4 * (7 - 2)). A similar calculation will yield (4 * 5), and that finally evaluates to 20. So the whole expression ((1 + 3) * (7 - 2)) evaluates to 20.

  3. Variables in JavaScript are like named boxes that hold values for the program to use.

    You put a value into the box by using the operation =, which is sometimes pronounced "gets". So z = 3 is pronounced "zee gets three". If you say "zee equals three", people won't always realize that you're describing something that is changing the meaning of z. That is why some people prefer to pronounce = in JavaScript (and some other languages) as "gets".

    In JavaScript, the boxes don't care whether the values are simple or complex; you say x = value and the box for x will contain value, whatever it may be.

    Variables are a kind of expression. Therefore, anywhere that you can put an expression, you can put a variable.

    So you can write ((x + 3) * (7 - y)) and the browser will start by looking for a number in the x box to figure out what it should add to 3 in (x + 3).

  4. Functions in JavaScript (as well as many other languages) are sequences of commands that have been packaged up into a value. Functions can have parameters, which are just boxes of values again. You can run the sequence of commands in a function by invoking the function; when you do this, you usually need to plug in values for the function's parameters. If a function f has two parameters, you invoke f on the values 1 and 2 by writing f(1,2).

    More generally, you can put expressions in as the parameters in function invocations: f( (x+3), (7-y) ).

    Function invocations are themselves expressions: if you have functions f and g that each take two parameters, this is a legal expression:

    f( f(1, x), g( 4, f(y, 2)) )
    and so is this:
    f(1, x) + g(y, 7).
    (But you won't see this usage very often in the code below, which makes Felix sad.)

    To define your own functions, you write the functon definition as

    function name(param-1, param-2, ...) { command-1; command-2; ... }
    (See the code below for a concrete example of a function definition.)

So, after all that exposition, here is the code itself:

Felix also had to change the HTML source so that the web browser will invoke the above init function when the brower loads the web page. One tells the web browser to do this by adding an onLoad attribute (with the value "init()", since that is the code we want the browser to run when it loads the body) to the body tag of the HTML document.


IV. Next Felix had to add a JavaScript function that would listen for when the user hits the keyboard.

This function introduces a couple new things.

  1. One is the use of == to compare values for equality. x == y is pronounced "Does x equal y" and it produces either the value true or the value false.

    There are also the > and < operators, pronounced "greater than" and "less than".

    There is also the ! operator, pronounced "not"; ! true is false, and ! false is true.

  2. Another is the use of && (pronounced "and") to combine boolean values.

  3. Another is the if statement. The statement

    if (test) { then commands } else { else commands }
    first evaluates test. If the value of test is true then we run the then commands. If it is false then we run the else commands.

  4. Finally there are some expressions that use a period (".", often pronounced "dot") in funny ways, sometimes pulling the numeric keyCode out of a key_event with an expression like key_event.keyCode and other times using = ("gets") with a left-hand side that looks like paddle.style.left. Felix is not going to discuss these things in this tutorial; he encourages you to investigate them on your own.

Felix also add to hook the above keyListener into the web page itself. Instead of editing the HTML (like we did to hook in the init function), this time we hook up the keyListener by modifying the init function.


V. At this time, Felix reloaded his page and checked if hitting the arrow keys caused any change to the page. Unfortunately, hitting the keys seemed to have no effect.

At first he pressed on to the remainder of the tutorial, hypothesizing that maybe the code is not intended to "work" yet, and hoping that maybe this problem would be resolved later.

But at some point Felix decided that he should double-check this theory. He did this by downloading the original script made by the "real JavaScript coders" at devx.com, deleting everything that would be added later in the tutorial, and then loading that file and seeing if the paddle responded to key strokes. The devx.com paddle did respond to key strokes, and Felix was sad.

Felix spent a while trying out different changes, trying to track down what he had done wrong. Eventually, he discovered his bug: he had typed the following in his script:

This code has a subtle but significant bug that causes the paddle to never move in response to key strokes. Can you find it? (You may need to do a line-by-line comparison against the keyListener code written above, which does correctly make the paddle move.)

Felix is relaying this story to you for two reasons:

  1. To tell you that even experts make mistakes
  2. To stress that every non-comment character you type might matter, so you need to have a sharp eye while debugging. Do not stay up all night doing it.


VI. Felix followed the next bit of instructions at devx.com, and added the following JavaScript code to the script tag in the head.

But Felix was not happy about doing this, because he really didn't know much about the functions detectCollisions, render, difficulty, setTimeout, or gameOver (because the devx.com author did not explain them at this point in the article).

Here are his initial guesses as to what these functions mean:

The original article does provide a couple of English paragraphs to explain all this, which I'll copy here for completeness, but if you do not understand all of the jargon it uses, that is okay.

The game continues until the user misses the ball. At that point, the ballTop variable will be larger than 470 (just over the height of the gameplay surface minus the height of the paddle plus the height of the ball) . . . The main game loop does all the work. It makes calls to detect whether the ball has collided with a boundary, renders objects to the screen, adjusts the difficulty of the game, and determines if the ball is still in play. If the ball is still within the acceptable playing area, it calls setTimeout(). The start() function is quasi-recursive; setTimeout() returns immediately so start() isn't "classically" recursive -- but the effect is the same. Using setTimeout() has the added benefit of allowing control over the frame rate of the game. I used a delay of 50 ms. You can adjust the overall speed of the game by manipulating that parameter.

Oh yes; in addition to adding the definiton of the start function to the script, Felix also had to add an initial invocation of the start function. (After the first time start is invoked, it will set up a timer to ensure that it will be invoked again in the future, as mentioned above in Felix's hypothesized purpose of setTimeout. But someone has to get start to happen initially.) The initial invocation of start will happen at the end of the init function.


VII. Once again, Felix decided to try reloading the page, just like he did after he added the keyListener This time he knew there was no way the code he added would "work", because he had no definitions for the functions detectCollisions, render, difficulty, or gameOver. But he wanted to know how the web browser would react.

Felix does not know how your web browser will react.


VIII. Finally, we have to write those four functions that Felix mentioned above. Felix essentially cut-and-pasted these from the devx.com article. (He would not have been able to come up with this code very quickly on his own; Felix is not an expert JavaScript programmer.)

Notice that the code is changing the browser's object for the web page at certain points, such as the line

score.innerHTML = 'Score: ' + currentScore;
The score_element of our page was originally
<div id="score_element">Score: 0</div>
This line of code is dynamically changing the inner text over time (at first its "Score: 0", then "Score: 5", then "Score: 10", etc).

Likewise the lines in the moveBall() function are changing the left and top attributes for the ball_element so that it moves around on the playing area. This is a very nice hack.

Felix would like to point out that he does not think that the code above is entirely correct; he suspects (based on interactions in his browser) that there are situations where the ball will not bounce properly off the paddle, but instead hops up and down across the top surface of the paddle. (Its an interesting effect but probably not intended by the original author.)


IX. Some friends at Felix's lab explained a bit more about CSS to him. They explained that the stylesheet should not have been using absolute position for all of its elements; only the ones below the playingArea_element.

They suggested the following change to the style of playingArea_element:

Felix thinks he understands the philosophy behind this change, but it will take too long to explain it here.


X. Lets work around the problem with holding down the arrow keys not actually causing a repeated key event. We do this by responding to the keydown and keyup events separately. When we see a keydown, we assign a non-zero velocity to the paddle, and when we see a keyup, we reset the velocity to zero.

First we add a variable that will hold the paddle's current velocity.

Then we add a function that will update the position of the paddle based on its velocity. This is analogous to the moveBall function.

Next we add an invocation of movePaddle function.

Next we rewrite the key down listener so that it does not move the paddle immediately, but rather adjusts the velocity. We also add a key up listener that resets the paddle's velocity to zero.

Finally we need to actually hook in our new keyUpListener.



Things to ponder

Questions

Here are some questions you should see if you can answer about the code above. They are meant to provoke discussion, rather than be simple yes/no questions.

  1. We used an operation above, || (pronounced "or") in the definition of collisionX(). What do you think it does? What is its relationship to the operation && ("and"). (Hint: look up George Boole, for whom the "booleans" are so named.)

  2. Why did we need to pass a string as the first argument to setTimeout, instead of writing the expression start() directly without surrounding it in quote marks?

  3. Why do we need both an init and a start function? Why can't we just pass init to setTimeout and get rid of start entirely?

  4. Why are collisionX() and collisionY() the only functions that have return statements? (Hint: see what happens if you remove the return statements from collisionX() or collisionY().)

Exercises

Here are some things that Felix thinks would make good exercises for extending the code above. (These are questions and tasks that an expert JavaScript programmer could probably figure out very quickly. Felix, a novice JavaScript programmer, can only guess that they are do-able; he does not know how to do them himself.)

  1. Add a button to the page that resets the game to the way it was at the start.

    (Felix thinks the init function already does the job of "resetting" for us. The hard part will probably be learning enough about HTML to add the button somewhere).

  2. Add support for a second paddle at the top or side of the screen, controlled by different keys. Then have players compete to try to get the ball past the other player's paddle. (a la "Pong"; ask your parents what "Pong" is).

  3. Make the ball bounce at different angles depending on where it hits the paddle, as if the paddle were slightly curved rather than perfectly flat. (Doing this properly requires some trigonometry; talk to your parents or to Julie for help with this.)

  4. Add support for mouse clicks; when the user clicks the mouse in the playing area, the ball jumps there and starts falling.

  5. Add rows of bricks to the top of the screen. When the ball hits a brick, the brick disappears.

    This is actually a very advanced problem that Felix doesn't actually intend for you to solve.

    (There are different approaches to this problem. One is an ugly brute-force approach where you, as the coder, have to write out a new HTML element for every single brick, and handle each one individually. Avoid this approach. The other approach is to create all of the bricks dynamically and treat them uniformly; doing this well requires that you go back and first learn how to process "collections" of data, which is beyond the scope of this tutorial (and beyond Felix's current knowledge of JavaScript).