Hello %name!
A new mission is coming up.
Goal: power over the Internet*
Side effect: this page will self destruct after accomplishment
%name, your mission is not easy. You need to write JavaScript. But you are a smart student. Surely you can solve each task.
Whenever you solve a puzzle, the next one becomes available. In each step your code is tested against a specification. If your code fails a test, you have to go back and fix it.
Below is a text area. You put your code there. Let's start simple. Write 'Hello World' there, including the single quotes.
You passed your first test. You can read instructions and follow them. Great.
You wrote an expression that equals the string 'Hello World'.
Now, you have to write a function that returns
'Hello World'. A function looks like this:
function funkyFunctionName() {
return 'Pan Galactic Gargle Blaster';
}
This cute little function is called 'funkyFunctionName' and returns the string 'Pan Galactic Gargle Blaster'.
function is a keyword to define a function.
It is followed by the new function's name
— funkyFunctionName.
The round brackets () can contain parameters.
You will learn about them later.
The curly brackets denote a block of code which is executed
when the function is called.
In this example, the code block has only one statement
to execute.
It returns the string
'Pan Galactic Gargle Blaster'
to whatever calls this function.
Each statement needs a semicolon ; at the end.
Remove your 'Hello World' from the text field
and replace it with the code above.
Cool. Copy and paste works.
Let's do something useful now.
Let's create an HTML element.
The most general element is a
div element.
You can create a div element with the following code:
document.createElement('div')
Replace 'Pan Galactic Gargle Blaster'
with the code above.
Be exact, an apostrophe at the wrong place
or a missing semicolon can cause everything to fail.
But if you get it right,
your function will create a div element
and return it.
Excellent.
Your function is doing something useful.
But it still has a silly name.
Rename it to createDiv.
Hurray, we got a div element. But where is it? Well, at the moment, our element is empty, has zero size and is therefore not visible.
We want to change that.
But before we can do something with this element,
we have to give it a name;
we have to assign it to a variable.
Let's call it divElem:
var divElem = document.createElement('div');
Each HTML element has a style attribute. Its style attribute defines how this element should be displayed. All possible style attributes are defined in the Cascading Style Sheets language. For example, we can give it a width and a height:
var divElem = document.createElement('div');
divElem.style.width = '30px';
divElem.style.height = '30px';
Create a div element like in this example. And give it some more style attributes:
position: fixedtop: 0%left: 0%width: 1%height: 1%backgroundColor: red
Remember to return the changed variable
divElem at the end of the function.
Code after a return statement is not
executed.
It's still not visible?
There is one last thing missing to display a newly
created HTML element.
You have to attach it to another element that is
already there.
In the case of an HTML web site,
there is always the body element.
Attach it with this additional line:
document.body.appendChild(divElem);
Finally! Do you see a little red rectangle in the top left corner? That was created by you. Now, let's move it.
Keep your function createDiv and
add a new function called moveElementTo below it.
But this one has three parameters:
element: the element to movex: the new x coordinate of the elementy: the new y coordinate of the elementThe parameters are listed with commas in the brackets after the function name:
function createDiv() {
...
}
function moveElementTo(element, x, y) {
...
}
The parameters x and y
will be simple numbers
like 1, 2 or 35.
Given the parameter x = 5,
we want the element to be 5% from the left window side:
element.style.left = x + '%';
The expression x + '%' is taking whatever is
in the parameter x and appending a
% character. The result is assigned to the
left style attribute.
Put that code into your new function and add another line
for the y parameter.
Very Good. We are coming closer to our target.
The test program gave your program an
x and a y coordinate.
These coordinates are the place
where your rectangle is right now.
Our goal is to move things. Let's move your rectangle a bit further right. Insert this line at the top of your function:
x = x + 1;
That statement says from left to right:
"Assign to the variable x the value of
x + 1."
Whatever you get for x,
you add 1 to it.
You will get more familiar with this kind of thinking.
You are moving a rectangle right next to given coordinates. But on this level, the test program won't give you the coordinates any more. You are getting only the element.
You have to get the current coordinates of the rectangle from its style attributes:
var x = +element.style.left.replace('%', '');
You can probably guess what replace is doing.
The + marks the result as a number,
not a string of characters.
If x would be the string '44',
then x + 1 is '441'.
But if x is the number 44,
then x + 1 becomes 45.
Next level. You are moving a rectangle to the right. We want you to move it to a target. We want you to change your function parameters:
function moveElementTo(element, target) {
...
}
Define the variables targetX
and targetY.
If the target is on the right side of element,
then move it right.
Such a condition is programmed with an if
statement:
var targetX = +target.style.left.replace('%', '');
if (targetX > x) {
x = x + 1;
element.style.left = x + '%';
}
You guessed your next task?
If the target is below your element, move your element
one step down: y = y + 1.
Add the same logic for the case that the target is on
the left or above your element.
But do only one of these actions.
You can use the keyword else for that:
if (targetX > x) {
...
}
else if (targetX < x) {
...
}
else if (targetY > y) {
...
}
else if (targetY < y) {
...
}
Great.
You can move a rectangle towards a target step by step.
You are ready for more control.
For the previous tasks,
the test program used your createDiv()
to create your rectangle and a target rectangle.
It placed the target somewhere
and called moveElementTo(element, target)
to move your rectangle towards the target.
Add a new function called main
without any parameters.
In that function, create the target first.
Use your defined function for that:
var target = createDiv();
Place the target at the coordinates x = 3
and y = 5.
Create another rectangle afterwards and assign it to the
variable element.
Last, call your moveElementTo function once
to move the rectangle one step closer to the target.
You might want to give the two rectangles different colours.
This is all following a big plan.
If you got here, you are probably ready for the next
cool feature of JavaScript.
You wrote several functions so far.
They have names like moveElementTo
or main.
But you can define anonymous functions as well:
function () {
// do something
}
The question is:
How can you call a function that has no name?
And the answer is:
You can assign it to a variable
and call it by the variable's name.
Why would you do that?
You can pass the variable to another function.
That other function can call your given function
without knowing its name or what it actually does.
Sounds complicated?
It is.
But we need it for the next task.
Create an anonymous function
and assign it to a variable called action:
var action = function () {
// do something
}
Add that code to your main function.
Move your call of moveElementTo inside
the anonymous function.
And last, return the variable action
at the end of your main function.
Now comes a fun part. We will see things moving. If we call your anonymous function multiple times, it will move the rectangle multiple times. But it will be so quick that you can't see it moving. You just see where it ended up after all movements. We have to give your eyes some time in between.
JavaScript has a function to execute one command periodically.
It's called setInterval and it takes
two parameters:
func: a function like the anonymous
function you defined. It will be executed in each
interval.delay: the time in milliseconds between
the calls of func.
window.setInterval(action, 200);
Add that line to your main function.
Keep always in mind that the return
statement is always the last to be executed
in a function.
Therefore, you have to insert the new line before that.
You are ready for the real world stuff. Targets can be anywhere. We will place it randomly on the screen.
JavaScript has a function to generate a random
floating-point number
between 0 and 1.
var random = Math.random();
But our coordinates are between 1 and
100.
You have to multiply the given number by 100
to get the scale right.
The result will still be a floating-point number like
70.5226894644453.
To keep it simple, we should round that number to a close
integer.
Use the function Math.floor for that:
var randomCoordinate = Math.floor(random * 100);
Add another function called randomCoordinate.
It has no parameters and returns an integer between
0 and 100.
Use your new randomCoordinates function in
the main function
to get a random x and
a random y coordinate for both rectangles.
Assign these to the style properties
to place them randomly on the screen.
It's time to reveal a part of our plan. Your tasks will create a simulation of a video game known as snake. You already created a snake, one rectangle, that is looking for food, the target rectangle. We will call these two rectangles snake head and target.
Once the snake head reaches the target, the snake should become longer and the target should appear somewhere else. The additional rectangles will be called snake tail.
Look at the function
moveElementTo(element, target).
It checks if the target is on the right, left, under or
above the snake.
If none of this is the case,
then the snake has reached the target.
After all these lines with if
and else we can add a last
else block.
...
} else {
// code for letting the snake grow and moving the target
}
How does the snake grow? Create a new rectangle with exactly the same properties as the snake head. JavaScript has a handy shortcut for that.
var tail = element.cloneNode();
The new tail element has to be appended to the document's body again. And placing the target at new random coordinates should be no problem for you any more.
You probably noticed that the snake tail is not moving yet.
We need some logic to move it with the snake head.
The snake tail needs to move every time the head moves.
We can do that at the top of the
moveElementTo function.
But we can do that only if the snake has a tail already.
if (element.tail) {
element.tail.style.left = element.style.left;
element.tail.style.top = element.style.top;
}
That was one step ahead.
We use element.tail here,
but we have to assign that variable to the snake before.
Look further down, where the tail is created.
var tail = element.cloneNode();
element.tail = tail;
You have to change your code accordingly or adapt
the example and use element instead.
Furthermore, you might wonder why we have to add this line.
When you create a new tail and assign it to the variable
tail,
this variable is only available in this block of code.
The next time moveElementTo is called,
the tail variable is not there any more.
Only if the snake reaches the target again,
you create a new tail variable
with a new rectangle.
In order to remember that tail element, we have to store
it somewhere.
In this example, we store it as attribute of the
element variable.
The moveElementTo function is given the
element variable as first argument
and we can access its tail attribute as soon
as the snake reached the target and the attribute was assigned.
One tail element is following the snake head.
But only the newest one.
When a new tail element is created,
it is stored as tail attribute of the
snake head element.
But then we can't access the old tail anymore,
which was stored as tail attribute of the
snake head element before.
You can solve that with a chain of tail elements.
Imagine that each tail element has a
tail attribute.
The snake head has then a tail attribute
storing the newest tail element.
The newest tail element has a tail attribute
that stores the second newest tail element.
The the second newest stores the third newest and so on.
All you need to do is to store the old tail element if the snake head has already a tail element. That old tail element has already a reference to the next older one.
var tail = element.cloneNode();
if (element.tail) {
tail.tail = element.tail;
}
element.tail = tail;
That's a bit mind blowing. This kind of data structure is called a linked list.
Here comes the next mind blowing thing. All the tail elements are stored in this chain, in which each element knows the next one. But still, only the newest tail element is moved.
In order to move the whole tail,
add a new function called moveTail.
It has one parameter element.
This function contains the code for moving
one tail element like before.
function moveTail(element) {
if (element.tail) {
element.tail.style.left = element.style.left;
element.tail.style.top = element.style.top;
}
}
That is the same code used in the
moveElementTo function.
Replace that code with a simple call of
moveTail(element).
Nothing won so far.
We moved that code into its own function.
But now, one line more does the trick.
Look at the moveTail function.
It takes an element and moves its tail
to the element's position.
Do the same for the tail's tail.
Inside of the if block,
just before the style attributes are copied,
insert a line calling moveTail(element.tail).
Whenever you call moveTail(element)
and that element has a tail,
moveTail will be called with the element's
tail as argument.
That tail has its own tail attribute,
so that moveTail
will call itself with the tail's tail as argument
and so on.
That could be an infinite recursion
and ends as soon as one element in that chain
does not have a tail anymore.
In that case, the moveTail function does
nothing.
In the end, moveTail will be called for each
tail element,
placing it at the same position as its previous element.
Fortunately, the previous element is moved afterwards
so that the tail elements don't end up at the same place
together.
This little snake simulation is just
a small demonstration of your new JavaScript skills.
There is a lot more to explore.
Your final task is to add a simple line at the beginning of
of the main function:
document.body.innerHTML = '';