<<
{{chapter}}
- Clear Output Window
- Clear Code Window
- Show Changes
- Forget Changes (this slide)
- Forget ALL Changes (every slide)
{{out}}
{{err}}
Welcome to JavaScript, a very useful language!
Here we have a simple "Hello World!" program. Try running it now, either by
clicking *Run*, or pressing *Shift-Enter*.
What happened? The web browser you are running contains a program called a
*JavaScript interpreter*. When you ran the program in the code window,
you instructed the interpreter to start at the top of the code and do just
what it said to do, from top to bottom, one step at a time.
In this case, we have told it to do exactly one thing, which is to show an
alert window containing the text |Hello, JavaScript!|. This text is called
a *string* (of characters).
But what is |alert|? And why is there a |;| at the end? We will get to both
of those topics shortly. Meanwhile, let's talk a bit about the tutorial
itself:
- The Table of Contents is above, marked *TOC*.
- *Page-Up* and *Page-Down* keys can be used to navigate.
- Code can be run with the *Run* button or *Shift-Enter*.
- Check out the options in the *Run* menu (the arrow). Among other things,
you can see what you have changed from the original slide.
Exercises
- Try running the program with a quotation mark missing. What happens? What
if you swap |"| for |'| everywhere?
- Change the string to say hello specifically to you.
- Try printing 'Hello, JavaScript!' using two strings instead of one, like
this: |alert('Hello ' + 'JavaScript!')|.
--
alert("Hello, JavaScript!");
On the first slide, you did several things:
- You called a **function**,
- You passed it an **argument**,
- You ended the **statement**, and
- You **interpreted** a **script**.
That's a lot for just one line! Let's pick this apart a bit. But before we
do, don't worry about memorizing all of this. You will get very good with
these by the time we are done.
Let's work backwards. First, the **interpreter**. JavaScript is a language, and
your web browser contains an interpreter that speaks it. Like any language,
there are rules. If you break them, the interpreter won't understand you and
you will see an error.
This brings us to our first rule: always end your statements with a
semicolon. Of all the rules, this is actually the one that JavaScript is
most forgiving about, but you can and will get into weird situations if you
leave them off. It's best to just always use them.
This brings us to "statements". A **statement** is basically "one thing" that
you are asking the interpreter to do, like a "unit of work". Sometimes it's
simple, like |alert('hello')|, but sometimes it's big and complex. The full
meaning of it should become clearer with time.
While we're talking about units of work, a statement that produces a value
is called an "expression". We'll see those very soon.
Finally, the interpreter has a fairly big vocabulary; it already knows a
lot of words. One of those words is |alert|. Just like in natural language,
some words are like _verbs_ and some are like _nouns_, and sometimes they
do different things based on context. The word |alert|, when followed by
parentheses, is like a verb: it indicates that you want to _do_ something.
This is called **calling a function**: placing parentheses after a verb, and
depending on what that verb means, optionally passing it **arguments**. These
change what the function does when called. For |alert|, if you play around
with it, you will see exactly what the argument does: it tells |alert| what
to display.
Finally, a **script** is your program: a sequence of commands that you give to the
interpreter, something that you make it do; it follows your script.
If you want, feel free to poke around a bit more before going on.
--
alert("Hello again. It's been a while.");
JavaScript, like many other programming languages, ignores **comments**:
bits of text that start with |//| and extend to the end of the line. They
are strictly for humans to read, and do not affect how your program runs.
// This is a comment.
There is also a multi-line version, delimited with two special symbols:
/* This is a
multi-line
comment. */
These are often formatted as seen in the code window, with repeated
asterisks and the terminator on a line by itself, but that is merely
convention. What makes it a comment is the way it _begins_ and _ends_.
Comments are useful for making notes to yourself
or others about how the program's code works.
Note that comment delimiters like |//| or |/*| inside of a string don't
mean anything special: they're just part of the string.
Exercises
- Output a string with a comment delimiter inside of it, e.g., |alert("Not // a comment!")|.
--
// This is a line comment. Line comments start
// with '//' and extend to the end of the line.
/* This is a multi-line comment.
* This formatting (with repeated asterisks) is
* conventional. The comment terminator stands alone.
*/
alert("This is not a comment.");
A **variable** is a place to remember something.
You assign a value to a variable using the
**assignment operator** (a single |=|) like this:
var a = "hi";
var A = "hello";
Now the _variable_ |a| contains the _string_ |"hi"| and |A| contains
|"hello"|. Yes, case matters: |a| is _not the same_ as |A|.
In JavaScript, variables spring into existence when they are declared with
|var| *or* assigned a value. Before that they do not exist, and referring
to them is an error, as with `i_dont_exist` on the last line of the example
code. It is typical to declare and assign at the same time. Until more is
explained later about when it is appropriate to omit it (hardly ever, it
turns out), always use |var| when assigning a variable for the first time.
A valid **variable name** can contain letters, numbers, and the characters
|_| and |$|, but cannot begin with a number (it can also use a substantial
set of Unicode characters, but we'll stick with plain ASCII for this
tutorial). A variable can contain any kind of value.
**Note:** when errors occur in your program, like the reference to the
nonexistent variable |i_dont_exist| in the example code, the program will
stop running at that point and the interpreter will give up. Any code after
the error is ignored and won't run.
Exercises
- Try assiging to a variable name that starts with a number
(like |1eet|). See what happens.
- Assign a new variable to an existing one, e.g.,
|b = a|. Output it.
--
var a = "hi there"; // 'a' now contains a string of text.
alert(a);
// Here we *reassign* 'a' to contain a number. Note the lack of 'var'.
a = 10;
alert(a);
// The $ is just another character. It has no special meaning.
var $my_longer_varname = 14;
alert($my_longer_varname);
// Until assigned, variables do not exist.
alert(i_dont_exist);
JavaScript understands a few fundamental **types** of data. You have
already encountered a couple of them, namely _strings_ like |"hello"| and
_functions_ like |alert|. We'll dive more deeply into functions later. For
now, let's focus on numbers.
You have already seen examples of numbers being assigned to variables, and
it works fairly naturally. Numbers do pretty much what you expect.
You can apply all of the basic mathematical operators to numbers:
* Multiply
+ Add
- Subtract
/ Divide
% Remainder
JavaScript represents all numbers in the same way underneath: as
_double-width IEEE floating point_ numbers, but you won't need to know much
about that for this tutorial. The important thing to know is that it knows
how to treat numbers as integers, as well, and sometimes we'll lie a bit
and pretend that it actually thinks of them that way.
Exercises
- Try adding |+| to the front of a number instead of |-|.
- Try using the modulus operator |%| between a couple of integer numbers.
What does it do?
--
// Normal decimal notation works great.
var myInt1 = 15;
var myInt2 = -25;
// As does hexadecimal!
var myInt3 = 0xdeadbeef;
// Various styles of floating point are allowed,
// including scientific notation.
var myFloat0 = 1.0;
var myFloat1 = 0.0423;
var myFloat2 = 1e-5;
var myFloat3 = 17.2e12;
// Let's see what that hex number above looks like:
alert(myInt3);
// Now for some operators:
alert(10 + 6);
alert(15 - 21);
alert(7 * 20);
alert(7 / 12);
// What is this value?
var a = 3 + 50 * 2;
// How about this?
var a = (3 + 50) * 2;
If you plan on always using semicolons and never accidentally forgetting
one, go ahead and skip this slide.
In the previous examples, we have taken it for granted that every statement
ends in a semicolon. JavaScript is kind of funny this way, because strictly
speaking, the semicolon is _not required_. There is a major caveat,
however. Sometimes the computer can't tell whether you intend to end a
statement or continue it.
Because of this, strange and unexpected things can happen if you omit the
semicolon. Consider the code window. There is an example of a case where
JavaScript can figure out what you meant, and another where it cannot.
There are many more such examples, so always be clear to the parser: use
semicolons at the end of statements.
Another important note: extra **whitespace** is not usually significant.
The indentation shown in these slides is for humans. The interpreter just
skips over it. The same goes for blank lines, spaces between
operators and numbers, comments, spaces between statements in function calls, etc.
But this rule is not consistently applied. Where possible, JavaScript will
insert a virtual semicolon for you at the end of a line if it thinks it
can, and that can be tough to predict: it's best to just always use them.
Exercises
- Before running the code, guess what the final output will be.
--
// This is allowed - just a number, hanging out, not doing anything.
+10;
// Oops - no semicolon. Is that OK?
var a = "message"
// Yes, because JavaScript can tell that this is a new statement.
alert(a);
// So, this should be fine, too, right?
var a = "hi there"
// Oops, this could be part of it, just really far away, even separated by
// comments!
+10;
alert(a);
There is no difference between |'| and |"| - they both form
equivalent strings. People usually pick one based on preference,
changing only to include quotes inside, like this:
"Don't touch my quoting."
'I need to "work", now.'
Occasionally, you need to include both kinds of
quotes inside of a string. In these cases, you can
**escape** quotes (take away their special meaning) using a
backslash:
"This string contains the \" delimiter."
Strings accept other escape sequences, like |'\n'|, which inserts
a line feed character, making a new line. More
info can be found here:
http://www.w3schools.com/js/js_strings.asp
Exercises
- Try creating a string that contains a backslash:
the backslash itself will need to be escaped with a backslash.
--
var message = ("This has a double quote \" inside.\n" +
'This has a single quote \' inside.\n' +
"This has a second line:\n Yup.");
alert(message);
JavaScript understands addition, using the |+| operator. That is as you
would expect. But it does different things based on the *type* of things
you are adding.
If you are adding numbers, that works in unsurprising ways. If you are
adding strings, however, it _concatenates_ them; it joins them together.
var myString = "hi " + "there.";
// myString now contains "hi there"
Exercises
- See if you can predict what the code in the code window does.
- Try adding a number to a string.
- Try adding a string to a number.
--
alert(5 + 15);
alert("5" + "15");
We are finally ready to dig more deeply into function calls. We have been
using them from the very beginning: it's hard to learn a language without
any verbs! We were introduced to |alert| early on, but JavaScript has a
*lot* of functions built in that you can just use.
A function is **called** (or _invoked_, _executed_, or _run_, take your
pick) by placing parentheses after its name. If it accepts **arguments**,
then they go inside as in |alert("Hello there!")|, which should look
familiar. We'll use some of the |Math| functions as examples here.
All JavaScript functions also **return** something. This means that, in
addition to taking arguments _in_, they also send values _out_. To obtain
the returned value of a function, just use that function call in place of a
value, e.g., |a = Math.sqrt(4)| or |alert(Math.ceil(1.5))|. You can
think of it as replacment: the whole function, including it's name,
parentheses, and arguments, is replaced with the value it returns.
You can call some functions with more than one value. In that case, supply
values separated by commas, e.g., |Math.pow(2, 10)| (more on that later).
Functions like |alert| are only used for their _side-effects_, so they implicitly
return the special value |undefined|.
As a convenience in this tutorial only, we make another function available
to you: |_output|. It accepts _any number_ of arguments and prints all of
them, separated by spaces, in the output window.
Exercises
- Look up more Math functions here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math
- Try a few of them. See what they do. Try them with integers (like 5) and
floating point numbers (like 6.7);
- There are a lot more functions in JavaScript. Peruse the reference material at
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
--
// This is unique to this course. It is not a standard JavaScript function,
// but is provided for convenience as you monkey around.
_output("Hello, output window.");
// You can give it multiple arguments.
_output("intVal", 10);
// Math.pow does exponentiation (a number to the power of another).
var y = Math.pow(2, 3);
_output(y);
// Math.floor produces the nearest integer below its argument.
_output(Math.floor(3.8));
// Math.ceil produces the nearest integer above its argument.
_output(Math.ceil(2.1));
// Note that if you just call a math function without outputting its
// result, it won't show you anything in the output window.
// The call below is replaced by its value, but then we don't do anything
// with it, so we don't see it.
Math.round(4.4);
// Note that alert returns the special value "undefined"
var alertOutput = alert("hello");
_output("output of alert: " + alertOutput);
Now that we know about function calls, we can talk about some of the more
interesting mathematical operations that are not expressed as symbols, like
taking a square root.
But first we need to talk a tiny bit about **objects**. You can think of a
JavaScript object as something that groups stuff that belongs together.
This definition will become more precise in time, but for now that is good
enough.
To access the stuff within an object, you name the object, put a dot after
it, and name the thing inside, like this:
Math.floor(55.3)
This calls the |floor| function that is held by the built-in JavaScript
|Math| object. There are many such built-in objects with many useful
functions inside, including things that manipulate strings, dates, times,
numbers, and elements on a web page that hosts your JavaScript.
Exercises
- Compute |Math.floor(52.5)| and |Math.ceil(52.5)|. What do these do?
What if you use negative numbers?
- Try adding a new value to the Math object by setting it, e.g.,
|Math.myAwesomeValue = 42;|. Did it work?
--
_output("pi =", Math.PI);
_output("sin(pi/2) =", Math.sin(Math.PI / 2));
// Uh-oh - why is this not exactly equal to 0.5?
// The answer lies in the IEEE floating point definition. Some numbers
// cannot be expressed perfectly in floating point.
_output("cos(pi/3) =", Math.cos(Math.PI / 3));
_output("sin(pi/3) =", Math.sin(Math.PI / 3));
_output("tan(pi/3) =", Math.tan(Math.PI / 3));
// Square roots work:
_output("sqrt(2) =", Math.sqrt(2));
// And you can take other roots using negative powers:
_output("cube root of 3 =", Math.pow(3, -3));
_output("4 to the 3rd power =", Math.pow(4, 3));
// Wait, did you expect this? How big is this number?
_output("sin(pi) =", Math.sin(Math.PI));
Remember how we said that objects are sort of like containers that hold
stuff inside of them? Well, it turns out that in JavaScript, _everything_
is an object, including things like strings and numbers. The *prototypes*
of these objects are |String| and |Number|, respectively (capitalization
matters).
A function that operates on the object that holds it is typically called a
**method** of that object. You'll see the terms used interchangeably
sometimes.
For now, let's explore the idea that everything is an object. Strings, for
example, have a few very useful methods, as do numbers. Take a look in the
code window for some examples.
Exercises
- Look at the documentation for String at
http://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String .
Try a few methods in the code window.
--
// The "length" member of every string tells you how many characters it
// contains.
_output("Hi there!".length);
// The "slice" method takes a starting index and an ending index, and
// returns a string from start (inclusive) to end (exclusive).
_output("0123456789".slice(2, 5));
// The "split" method allows you to split a string up into words, and
// accepts a delimiter.
_output("1:b:%".split(":"));
// Variables are just names for things like strings, etc., so you can
// obviously call methods on them as well.
// The "trim" method removes space from the beginning and end of a string.
var s = " A string with leading and trailing space. ";
var s_trimmed = s.trim();
_output("'" + s_trimmed + "'");
// When doing this trick with numbers, you need parentheses, otherwise
// it looks like you're specifying a floating point because of the dot.
// Note that many objects have a "toString" method.
_output((253).toString());
There are a lot of functions available in JavaScript itself. You can find
documentation for these at a couple of popular places.
Official: http://developer.mozilla.org/
Popular: http://www.w3schools.com/jsref
In addition to what the language itself has to offer, JavaScript usually
runs in a **hosted environment**, which will expose its own variables,
types, and functions that you can use to manipulate that environment.
One of the most common hosted environments is the web browser, though
you can also run JavaScript without a browser in environments like
"node.js". We'll focus mostly on the browser for this tutorial.
The point is that there are so many functions and variables available
that even seasoned developers do their work with the documentation open
all the time. Don't hesitate to keep the documents in front of you when
you are working on solving a problem.
Since we work within the web browser most of the time, it is useful to
know what that environment is called. It is referred to as the "Document
Object Model" or "DOM". Documentation for the DOM is often found
side-by-side with generic JavaScript documentation, and that is certainly
the case for http://www.w3schools.com/jsref, as well as for the official
http://developer.mozilla.org/ site.
Exercises
- Poke around the two sites above and see if you can find the JavaScript
documentation.
- Now look for DOM documentation. They're organized slightly differently
but you should see some big similarities once you drill down a bit.
--
// What is this? No idea? Try looking it up!
// Also, remember how you can use a function call anywhere a value is needed?
// What does this "chained replace" do below?
// REMINDER: the left side of dots like these has to be an object.
var a = "\tHello there\n".replace("\t", "<tab>").replace("\n", "<newline>");
_output(a);
Things are **equal** to each other if they have the _same values_. In
JavaScript, testing for equality is done using the |==| operator, and
inequality is tested with |!=|. As you might expect, |true != "true"| (one
is a Boolean value, the other is a String), but |10 == 5 + 5| (both sides
are numbers with the same value). The value of these operations is a
*boolean value*, one of **true** or **false**.
The |==| operator isn't generally a safe thing to use because it tries to
make your types match: it will try to make a number into a string if that
helps the equality check, so |"10" == 10|, even though one is a string of
characters and the other is a number.
The |===| operator checks both value *and* type, and is therefore safer: it
has fewer surprises. Always use |===| and |!==| when testing for equality,
and you will have fewer surprises. This is especially true when comparing
things against the value |null|, which is special and usually indicates "no
value" or similar. The |==| operator will surprise you when comparing
against a variable containing |null|, which can hide serious bugs by
failing to show you an error.
Exercises
- There are other comparison operators, and they
do what you'd expect, even on strings and other
sequences. Experiment with |<|, |<=|, |>|, and
|>=| - see what happens when you print something
like |5 < 7| or |'hello' >= 'hello there'|.
--
_output("Equality is squishy with strings and numbers.");
_output("10" == 10); // true
_output("10" === 10); // false
_output(10 == 5 + 5); // true
_output("Variables work, too");
var a = 1543;
var b = a;
_output(a == b); // Obviously true - same data.
_output(a != a+1); // Indeed.
You have already seen one kind of sequence: the String. You can access
members of a string using the |[]| operator:
"Hi there"[1] == "i"
There are other sequences, as well. All sequences in JavaScript, like
String, are _zero-based_, meaning their first element is element |0|.
A very common sequence type is the **Array**, typically created thus:
[1, 1, 2, 3, 5, 8]; // A 6-element array.
[]; // An empty array.
If you look up Array in the JavaScript documentation, e.g., by looking up
"mdn javascript array" on your favorite search engine or using the
documentation links above, you will find that it has some useful properties.
You can get the length of an array by accessing |.length|, access and even
set its members using |[]|, add elements to the end using |.push|, join
elements together in a single String using |.join|, sort elements using
|.sort|, etc.
Exercises
- Look up the Array and see what kinds of functions and data elements it
provides: http://www.w3schools.com/js/js_arrays.asp . Don't worry about
reading and understanding all of it, just poke around there and in the JS
Arrays Methods section.
- Try setting setting values beyond the end of the array. What happens?
--
// Create an array:
var a = [7, 3, 1, 9];
_output(a);
_output("a has", a.length, "elements");
// Indexing works as expected.
_output("third element", a[2]);
_output("last element", a[a.length - 1]);
// Arrays are *mutable*: they can be changed in place.
a[3] = "hello"; // Change element 3.
_output(a);
// And you can push values onto the end
// of them, among many other things.
a.push("new value");
_output(a);
// Sorting is really useful.
a.sort();
_output("sorted", a);
// Joining into a string is another.
_output("joined", a.join("::"));
// You can also take "slices" of an array.
// What does this do?
_output("sliced", a.slice(1, 4));
Up to this point, we have accessed the members of objects using dot
notation, e.g., |a.length|. As it happens, members of objects can also be
accessed and set using index notation: |a["length"]|. This makes the object
act like a _dictionary_ or a _map_, which is a data structure that maps keys
to values.
This is very useful when you just want to keep track of a bunch of **keys**
and **values**, and you don't necessarily know the keys beforehand.
An object that is suitable for use in this way can be created using |{}|:
{"name": "Mortimer", "age": 30}
{} // An empty object.
If the keys are valid JavaScript variable names, you can omit the quotes:
{name: "Fred", food: "pizza"}
To access these keys, you use indexing notation:
var a = {};
a["my key"] = 10; // Set the value
a["my key"] == 10; // Test: true
Exercises
- Try setting your own key/value pairs and outputting your dictionary.
- Set a value with dot notation and print it with index notation.
- Read about the built-in JSON object at
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
--
var things = {
"message": "something useless",
"name with spaces": 15.23,
3: "keys can be numbers"};
// Wait, what is this?
_output(things);
// We're getting ahead of ourselves here, but this is
// More likely what you expected to see:
_output("JSON output:", JSON.stringify(things, null, " "));
_output("numeric index:", things[3]);
_output("dot notation:", things.message);
_output("spaces require indexing:", things["name with spaces"]);
// You can set values in this object, as well:
things["newval"] = "something completely different";
_output(JSON.stringify(things));
We just finished outlining the most important _data structures_ in
JavaScript: arrays and objects. We also know how to find and call functions.
This is a good time to take a breather and try to do something more
exciting.
Since we are working within a relatively modern browser (so long as it isn't
IE 8 or earlier), we have access to the HTML 5 **canvas** element. Let's do
something with one.
When drawing on 2-dimensional canvas, the coordinate system is a little
different than you might be used to. The upper-left corner is (0,0), and
the lower-right corner is at (width-1, height-1). You are probably used to
thinking of the y-coordinate as going the other direction, so this can take
some getting used to.
Take a look at the code in the code area. Note that _there are no new
JavaScript ideas_ represented there. Everything there is something you
already know how to do _with the language itself_. But it all looks
unfamiliar! How could you possibly have known to write exactly that code
to display some rectangles?
Even seasoned programmers spend a lot of time with documentation open in
front of them (I did that when creating this example, in fact!), because
there is no way to remember it all, and things are constantly changing. They
also spend a lot of their time **learning new things**, because every
situation demands a new bit of knowledge. In this case, that new bit of
knowledge is a bit of DOM manipulation and displaying stuff on a canvas.
So, take heart! Once you know the _syntax_ of JavaScript, you can not only
start grasping the vast _vocabulary_ of the language and its host
environment, you can also _create your own_. We'll get into that shortly.
Exercises
- Look up canvas functions and try some different shapes.
http://www.w3schools.com/tags/ref_canvas.asp
--
// Ask the DOM for the output window (where we have
// been showing our output up until now).
var container = document.getElementById("output");
// Create a new DOM object: a canvas. Add it.
var canvas = document.createElement("canvas");
canvas.width = 200;
canvas.height = 200;
canvas.style.border = "1px solid black";
container.appendChild(canvas);
// To draw on a canvas, you need a "context".
// We'll stick with a 2-dimensional context, here.
var ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 50, 50);
ctx.fillStyle = "blue";
ctx.fillRect(70, 30, 100, 150);
You have already encountered a non-built-in function in these lessons:
|_output|. This is a function that I wrote to make it easy to see what is
going on without having to open an |alert| window. In the code window
you can see that we call |_output(_output)|. This basically instructs
the _output function to print a string version of itself. Check it out.
There is a lot in there that we haven't covered yet, and some stuff that is
specific to my use of AngularJS (http://angularjs.org/), so don't worry
about understanding it in depth right now.
The point is that _I made that_, and you can make your own functions, too.
To define your own function, we'll start with the named version:
function myFunc(a) {
// Your stuff goes here.
}
Defining a function is as simple as using the |function| keyword, giving it
a name, indicating what arguments it expects to receive (in the above case,
just one called |a|), and putting the **body** in curly brackets.
If you want to return a value other than |undefined| when the function is
called, use the |return| keyword. There are examples of this in the code
window.
Exercises
- Write a function that accepts two strings and returns them as one string
separated by ":". HINT: you know how to join strings using |+|, and you can
accept multiple arguments by separating them by commas in the function
definition like so |function name(arg1, arg2)|.
- Try the special |_canvas()| function that I provide with this tutorial to
get a canvas more easily. You can pass it an optional width and height if
you want.
--
// Let's peek inside, shall we?
_output(_output);
// Make your own!
function timesFifteen(x) {
return x * 15;
}
_output("3 times 15:", timesFifteen(3));
// Some functions don't need to return anything.
function myAlert(message) {
_output(message);
alert(message);
}
myAlert("alerts and displays");
// This is useful: make canvas creation easier:
function simpleCanvas() {
var container = document.getElementById("output");
var canvas = document.createElement("canvas");
canvas.width = 200;
canvas.height = 200;
canvas.style.border = "1px solid black";
container.appendChild(canvas);
return canvas;
}
var canvas = simpleCanvas();
var ctx = canvas.getContext('2d');
ctx.fillStyle = "green";
ctx.fillRect(40, 50, 60, 70);
Programs are not very interesting if they always do the same thing and ignore
the state of the world around them. One important way to respond to outside
stuff is _conditionals_. The most common conditional is the *if statement*.
if (test) {
// Do stuff if "test" is true.
} else {
// Do stuff if "test" is false.
}
The above is a basic |if else| structure. We encountered the _boolean
values_ **true** and **false** before, and now we can use them to change
the behavior of our programs. If the condition inside |()| evaluates to
**true**, then the first block executes, otherwise the second block
executes.
The |else| part is optional. You can omit it if you don't need it.
Exercises
- Write a function that displays a red rectangle if its argument is |true|,
and a blue rectangle otherwise.
- Read this short post on JavaScript _truthiness_:
http://james.padolsey.com/javascript/truthy-falsey/
--
function perkyBoolean(arg) {
if (arg) {
_output("Yes!");
} else {
_output("That's okay! I'm happy anyway!");
}
}
perkyBoolean(true);
perkyBoolean(false);
// Any boolean expression can be used in the if statement above.
perkyBoolean(10 == 5);
perkyBoolean(15 > 12);
// If statements actually evaluate their condition
// in a *boolean context*. In a nutshell, this means
// that they try really hard to convert any value into
// a true or false before proceeding. This can have some
// surprising effects:
perkyBoolean(""); // false - empty string
perkyBoolean("0"); // true - non-empty string
perkyBoolean(0); // false - numeric zero
Functions are really neat, and the ability to define your own changes
the game entirely. One thing that may not be obvious is that functions
can _call themselves_. When a function ends up calling itself, either
directly or indirectly, it is called _recursion_. Functions that do this
_recur_ (they do _not_ "recurse" -- that's what _I_ do when I hear that word
again and again).
Recursion is sometimes a bit mysterious, so let's start with a simple
example: function call chaining.
Suppose we have a function that adds two numbers together, called |sum|:
function sum(a, b) {
return a + b;
}
This should feel familiar by now. If it doesn't, please don't go on - go
back and go through the basics of functions first. This is where the jungle
thickens.
We can call that function like this:
var total = sum(4, 6);
Let's suppose we have *3* numbers we want to add together: |[3, 5, 7]|. How
would we do that? Well, we could do this:
var partial = sum(3, 5);
var total = sum(partial, 7);
But that introduces a variable that we're just going to throw away. We can
fix that by reusing |total|, but what if we just did this instead?
var total = sum(3, sum(5, 7));
Do you see what we did there? We know that |sum| returns a single value,
the sum of its two arguments. That value *replaces* the call to |sum|, and
then is used in another call to |sum|. Again, if this is not comfortable,
it's time to stop and practice before moving on.
Ready? Cool. Now let's suppose that we are summing an array of values:
var ns = [3, 5, 7];
var total = sum(ns[0], sum(ns[1], ns[2]));
Note that this is _exactly the same_ as before, we're just doing it to an
array. What if the array has more numbers?
var ns = [3, 5, 7, 4];
var total = sum(ns[0],
sum(ns[1],
sum(ns[2],
sum(ns[3], 0))));
What pattern did you see emerge here? Did you see how we snuck a 0 in
there? What does that do to the pattern? Could you do this with even larger
arrays? Don't move on until you are sure you know the answer, because we're
about to do something very different.
The pattern above is this: |sum| can compute the answer for an entire array
if it is given
# A number in the array (or the index of that number), and
# The sum of the rest of the array.
In other words, we can write the pattern like this:
var total = sum(first_number, sum_of_rest);
Well, that's a fairly simple pattern. Let's see if we can write a function
that does that accepting an array as input and using the above pattern to
sum over it:
function sum(arr) {
return arr[0] + sum(arr.slice(1));
}
Whoa. What did we do there? Let's look at it a piece at a time. First, we
take the first element of the array using |arr[0]|. We want to add that to
sum of the _rest of the array_. To get the "rest" of the array, we take a
|slice| starting at index |1| and going to the end: |arr.slice(1)|.
And that's it! There is one problem, though: if the array is empty, then
accessing |arr[0]| will produce an error. Let's fix that:
function sum(arr) {
if (arr.length == 0) {
return 0;
}
return arr[0] + sum(arr.slice(1));
}
And that's what you see in the code window.
What we have just done here is a _recursive call_: |sum| has a call to
|sum| inside of its definition. This is an incredibly powerful concept, and
as alluded to earlier, is powerful enough that you don't need anything
other than what you have learned to perform complex computations.
Exercises
- Fix the use of |sum| when it is given strings. HINT: add an argument to
|sum| called |default| that specifies the "base case". If |default ===
undefined|, you can set it to 0 and proceed, otherwise use it in the right
place.
--
function sum(arr) {
if (arr.length == 0) {
return 0;
}
// Not specifying the second argument of slice
// implies "all the way to the end".
return arr[0] + sum(arr.slice(1));
}
_output(sum([1, 2, 3, 4, 5]));
_output(sum([2, 3, 5, 7, 11]));
_output(sum([1, 1, 1]));
// This *almost* works! What's wrong with it?
// The exercises invite you to fix it. Give it a try.
_output(sum(["hi", " ", "there", " folks!"]));
// This one creates an array with every integer up
// to but not including 'n' in it. How does it work?
function range(n) {
if (n == 0) {
return [];
}
// We could also just return
// range(n - 1).concat([n - 1]);
var a = range(n - 1);
a.push(n - 1);
return a;
}
_output(range(10));
We have seen how to use recursion to visit and operate on every element of
a list, and that's a very powerful _functional_ approach to the problem.
Lisp users rejoice! JavaScript, however, is an _imperative_ language, and
as such it is more common to use a **loop** to modify a variable to achieve
the same end.
A loop is a body of code that is conditionally executed over and over
again. Let's have a look at the simplest of these loops: the |while| loop.
while(condition_expression) {
// do stuff
}
When JavaScript enters a loop like this, it immediately tests the
condition. If it's true, it proceeds to execute the loop's body. If false,
it skips the whole thing. It's a lot like an |if| statement, with the
difference that it goes back and evaluates the condition again after it
finishes with the body. In other words, it evaluates the body over and over
again while the condition is true.
A related loop is the |do|-|while| loop, written thus:
do {
// do stuff
} while (condition_expression);
The difference is that this always executes at least once. It's not super
common, but is occasionally useful to know.
Exercises
- Thought exercise: what happens if the |while| condition never becomes
|false|? (Note, if you are fiddling with this, you might have to kill
this browser tab and reopen it to fix it).
- Make a loop that populates an array with the numbers 0 through 17 and
then prints it.
--
var i = 0;
while (i < 10) {
_output(i);
i++; // New concept! See below.
}
// What is this i++ thing all about?
// It's the "postincrement" operator, and it basically adds 1 to i.
// If you set something equal to it, that thing gets the value of i
// *before* the increment.
var a = i++;
_output("postincrement", a, i);
// There is a corresponding "preincrement" operator, which *also*
// adds 1 to i, but increments i *first*, and then lets you assign
// a variable to it.
a = ++i;
_output("preincrement", a, i);
A much more common kind of loop is the |for| loop. It looks something like this:
for (var i = 0; i < 10; i++) {
// do stuff with i
}
It consists of three parts:
- Initialization (|var i = 0|)
- Test (|i < 10|)
- Increment (|i++|)
All of them are optional, and they can be any kind of statement (or list of statements).
It is equivalent to this |while| loop:
var i = 0;
while (i < 10) {
// do stuff with i
i++;
}
The |for| loop is more compact and puts important information (like how the
condition is ever going to become false) right up front where you can
glance at it. For these reasons, it is the most popular loop in JavaScript
by far.
Exercises
- See if you can use a |for| loop to populate an array with the numbers 3 through 20.
--
for (var i = 0; i < 5; i++) {
_output(i);
}
// A common idiom for iterating over elements of an array or
// an array-like thing (particularly if you are working with
// the DOM, where getting the length can be expensive and
// should not be done every time through the loop) is this:
var arr = [2, 3, 5, 7, 11, 13];
for (var i = 0, len = arr.length; i < len; i++) {
_output(i, arr[i]);
}
// Note how the initialization has two parts to it, separated by commas.
// That's part of how |var| declarations work. We declare and assign two
// variables at once that way.
There is another type of |for| loop that is quite common, and that is the
|for|-|in| loop. This loop lets you iterate over all of the keys in an
object, or all of the indices in an array (which is in a lot of ways the
same thing).
It looks like this:
for (var key in object_of_interest) {
// do stuff with key
}
The order is not guaranteed to be meaningful for object keys, but it will
be in order for array indices.
Exercises
- Try something crazy, like iterating over all of the fields in the
top-level DOM |document| variable.
- What about the DOM |window| variable?
- What happens if you try to also output the *values* of |window|?
--
// A for-in loop that iterates over the indices
// in an array (not the values!).
_output("array loop");
var a = [2, 3, 5, 7, 11, 13];
for (var k in a) {
_output(" ", k, a[k]);
}
// A for-in loop that iterates over the keys
// in an object (again, not the values!).
_output("object loop");
var o = {"a": 1,
"b": 3,
"name": "thing"};
for (var k in o) {
_output(" ", k, o[k]);
}
As we work more with loops and conditionals, we'll be using boolean operators.
Recall that JavaScript's truthiness means "if it isn't |""|, |0|, |null|, |undefined|, or
|false|, it's considered true".
There are three very common and important boolean operations that we need to
cover: |!|, ##||##, and |&&|.
|!| is boolean "not" (inverse):
!false == true
!true == false
|&&| is boolean "and" (conjunction):
false && false == false
false && true == false
true && false == false
true && true == true
##||## is boolean "or" (disjunction):
false && false == false
false && true == true
true && false == true
true && true == true
Note that with these last two, there is an important caveat: if both values
are truthy, |&&| returns **the second value** (not just |true|).
Similarly, ##||## returns **the first truthy value**. Examples of this are
in the code window.
Exercises
- Play around with the "returns second value" nature of |&&|.
--
// Boolean AND:
_output("f and f =", false && false);
_output("f and t =", false && true);
_output("t and f =", true && false);
_output("t and t =", true && true);
// Spacer
_output();
// Boolean OR:
_output("f or f =", false || false);
_output("f or t =", false || true);
_output("t or f =", true || false);
_output("t or t =", true || true);
_output();
var a = null;
// Since 'a' is null, b gets the next value.
var b = a || "hello";
// Since 'b' is not null, c gets b.
var c = b || "hi";
_output("a", a);
_output("b", b);
_output("c", c);
Within a loop (any kind of loop), you can use the |break| and |continue|
keywords. These are short-circuiting operations. The |break| keyword
instructs the interpreter to "exit this loop immediately", and the
|continue| keyword instructs it to "go back to the beginning of the loop,
skipping the rest of its body."
The code window shows examples of how these statements work. They are both
pretty useful, but you will tend to see |break| a lot more frequently.
Of course, if you are in a loop within a function, you can also call
|return| at any time to jump all the way out of the function (and the loop,
obviously).
Exercises
- Try iterating over all of the keys in the |window| object. Stop when you
find one that starts with 'c'.
--
_output("first loop");
// break exits early.
for (var i = 0; i < 10; i++) {
if (i == 5) {
break;
}
_output(i);
}
_output("second loop");
// continue goes back to the beginning immediately.
for (var i = 0; i < 10; i++) {
if (i > 2 && i < 7) {
continue;
}
_output(i);
}
It's time to apply what we have learned. If anything in this chapter is
unclear, this would be a great time to go back and review.
With that, let's talk about sorting. If you were to imagine a simple
sorting algorithm, you might come up with the following:
- Find the smallest element of the array,
- Sort the rest of the array and add the smallest element to the beginning.
This is called a **selection sort**, and it is indeed simple and effective.
It is also fairly slow: every element that goes into its correct location
requires finding the smallest of the remaining elements by checking them
all in turn. This is basically quadratic behavior (for a list of size |n|,
it takes about |n*n| steps).
Another way of viewing the selection sort is this:
- Pick the first element as the one to "fix",
- Find the smallest element of the array from "fix" onward,
- Swap it with "fix",
- Move "fix" to the next position and start again until finished.
This iterative algorithm highlights just how many steps are being done to
sort an array this way. An implementation of it (using the first recursive
definition) is in the code window.
What if you tried a _divide and conquer_ approach like **mergesort**
instead? Here's how it works:
- Divide the array into two pieces.
- Sort both pieces.
- Merge them together (repeatedly take the smallest of each).
This is better. The idea is that smaller lists are (much) faster to sort,
and once sorted, merging is very simple: the smallest element of each
sub-array is already at the front.
If we divide the array into two pieces, then we can divide each of *those*
into two pieces and sort even smaller arrays. If you keep going like that,
you eventually have an empty or 1-element array to sort, and those are both
already sorted!
The work done is |n * log(n)| for this divide-and-conquer algorithm, far
better than |n * n|.
A recursive implementation of mergesort is also provided in the code window.
Exercises
- Spend some time understanding all of the code in the code window. Don't
worry, it's less than it appears! Also, note that absolutely none of it
is new at this point. You have learned every concept displayed there.
If some of it seems unfamiliar, go ahead and review a few concepts. The
rest of the tutorial will assume that you are comfortable with this slide.
- Try turning the explanations above into pictures of what is going on. It
might help to draw an array somehow and label pieces of it, then step
through the idea on paper.
- Check out these animations of various sorting algorithms. The ones we
have looked at are "Selection" and "Merge":
http://www.sorting-algorithms.com/
- Try removing all of the comments and see how small the algorithm really is.
--
// These will be helpful in counting how much
// work each algorithm does.
var selection_compare_count = 0;
var merge_compare_count = 0;
// All sorting functions here create new arrays from the old ones.
// It's easier to see what they're doing that way.
function selection_sort(arr) {
// Arrays with one or fewer elements are already sorted.
if (arr.length <= 1) {
return arr.slice(0); // return a copy
}
// Find the location of the smallest element.
var index_of_smallest = 0;
for (var i = 1; i < arr.length; i++) {
selection_compare_count++; // keep track of comparisons
if (arr[index_of_smallest] > arr[i]) {
index_of_smallest = i;
}
}
// The rest of the elements don't include the
// smallest one we just found.
var piece1 = arr.slice(0, index_of_smallest);
var piece2 = arr.slice(index_of_smallest + 1);
var rest = piece1.concat(piece2);
return [arr[index_of_smallest]].concat(selection_sort(rest));
}
// Merge is a helper function, creating one sorted
// array from two sorted arrays.
function merge(a1, a2) {
merge_compare_count++; // keep track of comparisons
if (a1.length == 0) {
return a2;
} else if (a2.length == 0) {
return a1;
} else if (a1[0] < a2[0]) {
return [a1[0]].concat(merge(a1.slice(1), a2));
} else {
return [a2[0]].concat(merge(a1, a2.slice(1)));
}
}
function mergesort(arr) {
if (arr.length <= 1) {
// Trivially sorted. Return it.
return arr;
}
// Split and sort each piece.
var mid = Math.floor(arr.length / 2);
var left = mergesort(arr.slice(0, mid));
var right = mergesort(arr.slice(mid));
return merge(left, right);
}
// Let's try it out!
var a = [5, 2, 6, 3, 9, 10, 22, 5, 1, 3, 0.1, -3,
23, -5, 28, 8, 11, 19, 33, 27, 13, 18];
selection_compare_count = 0; // reset before running
var s_sorted = selection_sort(a);
_output("selection sort (" + selection_compare_count + " comparisons):", s_sorted);
merge_compare_count = 0; // reset before running
var m_sorted = mergesort(a);
_output("mergesort (" + merge_compare_count + " comparisons):", m_sorted);
So far we have mostly focused on functions that take a fixed number of
arguments. We have seen an example or two where you can *omit* arguments
when calling a function, and they will take on the value **undefined**
within it.
It turns out that you can have a function accept an *arbitrary* number
of arguments, as well, by using the special |arguments| sequence within a
function. You may have noticed that the implementation of |_output| uses
this trick (try running |_output(_output)| again and take a look).
Check out the code window for examples of how function arguments work.
Also, this is worth reading: http://www.w3schools.com/js/js_function_parameters.asp.
Exercises
- Write a function like |_output| that joins an arbitrary number of
arguments together into a string, with ':' as the separator.
--
// Functions can have trailing arguments omitted, or
// explicitly set to undefined:
function fixed(a1, a2, a3) {
_output("a1:", a1, "a2:", a2, "a3:", a3);
}
// Omitting trailing arguments:
fixed("hi");
fixed("hi", "there");
// Specifying some as undefined:
fixed(undefined, "and", "stuff");
// You can send more than was asked for, but the extras
// are simply ignored.
fixed(1, 2, 3, 4, 5);
// Functions can take arbitrary arguments:
function arbitrary(a1, a2, a3) {
_output("arbitrary a1:", a1, "a2:", a2, "a3:", a3);
// Note that even though we name a1, a2, and a3, they are repeated
// in the arguments sequence. For this reason, functions that use
// it don't typically have any listed in the header.
for (var i = 0, len = arguments.length; i < len; i++) {
_output(" arg " + i + ":", arguments[i]);
}
}
arbitrary("hey", "there");
arbitrary("1", "2", "3", "4", "5");
Functions in JavaScript are **closures**. This is a rather technical term,
but the concept is fairly simple. The idea is that while a function can
access the variables and arguments defined _inside_ of it, it can also
access the variables defined _globally_ and _in the surrounding scope_.
It helps to remember that functions are objects, just like everything
else in JavaScript, and they can therefore be assigned to variables and
passed around and everything. In fact, you can create a function using a
**function expression**: a nameless function that is created on the fly:
var myFuncVar = function(arg1, arg2) {
// do stuff here
};
The code window shows an example of how this works. Though there is not
much code there, it will likely take a little bit of time to fully
understand what is going on. It helps to think of it this way: when a
function is _defined_, JavaScript packages up all of the environment around
that function and allows that function to access and change that
environment any time it is _called_ later on.
Exercises
- Remember the global counter variables we used in the sorting slide a
little while ago? Go back and see if you can move them into a closure
instead. HINT: once you do this properly, you won't have to reset them
before sorting anymore - that will happen inside the sort function (which
will declare the counter, create a closure that uses it, call it, and
return the count).
--
// This function makes a new function and returns it.
function make_a_closure(a, b) {
var inside_counter = 0;
// Here we create a function without a name.
// See how we just say "name = function(args)"?
var f = function(message) {
// We can access this because it's in the surrounding
// scope. What's more, it belongs to the function
// *definition*, so it lives longer than this function call.
inside_counter++;
_output("f (count " + inside_counter + "): " +
"a", a, "b", b, "message", message);
}; // note the semicolon: this is the end of a variable assignment
// "f" is just an object that happens to
// be a function, so we create it here and return it.
return f;
}
var c1 = make_a_closure("a1", "b1");
var c2 = make_a_closure("a2", "b2");
// Watch what happens to the counter.
c1("hi 1");
c1("hi 1");
c1("hi 1");
c2("hello 2");
c2("hello 2");
c1("hi 1");
We now have enough knowledge to do just about anything. Let's start
building a simple game.
Games are interesting because they are fun, but they are also interesting
because they exercise so much of computer programming. You have to accept
input, process information at regular time intervals, display things on the
screen, and handle errors. They have a lot of moving parts!
But we can simplify things quite a bit. Let's start with displaying things
on the screen. Earlier we saw that you can create a |canvas| object and
draw on it. Let's remind ourselves of how that works here. We'll simplify
the process a bit by calling the special |_canvas| function designed
especially for this tutorial (note: if you want to see how it works, just
_output(_canvas) and read up!).
The code window gets the output canvas and draws on it.
Exercises
- Play around with the canvas. Look up the context drawing functions at
http://www.w3schools.com/tags/ref_canvas.asp and try drawing various shapes
of differing colors.
- Try using |save| and |restore|. These allow you to change things like
fillStyle, do some operations, then go right back to what it was before
you started.
- How would you clear the whole canvas using |clearRect|? What parameters
would you pass?
--
var canvas = _canvas(200, 200);
var ctx = canvas.getContext('2d');
ctx.fillStyle = "#cfcfcf"; // red, green, blue in hexadecimal
ctx.strokeStyle = "#ff0000"; // as red as can be, no green or blue
ctx.fillRect(40, 40, 100, 100);
ctx.strokeRect(40, 40, 100, 100);
ctx.strokeRect(50, 50, 60, 60);
If we're going to make a game that accepts input, we'll need to graduate
from this stuffy little output window. We'll want to create a window that is
completely dedicated to the game.
We do this using the DOM, specifically, the |window.open| function:
window.open(url, windowName, spec);
We won't be specifying a URL, and the name of the window doesn't much
matter, but we do want to tell it how big to be, so we'll use the spec:
window.open("", null, "height=200,width=200");
Once we do open a window, we can then access all kinds of stuff within it.
For example, every window has a |document|, and every |document| has a
|body|. We can write to that body, and we can add elements, including a
|canvas| element, to it.
We do exactly that in the code window.
Exercises
- Remove the "innerHTML" setting. What happened?
- When you run the code, click back on this window and run it again.
It opens a new window again! Make it open the *same* window again by
giving the window a name in |window.open|.
- Now when you run the code, click away and run it again, |w| is a
reference to that original window. But it doesn't come to the front when
you do that. Add a |w.focus()| call after |window.open| to make the window
come forward when you run the code again.
--
// Open a window, put some text in it, get rid of margins.
var w = window.open("", null, "height=200,width=200");
w.document.body.style.margin = "0";
w.document.body.innerHTML = "Hello game world!";
// Create a canvas and add it to the new window.
var canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 200;
canvas.style.border = "1px solid black";
w.document.body.appendChild(canvas);
// Draw in the new window's canvas!
var ctx = canvas.getContext('2d');
ctx.fillStyle = "red";
ctx.fillRect(20, 20, 50, 50);
Often we want to make things a little more exciting. For that, randomness
is useful.
To get a pseudo-random number in JavaScript, use the |Math.random|
function. It returns a number between |0.0| (inclusive) and |1.0|
(exclusive). Let's see if we can use it to create random rectangle corners.
Note that we pull a nifty trick to get random colors. Because you can
represent colors as integers in the range 0 (000 hex) to 4095 (fff hex), we
can create a random number in that range and get a valid color.
As a quick reminder, hexadecimal is a number base with the digits |0| through
|f|. There are 16 of them, so |f| is |15| in decimal. If you were counting
in hex, you would count like this:
0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 ...
Thus, every hex digit represents 16 values (in decimal, a digit
represents 10 values). You can assign a value from 0 (black) to 15
(maximum brightness) to each of red, green, and blue to get a color
mixture. Prefix it with "#", and the DOM knows exactly how to display it.
The |toString| method of all Numbers accepts an optional base, which we
give as |16|, indicating hexadecimal.
Note that you can also use 6 hex digits to get finer color control. The DOM
knows how to deal with numbers like #ff0 as well as #ffff00 (two digits per
color).
Exercises
- There is a bug in this code where colors are calculated. Sometimes a
number with only two digits will be produced. The DOM doesn't know how to
deal with that, so it outputs black. Fix it to add zeros to the front of
the color number when it is two short.
- There is another interesting effect in this code: sometimes the
rectangles are printed partially out of bounds on the right or bottom of
the canvas. Change the x and y calculattions so that rectangles are never
out of bounds.
--
// Let's just define these once, this time.
// When making constants this way, it is
// customary to use ALL_CAPS.
var WIDTH = 200;
var HEIGHT = 200;
var RECT_WIDTH = 20;
var RECT_HEIGHT = 20;
var x = WIDTH * Math.random();
var y = HEIGHT * Math.random();
var c = _canvas(WIDTH, HEIGHT);
var ctx = c.getContext('2d');
// Let's mix up the colors, shall we? We can use any
// valid integer from 0 (black) to 0xFFF (white,
// also the maximum color value 4095) to represent
// the RGB color. Since Math.random() returns numbers from 0 to not quite
// 1, we multiply by the maximum color + 1 (0xFFF + 1 = 0x1000).
ctx.fillStyle = "#" + Math.floor(Math.random() * 0x1000).toString(16);
ctx.fillRect(x, y, RECT_WIDTH, RECT_HEIGHT);
With canvases, new widows, and loops, we can start doing animation.
Animation is just displaying a series of images fast enough that our brains
are fooled into seeing motion.
That seems pretty easy, right? You might think that the following would work:
while(game_should_run()) {
draw_stuff();
delay_a_teeny_bit();
}
In DOM JavaScript, however, that won't work, though you are welcome to try
it. The problem is that the loop never "gives up" control back to the
browser, so the browser can't display the changes made to the canvas.
Not all languages and environments have this limitation, but JavaScript in
the browser definitely does. What we need instead is to get the browser to
call on _us_ periodically to draw stuff. We need a **timer**.
There are several ways to get a timer in the DOM:
window.setTimeout(func, duration_milliseconds);
window.setInterval(func, duration_milliseconds);
window.requestAnimationFrame(func);
The first two are older than the last one, and work in more browsers, but
they are discouraged in modern settings because they are somewhat
unreliable. For animation, we want to use |requestAnimationFrame|. We pass
it a function of our own making. That function accepts a "timestamp"
parameter that is a time in milliseconds. The next time the browser
repaints, it calls our function once and promptly forgets about it.
Exercise
- The code in the code window only shows a single rectangle, and the
animation frame is only requested once. Request another animation frame
from within |step| to make it keep generating rectangles.
--
var WIDTH = 200;
var HEIGHT = 200;
var canvas = _canvas(WIDTH, HEIGHT);
var context = canvas.getContext('2d');
// This function just outputs a random rectangle.
function step(timestamp) {
context.fillStyle = "#" + Math.floor(Math.random() * 0x1000000).toString(16);
context.fillRect(Math.random() * WIDTH, Math.random() * HEIGHT, 30, 30);
}
window.requestAnimationFrame(step);
When we made the previous example display lots of rectangles, we didn't
have much control over how often it did that. They just came as fast as
they possibly could. That's nice for a demo, but it won't help much if we
want to make a game. We need more control.
The function registered with |requestAnimationFrame| accepts a timestamp
argument, given in milliseconds. We can use that to get a little more
control of things.
To do so, we'll want to keep track of some state. We'll need to know when
the animation first started, and we'll want to keep track of the next
allowed drawing time. To do this, we'll make use of a new kind of operator:
the *increment-assignment operator* |+=|, e.g.,
a += 10;
This is exactly equivalent to |a = a + 10;|, but is more convenient in some
respects (and it can be made to run a bit faster).
When |step| first runs, it notes that it has never run before beacuse it
doesn't have a |first_draw| time. Then, before drawing, it checks to see
that |timestamp| has passed |next_draw|. If not, it just requests a new
animation frame and returns.
If it has passed the time to draw again, however, then it draws. It also
calculates a new |next_draw| time by adding |DURATION| to the existing one.
This way we |next_draw| marches by one second at a time, forever.
Exercises
- Change the |DURATION| to be faster or slower.
- What happens if you set |next_draw| to |timestamp + DURATION| instead of
just adding |DURATION| to it? HINT: try using |_output(next_draw)| inside
of |step|. See how it changes with the two different strategies.
--
var WIDTH = 200;
var HEIGHT = 200;
var DURATION = 1000; // one rectangle per second
// These keep track of timing so that we get draws as close to 1 second
// apart as possible.
var first_draw = null;
var next_draw = null;
var canvas = _canvas(WIDTH, HEIGHT);
var context = canvas.getContext('2d');
// This function just outputs a random rectangle.
function step(timestamp) {
// First time? Remember this moment, and make sure we draw something.
if (first_draw == null) {
first_draw = timestamp;
next_draw = first_draw;
}
// If we are ready to draw, go ahead and do it.
if (timestamp >= next_draw) {
context.fillStyle = "#" + Math.floor(Math.random() * 0x1000000).toString(16);
context.fillRect(Math.random() * WIDTH, Math.random() * HEIGHT, 30, 30);
// Don't forget to set a new "next_draw" time.
next_draw += DURATION;
}
window.requestAnimationFrame(step);
}
window.requestAnimationFrame(step);
We are using a lot of global variables in our animations. That is fine for
small demonstrations, but will end up being an issue as programs get
larger. If everything you do requires a new little bit of state, you'll
have a ton of these variables. And they all have to have unique names.
Far better is to use a closure to keep these things more compartmentalized.
The code window illustrates this concept. The |step| function is defined inside
of |run_demo|, and therefore it has access to all of the variables defined
inside it.
But outside of |run_demo|, none of these variables exist. Furthermore, all
of the animation functionality is now in one place: you could write this
function, stick it in a library somewhere, and anyone else could easily
call it without having to know exactly what order the steps are to make
things work. Before, we had to know that |step| was an animation function,
and to start things off by using |requestAnimationFrame|. Now, we just have
to call |run_demo|.
This is related to a technique that is used just about everywhere on the
web. If you open up a web site debugger (e.g., by using "Inspect Element"
in the Chrome right-click menu), you can see source files for JavaScript
used by various sites. Many times you will see an anonymous function
declared and immediately called like this:
(function() {
// do lots of stuff with variables
})();
This allows all of the variables defined in that function to not "leak"
into the surrounding scope. We aren't using _precisely_ that technique
here, but the idea is the same.
Exercise
- Try putting _output(t0) after |run_demo|. What happens? Why?
--
function run_demo(width, height, duration) {
var canvas = _canvas(width, height);
var ctx = canvas.getContext('2d');
var t0 = null;
var t_next = null;
// Look, ma! "step" is now a closure!
function step(ts) {
if (t0 == null) {
t0 = ts;
t_next = ts;
}
if (ts >= t_next) {
ctx.fillStyle = "#" + Math.floor(Math.random() * 0x1000000).toString(16);
ctx.fillRect(Math.random() * width, Math.random() * height, 30, 30);
t_next += duration;
}
window.requestAnimationFrame(step);
}
// Start running the demo.
window.requestAnimationFrame(step);
}
run_demo(200, 200, 350);
Let's move this animation to its own window. Remember this little dance?
var win = window.open("", name, spec);
win.focus();
var canvas = document.createElement('canvas');
canvas.width = WIDTH;
canvas.height = HEIGHT;
win.document.body.appendChild(canvas);
This tutorial has packaged that boilerplate into a function called
|_canvas_window(width, height, name)|. You can see how it works by using
|_output(_canvas_window)|. You'll notice that it does a bit more than the
above, including re-using existing elements if they are already there.
It returns an object with some useful members:
- window: the window object either created or brought forward
- canvas: the canvas object to draw on
- context: the context to use for drawing
The code window has the animation in it, but this time using the
|_canvas_window| function.
Exercises
- The rectangles are sometimes drawn partly off of the window. Fix the
random locations to always allow for the width and height of the rectangle
to be completely inside of the window.
- Add another closure called |stop|. Have it set a variable called
|finished| to |true|, and change |step| to only request a new animation
frame if |finished == false|. Then change the call to |run_demo| to get
the |stop| function out of it, and uncomment the line that calls it in
|setTimeout|.
- Try adding one more closure called |start| that calls the first
|requestAnimationFrame| instance and sets |finished| to |false|. Instead
of returning just one closure, return an object that contains both
|start| and |stop|. Note that you are much more on your own here, but you
can definitely do this.
--
function run_demo(width, height, duration) {
var win_info = _canvas_window(width, height);
var ctx = win_info.context;
var t0 = null;
var t_next = null;
function step(ts) {
if (t0 == null) {
t0 = ts;
t_next = ts;
}
if (ts >= t_next) {
ctx.fillStyle = "#" + Math.floor(Math.random() * 0x1000000).toString(16);
ctx.fillRect(Math.random() * width, Math.random() * height, 30, 30);
t_next += duration;
}
window.requestAnimationFrame(step);
}
// Start running the demo.
window.requestAnimationFrame(step);
}
// TODO: var stop = run_demo(...);
run_demo(200, 200, 350);
// TODO: uncomment this after "stop" is returned from run_demo.
// window.setTimeout(stop, 5000);
The |step| function we have been using seems like something we would want
to write over and over again, for every kind of animation we want to do.
Similarly, we keep drawing rectangles, but we do a lot of the same stuff
over and over again. Let's make this a bit simpler by *abstracting* these
concepts into some functions. While we're at it, we'll make it easy to
draw circles.
Doing this kind of thing is called _refactoring_: sometimes code starts to
get messy, and you want to move common patterns out of the way so they can
be compartmentalized and reused elsewhere. It's an important and common
programming practice.
The code window now has three new functions:
animation_loop
fill_rect
fill_circle
If you look at |run_demo| now, it is much more straightforward. You specify
the drawing function and pass it to the animation loop and you're done.
Within |draw| we're just calling simple drawing functions, as well.
Exercises
- Instead of defining a function named |draw| and passing that name into
|animation_loop|, pass an **anonymous** fnction into |animation_loop|
directly, e.g., |animation_loop(function(ts) { /* stuff here */ })|. This
is a very common idiom that you will start to see more of.
- Implement the "once-per-duration" logic from previous slides, but inside
of the |draw| function. Add the |duration| argument back to |run_demo|.
- These functions will be available in future slides as
|_animation_loop|, |_fill_rect|, and |_fill_circle|, respectively.
The implementation of |_animation_loop| also sends the amount of time that
has **passed** to its drawing function, as the second argument. Try it out.
--
// If drawFunc returns false, this loop will stop.
function animation_loop(drawFunc) {
function step(ts) {
// We explicitly check for "false" here because it
// makes this more friendly to use. If you don't
// return *anything*, it is assumed you wanted to
// loop (because undefined !== false).
if (drawFunc(ts) !== false) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
}
function fill_rect(context, x, y, w, h, color) {
// Since this is in a function, we want to
// leave things the way we found them. Nobody
// would expect to call this and have the global
// fill style suddenly changed.
context.save();
context.fillStyle = color;
context.fillRect(x, y, w, h);
context.restore();
}
// This is bit less obvious, and that makes it
// even more sensible to make a function for it.
function fill_circle(context, x, y, radius, color) {
context.save()
context.fillStyle = color;
context.beginPath();
context.arc(x, y, radius, 0, Math.PI * 2, true);
context.fill();
context.restore();
}
var RW = 30;
var RH = 30;
function run_demo(width, height) {
var wc = _canvas_window(width, height);
// Let's create a basic drawing function.
function draw(ts) {
var color = Math.floor(Math.random() * 0x1000000).toString(16);
var x = Math.random() * (width - RW);
var y = Math.random() * (height - RH);
// draw raindrop-like things.
fill_rect(wc.context, x, y, RW, RH, "#" + color);
fill_circle(wc.context, x, y, RW, "#" + color);
return true; // Otherwise it will only run once.
}
// Let's run our animation loop!
animation_loop(draw);
}
run_demo(500, 500);
We have a nice animation loop, and we have simple functions for drawing
circles and rectangles. Let's make something look like it's moving.
To animate something, we need to draw still frames fast enough to trick the
brain into seeing motion. We have the fast-drawing part down, now we just
need to make things behave as though they are moving.
Let's start by making a circle bounce around on the canvas. To do this, we
need to keep track of the circle's current position, we need a notion of
speed, and we have to know when to make it change direction (bounce). To
make this work, we'll create an object that contains information about the
circle.
Exercises
- You probably noticed right away that this just smears a circle across the
canvas. To animate it, you'll need to clear the canvas before drawing a
circle using clearRect. Do that.
- The next thing you might have noticed is that the circle does not bounce
off the left side. Make that happen.
- Make the circle change size over time, following a sinusoidal function.
You could, for example, use the previously-ignored |ts| parameter and
draw the radius as |radius * (Math.sin(ts) + 1) / 2|. Experiment with this.
- You might notice that bouncing doesn't look right, now. Move |radius|
into the circle's state and make sure bounces test against that value.
- Make the circle move in the y direction, as well. You'll need to keep
track of two speeds.
--
function run_demo(width, height, radius, color) {
var canvas = _canvas(width, height);
var ctx = canvas.getContext('2d');
var SPEED = 100;
var circle = {
'x': radius,
'speed': SPEED, // pixels per second
};
// It is customary to use the variable '_' to mean "we're ignoring this".
// Here we're making use of the fact that _animation_loop passes both the
// absolute animation time *and* the time since last animation to us.
function draw(_, delta_t) {
// Time difference is in milliseconds, so we divide by 1000 to get seconds:
var ds = delta_t / 1000;
// Compute the new x position using speed:
circle.x += ds * circle.speed;
_output(circle.x);
// Here we want to detect a collision and "bounce".
if (circle.x >= (width - radius)) {
circle.x = (width - radius);
circle.speed = -SPEED;
}
// All calculated - time to draw it!
_fill_circle(ctx, circle.x, height / 2, radius, color);
}
_animation_loop(draw);
}
run_demo(400, 200, 20, "red");
Animations are fun, and you just made one that looks pretty neat.
Congratulations!
Now it's time to start accepting input from our users, and changing our
animations in response to that input. We'll start by making a simple
drawing program using the mouse.
To listen to events like mouse clicks and key presses, we call
|addEventListener| on the object that wants to receive the event. So, for
the canvas element, we would call something like this:
canvas.addEventListener(
"mousedown",
mouse_down_function);
There are many such events, some for mice, others for keyboards, and still
others for touchscreens. Take a look here for reference (there are a ton of
them):
https://developer.mozilla.org/en-US/docs/Web/Events
When |mouse_down_function| is called, it is given an |Event| object that
has information about the event, like where on the screen it happened.
Let's give this a try! In the code window we respond to the _mousedown_
event by figuring out the location of the mouse and drawing a circle there.
This is a nice overview of |Event| object members:
http://www.htmlgoodies.com/beyond/javascript/events-and-javascript-part-3-the-event-object.html
But, what this does is not all that interesting. The exercises below have
some ideas on how to make it better.
Exercises
- Add a |mouseup| event handler that outputs "up" to the output window.
- Add a |mousemove| event that outputs "move X Y", where X and Y are the
actual coordinates of the mouse location.
- Use |mousedown|, |mouseup|, and |mousemove| to allow drawing to happen
when the mouse is moved, but only when the button is down.
--
function run_demo(width, height, radius, color) {
var wc = _canvas_window(width, height);
var canvas = wc.canvas;
var ctx = wc.context;
canvas.addEventListener('mousedown', function(event) {
var x = event.clientX;
var y = event.clientY;
_output("down", x, y);
_fill_circle(ctx, x, y, radius, color);
});
}
run_demo(500, 500, 20, "blue");
Let's see what it takes to get key events to work for us.
The main keyboard events are
keypress
keydown
keyup
The |keypress| event is simplest to work with, but can sometimes be less
desirable for games, because it works like you're typing (complete with
repeated keys after you hold it down long enough).
Usually you'll want to listen to the |keyup| event, so we do that here.
Note that the code for the spacebar is |32|, so that's what we check for
before launching the ball into space.
Exercises
- What happens if you press the space while the launch is in progress? Can
you fix this so that pressing "space" again pauses the launch?
- The animation keeps running even after the ball exits the screen. Can you
cause the animation to stop once the ball is gone? Remember that in this
tutorial, returning |false| from the drawing function will stop the
animation loop.
--
function run_demo() {
var WIDTH = 300,
HEIGHT = 500,
RADIUS = 20,
SPEED = 100;
var wc = _canvas_window(WIDTH, HEIGHT);
var launched = false;
var y = HEIGHT - RADIUS;
wc.window.addEventListener('keyup', function(event) {
if (event.keyCode == 32) {
launched = true;
}
});
_animation_loop(function(_, delta_t) {
if (launched) {
y -= SPEED * (delta_t / 1000);
}
wc.context.clearRect(0, 0, WIDTH, HEIGHT);
_fill_circle(wc.context, WIDTH / 2, y, RADIUS, "#cccccc");
});
}
run_demo();
We are nearly ready to make a real game that you can play. Now that we know
how to draw with animation frames, change state with time changes, and
accept user input, we can make a more useful game loop than just our little
|_animation_loop|.
The first issue with |_animation_loop| is that it combines two concepts,
and it probably shouldn't. The first concept is that of a ticking clock:
the arguments to the draw function are all about time and the passing of
time. But it is also expected that the draw function actually change
something on a canvas. Let's split these ideas up to make them each simpler
to reason about.
Finally, we can accept user input, but it can sort of come at any time, and
the mechanism for getting it is not very uniform. You may have noticed that
we accept mouse events on the _canvas_, but keyboard events on the
_window_. Let's get those details right once and forget about them later
on. To do that, we'll borrow an idea from the Racket language learning
libraries called *Big Bang*.
Note how the |big_bang| function is documented. There are various styles
for doing this, but they all typically have a lot in common: a simple
description, arguments are listed and defined, and return values are
explained.
Exercises
- Capture the |tick| event and do something interesting with it. You might,
for example, try outputting a "tick" in the output window every second.
- Try replacing your call to |big_bang| with a call to |_big_bang|. That's
right - this is now built in to the tutorial and you can just use it from
now on. You can see what it does with |_output(_big_bang)| - it's very
similar to what we have here, with a few little tweaks to make it more
robust to running code twice in the same window. It also adds the
"interval" configuration, so you can have |tick| called only every so
often.
--
// big_bang opens a window and starts a game loop in it.
// Args:
// width - the width of the new window in pixels.
// height - the height of the new window in pixels.
// config - a map with various configuration information in it, all of which is optional:
// tick - a function(ts, delta_t) to be called at each "tick" of the clock.
// draw - a function(canvas, context) to be called whenever a frame needs to be drawn.
// keyinput - a function(event) to be called whenever key input is received.
// mouseinput - a function(event) to be called whenever mouse input is received.
function big_bang(width, height, config) {
var wc = _canvas_window(width, height);
var tick = config.tick || function() {};
var draw = config.draw || function() {};
if (config.keyinput) {
wc.window.addEventListener("keypress", config.keyinput);
wc.window.addEventListener("keydown", config.keyinput);
wc.window.addEventListener("keyup", config.keyinput);
}
if (config.mouseinput) {
wc.window.addEventListener("mousedown", config.mouseinput);
wc.window.addEventListener("mouseup", config.mouseinput);
wc.window.addEventListener("mousemove", config.mouseinput);
}
_animation_loop(function(ts, delta_t) {
tick(ts, delta_t);
draw(wc.canvas, wc.context);
});
}
// Now let's try it out!
big_bang(400, 300, {
"mouseinput": function(event) {
_output(event.type, event.clientX, event.clientY);
},
"keyinput": function(event) {
_output(event.type, event.keyCode);
}
});
Now that we have the |_big_bang| function, it is convenient to think of a
game as an endless loop where some internal state changes as the clock
ticks and that state is _rendered_ to the canvas when the browser is ready
for it.
To illustrate this idea, let's make a simple not-quite-but-nearly-snake
game. The snake will be a single segment, and you will be able to move it
around. The big questions are these:
- What values do you need to keep track of to represent the world?
- Which of the values change, and which are constant?
- When should the values change?
For this non-snaky snake thing, we'll need
- The position of the head,
- The direction it is moving, and
- The boundaries of the playing field.
The next thing to ask is this: what changes the state, besides the clock?
One thing comes to mind:
- Pressing an arrow key.
Having identified these ideas, head over to the code window and see what is
up there. There is a lot of information in the comments, so don't forget to
read those.
Exercises
- Add the collision detection for the other two walls in |ontick|.
- Can you think of another way of expressing the |keyinput| logic? What if
you created a "map" with the keycodes of interest in it, and the values to
use for |vx| and |vy|?
- Look up the "switch" statement and see how that might help with some of
this code: http://www.w3schools.com/js/js_switch.asp.
- Add food to the game. You will need a food position, and your draw function will
need to also draw the food on the canvas. When does the food appear? When
do you need to create new food? Output "yum!" (or something less lame)
when the snake runs into a bit of food.
- Make the game harder by making the snake move faster.
--
var CELLS_WIDE = 30; // number of cells from left to right
var CELLS_HIGH = 30; // number of cells from top to bottom
var CELL_WIDTH = 15; // width of an individual cell
var CELL_HEIGHT = 15; // height of an individual cell
var WIDTH = CELLS_WIDE * CELL_WIDTH;
var HEIGHT = CELLS_HIGH * CELL_HEIGHT;
function start_game() {
// This represents the snake's head. Hey, it's a start!
var head = {
"x": Math.floor(CELLS_WIDE / 2),
"y": Math.floor(CELLS_HIGH / 2),
"vx": 1, // x velocity (in cells per tick)
"vy": 0, // y velocity (in cells per tick)
};
// When the clock ticks, we advance the snake in whatever
// direction it is already moving, and we check for crashes.
function ontick() {
var new_x = head.x + head.vx;
var new_y = head.y + head.vy;
// Check for a crash, quit if we see one.
if (new_x < 0) {
_output("Crashed into left wall!");
return false;
} else if (new_x >= CELLS_WIDE) {
_output("Crashed into right wall!");
return false;
}
// All set - move the head.
head.x = new_x;
head.y = new_y;
return true; // continue ticking
}
// When a key is pressed, all it does is change the direction of the
// head.
function onkey(event) {
if (event.type != "keydown") {
return;
}
// How do you think I figured out these codes?
// You could look them up in a table, or you could
// do what I did and go back a slide. Running that
// program causes the key numbers to be sent to
// the output window when you type them.
if (37 == event.keyCode) { // Left
head.vx = -1;
head.vy = 0;
} else if (38 == event.keyCode) { // Up
head.vx = 0;
head.vy = -1;
} else if (39 == event.keyCode) { // Right
head.vx = 1;
head.vy = 0;
} else if (40 == event.keyCode) { // Down
head.vx = 0;
head.vy = 1;
}
}
// All this has to do is draw the snake on the screen. We'll use
// poisonous green circles for the (one) segment(s) we have.
function ondraw(canvas, context) {
// Find the pixel coordinates from the current head cells.
// We do this by remembering that each cell is
// CELL_WIDTH x CELL_HEIGHT pixels. Since we use circles,
// We need to find the *center* of those cells.
var x = head.x * CELL_WIDTH + CELL_WIDTH / 2;
// Equivalent logic - the power of algebra:
var y = (.5 + head.y) * CELL_HEIGHT;
context.clearRect(0, 0, WIDTH, HEIGHT);
_fill_circle(context, x, y, Math.min(CELL_WIDTH, CELL_HEIGHT) / 2, "green");
}
_big_bang(WIDTH, HEIGHT, {
"tick": ontick,
"interval": 200, // 1/5 second
"draw": ondraw,
"keyinput": onkey,
});
}
start_game();
Now that we have the basics of motion and food in the snake game, it's time
to figure out how to represent an entire snake.
The point of this tutorial is not actually to make a snake game, but to
learn some interesting and hopefully useful principles. The principle to
learn here is how to figure out what state your program needs, and how to
convert that state into interesting output and interaction. The |_big_bang|
is a nice start (see what I did there?), and now we just have to figure out
state and interaction.
This is the most complex code we have worked with yet. Take a good close
look at it after reading through the tutorial here.
To make a snake, what we really want is a list of positions, starting with
the head. Let's change our snake definition accordingly:
var snake = {
"vx": 1,
"vy": 0,
"segments": [{
"x": Math.floor(CELLS_WIDE / 2),
"y": Math.floor(CELLS_HIGH / 2),
}]
};
Basically, we kept global snake properties like velocity in the main snake
object, and we moved segment-level properties into the segments list. The
snake starts with one segment.
Now, with a snake like this, what happens when the it moves? Obviously the
head moves, but then everything has to follow behind it until it gets
dragged into a new direction. Everything sort of keeps moving toward its
next location. There are lots of ways to do this (including adding a new
head and removing the tail), but we're going to pick "changing all of the
locations". We start at the back of the array and change every segment to
have the position of the one before it. Then we move the head according to
the directions indicated.
Exercises
- Figure out what the code is doing now. There are now multiple segments,
and to mix things up, we have changed the way that the snake moves (by
adding a head and removing the tail - see the |unshift| Array method in
the documentation).
- You might notice after playing the game for a bit that you can't run into
yourself. Fix the code to test for a self-crash.
- Change the |new_food| function to not allow food to appear on *any* part
of the snake.
- Add detection of the *Escape* key to end the game. HINT: you can find the
keycode for this by setting your keyinput listener to
|_output(event.keyCode)|.
- Use the |context.fillText| method to write the score onto the playing
field as the game progresses. Look up the |context.globalAlpha| field to
make the text semi-transparent so it doesn't interfere with the game.
- You may have noticed that two moves in rapid succession can sometimes
confuse this game; it will ignore the first of the two. Fix the |ontick|
code *and* the |onkeyinput| code to keep these from getting lost. HINT: you
will need to keep track of more than one move - use an array.
- Make the "Crashed" text appear in the game window instead of the output
window.
- The game starts out really quickly. Add a 3-2-1 countdown timer before
the snake starts to move. This will require you to keep track of some
more state, such as whether the countdown is over, and which count it is
on. Note that it is also going to move more slowly that game frames, so
you will need to do something to ignore some of the in-between ticks.
--
var CELLS_WIDE = 30, CELLS_HIGH = 30,
CELL_WIDTH = 15, CELL_HEIGHT = 15;
var WIDTH = CELLS_WIDE * CELL_WIDTH,
HEIGHT = CELLS_HIGH * CELL_HEIGHT;
function start_game() {
var snake = {
vx: 1, vy: 0,
segments: [{x: Math.floor(CELLS_WIDE / 2), y: Math.floor(CELLS_HIGH / 2)}],
};
// To create new food, we need to make sure it doesn't
// land on the current head.
function new_food() {
var head = snake.segments[0];
var x, y;
do {
x = Math.floor(Math.random() * CELLS_WIDE);
y = Math.floor(Math.random() * CELLS_HIGH);
} while (x == head.x && y == head.y);
return {x: x, y: y};
}
var food = new_food();
// When the clock ticks, we advance the snake in whatever
// direction it is already moving, and we check for crashes.
function ontick() {
var head = snake.segments[0];
var new_x = head.x + snake.vx;
var new_y = head.y + snake.vy;
if (new_x < 0 || new_x >= CELLS_WIDE ||
new_y < 0 || new_y >= CELLS_HIGH) {
_output("Crashed");
return false; // game over
}
// Add the new head.
snake.segments.unshift({x: new_x, y: new_y});
if (new_x == food.x && new_y == food.y) {
// Make new food.
food = new_food();
} else {
// Didn't eat - don't grow - take the end off.
snake.segments.pop();
}
return true; // continue ticking
}
function onkey(event) {
if (event.type != "keydown") {
return;
}
if (37 == event.keyCode) { // Left
snake.vx = -1;
snake.vy = 0;
} else if (38 == event.keyCode) { // Up
snake.vx = 0;
snake.vy = -1;
} else if (39 == event.keyCode) { // Right
snake.vx = 1;
snake.vy = 0;
} else if (40 == event.keyCode) { // Down
snake.vx = 0;
snake.vy = 1;
}
}
function ondraw(canvas, context) {
context.clearRect(0, 0, WIDTH, HEIGHT);
_fill_rect(context,
food.x * CELL_WIDTH, food.y * CELL_HEIGHT,
CELL_WIDTH, CELL_HEIGHT, "red");
for (var i = 0; i < snake.segments.length; i++) {
var segment = snake.segments[i];
var x = (.5 + segment.x) * CELL_WIDTH;
var y = (.5 + segment.y) * CELL_HEIGHT;
_fill_circle(context, x, y, Math.min(CELL_WIDTH, CELL_HEIGHT) / 2, "green");
}
}
_big_bang(WIDTH, HEIGHT, {
"tick": ontick,
"interval": 100,
"draw": ondraw,
"keyinput": onkey,
});
}
start_game();
You can do a lot of things with closures, and we have seen some very
powerful uses of them. They are powerful enough, in fact, that you could
just stick with them and be quite productive. They are a favorite tool of
programmers of many languages.
We won't get into it in any great depth in this tutorial, but JavaScript
also supports the notion of a **class**. Put simply, a class is a blueprint
for creating objects that have both data and functions in their fields.
The code window demonstrates an example of classes in JavaScript; here we
defined |Pos|. The first thing to notice is that this takes up a lot more
code than what we had before to accomplish the same thing. Why, then, is
this something people do? The answer is typically _encapsulation_ and
_reuse_. By defining things in this way, you can hide some of the
commonly-invoked functionality behind a nice uniform interface and allow
these ideas to be reused. This obviously makes a lot of sense for |Pos|
because it has a lot of applications.
To create an **instance** of a class, you use |new|:
var p = new Pos(20, 30);
var d = new Pos(0, 1);
If you look carefully at the code window, you will see references to |this|
scattered throughout. The |this| function variable is magical: it appears
when you are inside of a function called with |new| or a function that is
called from an _instance of a class_. In the above example, this means that
when you call |p.add|, the |this| variable is equal to |p|.
Another reason to create classes is _polymorphism_. This is when you have
an object of unknown type, and you want to do something with it anyway,
like convert it to a string. We see an example of exactly that, here,
because we define the method |toString| on our class. JavaScript, when it
is trying to convert an object to a string, will try to find and call
|toString|. If it is there, it uses it, otherwise it creates a default
string.
It is probably best for now to not worry too much about the special
|prototype| field. Just note that all functions in JavaScript have a
|prototype| field assigned to them, and that adding functions to it is how
you make object methods that have a reasonable |this| in them.
A final note is in order regarding |this|: it is convenient when working
with classes and their instances (objects), but it is unexpectedly magical
in very unfortunate ways. The most common way that it can bite you is when
it gets overwritten by an event handler that is calling your function. For
this reason, when creating event handler functions, don't _ever_ do this:
window.addEventListener("close", myobj.method);
If you do, you will find that |this| inside of |method| is not |myobj|
anymore, but something entirely different. It's a major wart on the face of
JavaScript, and one that trips people up quite frequently.
Instead, **always create a new function that calls your method** like this:
window.addEventListener("close", function(event) {
myobj.method();
});
It doesn't matter whether you create the function inline as above, or
outside of the call as we have done elsewhere. So long as the function does
not rely on |this|, it can be safely used as a callback in this way.
Exercises
- Remove the |toString| method (or rename it to, e.g., |_toString|) and see
how the output changes.
- Try calling |Pos| without |new| and see what happens when you try using it.
- Create your own |Snake| class that accepts a position and direction
(both |Pos| instances), and that has methods to get the |nextPos|, to
|eat|, and to |move|. How would having such a class make your big bang
code look?
--
// It is typical to name classes with an initial capital letter
// to set them apart from other functions.
// Class Pos: holds x and y coordinates.
function Pos(x, y) {
this.set(x, y);
}
// Pos.toString: make a string representation of this Pos.
Pos.prototype.toString = function() {
return "Pos(" + this.x + ", " + this.y + ")";
}
// Pos.set: set the x and y all at once.
Pos.prototype.set = function(x, y) {
this.x = x;
this.y = y;
};
// Pos.add: add another position to this one.
Pos.prototype.add = function(other) {
return new Pos(this.x + other.x, this.y + other.y);
};
// Pos.copy: create a New pos the same as this one.
Pos.prototype.copy = function() {
return new Pos(this.x, this.y);
};
var p = new Pos(20, 30);
var q = new Pos(-1, -12);
_output(p, "+", q, "=", p.add(q));
This particular tutorial is done. Congratulations! You can do real and
interesting work using JavaScript. If you aren't perfectly comfortable with
it, still, take heart: it takes more than one pass through material like
this to really get comfortable.
It would be a great idea to go over it again from the beginning: not only
will you get more out of it the second time around, you will also get a
picture of how much more you know than when you started!
Once you feel comfortable with this material, it is time to graduate to
writing your own JavaScript applications in your own web pages, a concept
that is beyond the scope of this language tutorial. Fortunately, there are
a lot of good materials out there, all of which will make more sense after
going through this tour.
Here are some parting shots, intended to get you started on the right path
as quickly as possible.
- Source code for this tutorial is available at http://github.com/shiblon/jstour.
It is all JavaScript and HTML. If you want to know how things work, check
it out. It uses **AngularJS**, **jQuery**, and **CodeMirror** (for the
nifty colored code window). To get started, check out the |static/index.html|
file and note all of the different |<script>| tags. The main
functionality is in |static/js/controllers.js|.
- Pretty much everyone uses helper libraries to manipulate the DOM. Even
modern browsers are all different, and it makes sense to use something
written with the express intent of hiding those differences from you.
Libraries like **jQuery** (highly recommended) make doing just about
everything with your web page easier. There are others like it, but it
is probably the most common and comprehensive: http://jquery.com.
- Everyone uses libraries for games, too. Check out http://impactjs.com,
http://craftyjs.com, http://gameclosure.com, or http://isogenicengine.com
if you really want to build games in JavaScript. For free images and
sound, see http://lostgarden.com and http://freesound.org. The internet
is full of wonders.
- Speaking of libraries, **AngularJS** is top notch. It makes building web
sites like this one a whole lot easier, and is well worth the time
investment to learn. Additionally, learning something like AngularJS is a
nice way to get your feet wet with web pages in general, since the
tutorial goes over basic web page construction and helps you fit together
HTML and JavaScript in order to get started: http://angularjs.org/.
- Once you have learned how to wire HTML and JavaScript together, head over
to http://jsfiddle.net and see what they have to offer there. It's not
only a great way to play around and experiment, it is also a great way to
get other people to help you: post a link to your fiddle and they can
suggest changes and offer advice.
- Never forget this little tidbit: once you learn one programming language,
it is *far* easier to learn another one. JavaScript shares a syntactical
heritage with other mainstream languages like C, C++, and Java. If you were
to want to continue learning, you might try Python - a powerful
langauge that is also easy and quick to learn. In fact, you can get a
similar tour for it here: http://github.com/shiblon/pytour.
- If you want to learn a completely different way of thinking about
programming, learn a LISP variant like Racket: http://racket-lang.org.
It will expand your mind. Download, install, then head over to
http://www.ccs.neu.edu/home/matthias/HtDP2e/index.html.
That's about it! Feel free to fiddle in the code window to your heart's
content. It has been left blank just for you. All of the _-prefixed
functions are available to you from previous chapters. Have fun!
--