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.
0
,
6
,
258
,
-4
, etc),
true
and false
and are collectively
called booleans; ask your parents),
'this is a string'
,
'and this is also a string'
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
.
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)
.
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.
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
.
Another is the use of &&
(pronounced "and")
to combine boolean values.
true && true
evaluates to true
true && false
evaluates to false
false && true
evaluates to false
false && false
evaluates to false
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.
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:
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:
detectCollisions
is going to check whether
the bouncing ball has hit the paddle or the wall, and
change the ball's "speed" (really "vector"; ask your parents)
accordingly. If the ball has not collided with anything,
then its "speed" will be unchanged.render
is going to update the position
of the ball
after one unit of time has
passed.difficulty
is going to
do. He is tempted to comment out that line until he has
read further.setTimeout
is a built-in function
provided by JavaScript. It is also the source
for a great trick in JavaScript:
when you want a function (like start
) to be
invoked some time in the future,
you pass some text holding an invocation expression
to setTimeout
, along with
the amount of time to wait before evaluating the invocation.
(Okay, Felix admits that he had heard of the setTimeout
function before.)
gameOver
will tell the user that the game
has ended.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, theballTop
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 callssetTimeout()
. Thestart()
function is quasi-recursive;setTimeout()
returns immediately sostart()
isn't "classically" recursive -- but the effect is the same. UsingsetTimeout()
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.
start
function:
The ball does not move, but the paddle continues to respond
to left and right key strokes.
start
function, but if you turn on
Firebug (a JavaScript debugger built into FireFox)
then it will actually tell you that an error occurred, with
the message:
detectCollisions is not defined;
and when you click on the message, it takes you to the
place in your source code where the error occurred.
So you may want to consider using Firefox with Firebug turned on for your JavaScript development; it seems very nice.
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
.
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.
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.)
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?
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?
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()
.)
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.)
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).
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).
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.)
Add support for mouse clicks; when the user clicks the mouse in the playing area, the ball jumps there and starts falling.
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).