# Stage 4

## Stage 4 Overview

In Stage 4 we'll work on our walls a bit more.

We need to make sure that if our flappy square runs into them then we end the game.

It would also be good to randomize how often they appear and how big the hole is that the flappy square needs to fly through.

## Lesson: Collision Detection

Collision detection is very common in games. You don't want your characters running through walls like our flappy square currently is.

In order to detect the collision with something else in the game you need to compare the position and size of everything in the game to make sure nothing overlaps. Unfortunately it takes quite a few calculations to check every object (and you have to check each side of the object) against every other object. Luckily the computer is very good at making these calculations so we can easily make these calculation each frame without slowing the animation down.

In this example we'll show a simple collision against a wall. Since we are only moving right we only need to check to see if the right side of the square overlaps with the left edge of the wall at all.

This requires a simple check:

``````if (box.x + box.size >= wallPosition) {
... end the animation ...
}``````

This is checking to see if the right side of the box (box.x + box.size) has move passed the left side of the wall (its "x" coordinate). If it has then we know a collision has taking place.

Quick Reference:

Message Log

## Challenge 1

So now we're going to take our code from Stage 3 and we're going to track whether our flappy square runs into a wall.

Collision detection is one of the more complicated problems you'll deal with when developing a game. The lesson above demonstrates how complex the calculations are. So take your time with this challenge and don't get discouraged. It's hard stuff.

Luckily because our flappy square is flying constantly to the right we don't need to worry about collisions to the left. This simplifies things a bit. When the flappy square is in the same "x" coordinates as a wall we need to check to see if the flappy square is passing through the hole in the wall.

In each frame we'll find the wall that the flappy square is flying through (if any) and make sure it is not flying through any of the "wall" area.

So, for example, if we wanted to check whether flappy square had passed beyond the right side of a wall (really the right side of the wall has passed the stationary flappy square) we could do something like this:

``````var left = square.x;
var wallRight = wallX + wall.width;
if (left > wallRight) {
... right side of wall has passed the left side of the flappy square ...
}``````

We'll also need to keep track of the positions of each wall to find the wall the flappy square is flying through.

You can do this by recording the "x" coordinate of each wall in a variable:

``````var wall = {
...
positions: []
}

...

wall.positions.push(wallX);``````

Then, you can iterate through these positions like so:

``````for (var i=0; i < wall.positions.length; ++i) {
var position = wall.positions[i];
}``````

You just need to be careful not to store the same wall multiple times. You may want to store values first, only storing the positions of new walls, and then draw all of the walls.

This is a for loop and you can use it to perform an action using each of the positions in your `position` array.

In the example code below we find the wall that the flappy square is passing through and check to see that the flappy square is above the bottom of the top part of the wall (not going through the hole). If it is then we call the method, endGame(), that we currently call from checkBoundary() to end the game.

``````var left = square.x;
var right = square.x + square.size;
var top = square.y;
var bottom = square.y + square.size;

for (var i=0; i < wall.positions.length; ++i) {
var wallLeft = wall.positions[i] - distance;
var wallRight = wallLeft + wall.width;

if (wallLeft > right) continue;
if (wallRight < left) continue;

var topWallBottom = wall.height;
if (top < topWallBottom) {
endGame();
}
}
``````

Remember this is only checking if the flappy square is hitting the top portion of the wall. You'll need to ensure it isn't hitting the bottom portion as well.

It's a bit complicated. Try to imagine each interaction (e.g. the square moving to the right and running into a top wall) and figuring out how you would know that the right side of the square has connected with the left side of the wall while the square is above the bottom of the wall (for the top part of the wall).

Yea, it's complicated.

Take it slowly and try to work it out.

In the end your flappy square should behave like the example provided at the beginning of this description.

Quick Reference:

Previous Challenge: View your code from Stage 3 Challenge 5 to use on this challenge.

Code Missing: You have not yet entered any code in to the previous challenge: Stage 3 Challenge 5
Stage 3 Challenge 5
Message Log
##### Canvas (your drawing will display here)

A Solution: Here's the code I wrote to complete this challenge. View One Possible Solution

var canvas = document.getElementById('flappy_square_stage4_challenge1'); var context = canvas.getContext('2d'); var interval; var distance = 0; var gravity = 0.5; var boundary = { minX: 25, minY: 25, width: 425, height: 275 }; var square = { x: 25, y: 75, size: 20, yVelocity: 0, jump: -8 }; var wall = { spacing: 125, width: 50, height: 100, positions: [] }; function drawBoundary() { context.strokeRect(0, 0, boundary.width, boundary.height); } function drawSquare() { context.fillRect(square.x, square.y, square.size, square.size); } function drawWall(x) { context.fillRect(x, 0, wall.width, wall.height); context.fillRect(x, boundary.height - wall.height, wall.width, wall.height); } function drawWalls() { var wallX = distance - wall.width; if (wall.positions.length > 0) { wallX = wall.positions[wall.positions.length - 1]; } else if (wallX < wall.spacing) { wallX = wall.spacing; } while (wallX < canvas.width + distance + wall.width) { if (wallX % wall.spacing === 0) { wall.positions.push(wallX); } wallX += 1; } for (var i=0; i < wall.positions.length; ++i) { var position = wall.positions[i]; drawWall(position - distance); } } function flap() { square.yVelocity = square.jump; } function adjustPosition() { distance += 2; square.yVelocity += gravity; square.y += square.yVelocity; } function clearBoundary() { var maxX = boundary.minX + boundary.width; var maxY = boundary.minY + boundary.height; context.clearRect(0, 0, canvas.width, boundary.minY); context.clearRect(maxX, 0, canvas.width - maxX, canvas.height); context.clearRect(0, maxY, canvas.width, canvas.height - maxY); context.clearRect(0, 0, boundary.minX, canvas.height); } function checkBoundary() { if (square.y >= boundary.maxY) { endGame(); } } function checkWalls() { var left = square.x; var right = square.x + square.size; var top = square.y; var bottom = square.y + square.size; for (var i=0; i < wall.positions.length; ++i) { var wallLeft = wall.positions[i] - distance; var wallRight = wallLeft + wall.width; if (wallLeft > right) continue; if (wallRight < left) continue; var topWallBottom = boundary.minY + wall.height; var bottomWallTop = boundary.maxY - wall.height; if (top < topWallBottom || bottom > bottomWallTop) { endGame(); } } } function endGame() { context.font = "20px serif"; context.textAlign = 'center'; var xCenter = boundary.width / 2; var yCenter = boundary.height / 2; context.fillText('Game Over', xCenter, yCenter); pauseAnimation(); } function programSteps() { context.clearRect(0, 0, canvas.width, canvas.height); adjustPosition(); context.save(); context.translate(boundary.minX, boundary.minY); drawBoundary(); drawSquare(); drawWalls(); checkWalls(); checkBoundary(); context.restore(); clearBoundary(); } function runProgram() { interval = setInterval(programSteps, 80); } canvas.addEventListener('click', flap); // The following code is provided for you. // It creates an eventListener that listens // for the canvas to come into "focus", which // happens when you click on it. // This allows us to stop and start each individual // animation on this whole page separately. function startAnimation() { runProgram(); } function pauseAnimation() { clearInterval(interval); } canvas.addEventListener('focus', startAnimation); canvas.addEventListener('blur', pauseAnimation); canvas.focus();

## Lesson: Randomization

Our game isn't very interesting with every wall looking the exact same.

Randomizing the spacing between the walls, and the location and size of the hole in the wall would make the game more interesting and challenging.

In this lesson we look at creating a drawing using random numbers.

We'll create a simple cityscape using rectangles that are different widths and heights drawn next to each other.

The `Math.random()` method allows us to randomize this process so if you hit "Run" multiple times you'll see the randomly generated "buildings" change each time.

So if we wanted to generate a random number between 40 and 200 for our building height we can do something like this:

``````40 + Math.floor(Math.random() * 160)
``````

The `Math.random()` generates a random decimal between 0 and 1 (such as 0.8). We multiply that decimal by the maximum number we want (in this case 160 or 200 - 40) to get a number between 0 and that maximum number.

`Math.floor()` then rounds that number down (for example from 129.83 to just 129) and we add 40 to give us a random integer between 40 and 200.

Quick Reference:

Message Log

## Challenge 2

Our game landscape is a little repetitive. Each wall is equally spaced apart and the holes through the walls are all the same size and in the same position. We need to mix it up a bit more.

Let's take our code from challenge 1 and randomize both the spacing of the walls and the size and position of the holes.

Use the `Math.random()` method to produce a random decimal between 0 and 1. Then multiply that decimal by a larger number to create a random number between 0 and that number. Use `Math.floor()` to produce an integer. Then you can add to that randomly produced integer to change the range (for example add 40 to go from 0 - 160 to 40 - 200).

This looks something like this:

``40 + Math.floor(Math.random() * 160)``

If you run this multiple times you'll get values ranging between 40 and 200.

Use something similar to position walls between 100 and 200 pixels apart.

You also need to keep track of where you position the walls so that they don't move around randomly as you draw each frame. Make sure that your wall collision detection works properly with their new random positions.

This is harder than it sounds when you're dealing with a moving background. You'll need to position enough walls so that as the background moves the walls keep coming, indefinitely.

There are many ways to do this, but I'd suggest you position walls until at least one wall is outside the bounds of the canvas. Once that wall is within the bounds of the canvas then you position the next wall off the canvas. If you continue doing this then you'll always have one wall outside of the bounds of the game ready to come in to view.

Let's also widen the hole in each wall so that we can easily fly through them and observe the random spacing between our walls more easily. Make the height of the walls 75 instead of 100.

In the end your flappy square should behave like the example provided at the beginning of this description.

Quick Reference:

Previous Challenge: View your code from Stage 4 Challenge 1 to use on this challenge.

Code Missing: You have not yet entered any code in to the previous challenge: Stage 4 Challenge 1
Stage 4 Challenge 1
Message Log
##### Canvas (your drawing will display here)

A Solution: Here's the code I wrote to complete this challenge. View One Possible Solution

var canvas = document.getElementById('flappy_square_stage4_challenge2'); var context = canvas.getContext('2d'); var interval; var distance = 0; var gravity = 0.5; var boundary = { minX: 25, minY: 25, width: 425, height: 275 }; var square = { x: 25, y: 75, size: 20, yVelocity: 0, jump: -8 }; var wall = { width: 50, height: 75, positions: [] }; function drawBoundary() { context.strokeRect(0, 0, boundary.width, boundary.height); } function drawSquare() { context.fillRect(square.x, square.y, square.size, square.size); } function drawWall(x) { context.fillRect(x, 0, wall.width, wall.height); context.fillRect(x, boundary.height - wall.height, wall.width, wall.height); } function drawWalls() { var wallX; if (wall.positions.length > 0) { wallX = wall.positions[wall.positions.length - 1]; } else { wallX = boundary.minX + 125; wall.positions.push(wallX); } while (wallX < canvas.width + distance + wall.width) { wallX += (100 + Math.floor(Math.random() * 100)); wall.positions.push(wallX); } for (var i=0; i < wall.positions.length; ++i) { var position = wall.positions[i]; drawWall(position - distance); } } function flap() { square.yVelocity = square.jump; } function adjustPosition() { distance += 2; square.yVelocity += gravity; square.y += square.yVelocity; } function clearBoundary() { var maxX = boundary.minX + boundary.width; var maxY = boundary.minY + boundary.height; context.clearRect(0, 0, canvas.width, boundary.minY); context.clearRect(maxX, 0, canvas.width - maxX, canvas.height); context.clearRect(0, maxY, canvas.width, canvas.height - maxY); context.clearRect(0, 0, boundary.minX, canvas.height); } function checkBoundary() { if (square.y >= boundary.height) { endGame(); } } function checkWalls() { var left = square.x; var right = square.x + square.size; var top = square.y; var bottom = square.y + square.size; for (var i=0; i < wall.positions.length; ++i) { var wallLeft = wall.positions[i] - distance; var wallRight = wallLeft + wall.width; if (wallLeft > right) continue; if (wallRight < left) continue; var topWallBottom = wall.height; var bottomWallTop = boundary.height - wall.height; if (top < topWallBottom || bottom > bottomWallTop) { endGame(); } } } function endGame() { context.font = "20px serif"; context.textAlign = 'center'; var xCenter = boundary.width / 2; var yCenter = boundary.height / 2; context.fillText('Game Over', xCenter, yCenter); pauseAnimation(); } function programSteps() { context.clearRect(0, 0, canvas.width, canvas.height); adjustPosition(); context.save(); context.translate(boundary.minX, boundary.minY); drawBoundary(); drawSquare(); drawWalls(); checkWalls(); checkBoundary(); context.restore(); clearBoundary(); } function runProgram() { interval = setInterval(programSteps, 80); } canvas.addEventListener('click', flap); // The following code is provided for you. // It creates an eventListener that listens // for the canvas to come into "focus", which // happens when you click on it. // This allows us to stop and start each individual // animation on this whole page separately. function startAnimation() { runProgram(); } function pauseAnimation() { clearInterval(interval); } canvas.addEventListener('focus', startAnimation); canvas.addEventListener('blur', pauseAnimation); canvas.focus();

## Challenge 3

Ok, now that we have a better understanding of randomization let's apply it to the position and size of the hole in each wall.

Rather than having each portion of the wall be the same height (75 pixels) let's vary them by randomizing the height of the top and bottom portion of the wall, creating varying hole positions and sizes.

You can play around with varying sizes, but I'd suggest varying the top portion between 25 and 150 pixels. Then you'll want to create a varying height for the bottom portion that is within the same parameters but you need to be careful. If you randomly create two walls that are 150 pixels then you won't have any room for the hole and you probably want the hole to be at least 75 pixels. Similarly if you both the top and bottom portions of the wall are 25 pixels then your hole will be 225 pixels (300 - 25 - 50), which is very big. So you may want to make your hole no more than 150 pixels as well...

You'll need to store this information as well so that you can draw the same hole in each frame. You may want to store a hash in the "positions" attribute of your "walls" variable that contains the position, the height of the top wall, and the size of the hole.

You'll need to update your wall collision detection to take this information into account as well.

In the end your flappy square should behave like the example to the right.

Quick Reference:

Previous Challenge: View your code from Stage 4 Challenge 2 to use on this challenge.

Code Missing: You have not yet entered any code in to the previous challenge: Stage 4 Challenge 2
Stage 4 Challenge 2
Message Log
##### Canvas (your drawing will display here)

A Solution: Here's the code I wrote to complete this challenge. View One Possible Solution

var canvas = document.getElementById('flappy_square_stage4_challenge3'); var context = canvas.getContext('2d'); var interval; var distance = 0; var gravity = 0.5; var boundary = { minX: 25, minY: 25, width: 425, height: 275 }; var square = { x: 25, y: 75, size: 20, yVelocity: 0, jump: -8 }; var wall = { width: 50, minHole: 75, maxHole: 150, positions: [] }; function drawBoundary() { context.strokeRect(0, 0, boundary.width, boundary.height); } function drawSquare() { context.fillRect(square.x, square.y, square.size, square.size); } function drawWall(wallInfo) { var x = wallInfo.x - distance; context.fillRect( x, 0, wall.width, wallInfo.topHeight ); context.fillRect( x, boundary.height - wallInfo.bottomHeight, wall.width, wallInfo.bottomHeight ); } function random(min, max) { return (min + Math.floor(Math.random() * (max - min))); } function drawWalls() { var wallX = square.x; if (wall.positions.length > 0) { wallX = wall.positions[wall.positions.length - 1].x; } var end = boundary.width + distance + wall.width; while (wallX < end) { wallX += random(100, 200); var topHeight = random(25, 150); var bottomHeight = random(25, 150); if (boundary.height - topHeight - bottomHeight < wall.minHole) { bottomHeight = boundary.height - topHeight - wall.minHole; } if (boundary.height - topHeight - bottomHeight > wall.maxHole) { bottomHeight = boundary.height - topHeight - wall.maxHole; } wall.positions.push({ x: wallX, topHeight: topHeight, bottomHeight: bottomHeight }); } for (var i=0; i < wall.positions.length; ++i) { drawWall(wall.positions[i]); } } function flap() { square.yVelocity = square.jump; } function adjustPosition() { distance += 2; square.yVelocity += gravity; square.y += square.yVelocity; } function clearBoundary() { var maxX = boundary.minX + boundary.width; var maxY = boundary.minY + boundary.height; context.clearRect(0, 0, canvas.width, boundary.minY); context.clearRect(maxX, 0, canvas.width - maxX, canvas.height); context.clearRect(0, maxY, canvas.width, canvas.height - maxY); context.clearRect(0, 0, boundary.minX, canvas.height); } function checkBoundary() { if (square.y >= boundary.height) { endGame(); } } function checkWalls() { var left = square.x; var right = square.x + square.size; var top = square.y; var bottom = square.y + square.size; for (var i=0; i < wall.positions.length; ++i) { var wallInfo = wall.positions[i]; var wallLeft = wallInfo.x - distance; var wallRight = wallLeft + wall.width; if (wallLeft > right) continue; if (wallRight < left) continue; var topWallBottom = wallInfo.topHeight; var bottomWallTop = boundary.height - wallInfo.bottomHeight; if (top < topWallBottom || bottom > bottomWallTop) { endGame(); } } } function endGame() { context.font = "20px serif"; context.textAlign = 'center'; var xCenter = boundary.width / 2; var yCenter = boundary.height / 2; context.fillText('Game Over', xCenter, yCenter); pauseAnimation(); } function programSteps() { context.clearRect(0, 0, canvas.width, canvas.height); adjustPosition(); context.save(); context.translate(boundary.minX, boundary.minY); drawBoundary(); drawSquare(); drawWalls(); checkWalls(); checkBoundary(); context.restore(); clearBoundary(); } function runProgram() { interval = setInterval(programSteps, 80); } canvas.addEventListener('click', flap); // The following code is provided for you. // It creates an eventListener that listens // for the canvas to come into "focus", which // happens when you click on it. // This allows us to stop and start each individual // animation on this whole page separately. function startAnimation() { runProgram(); } function pauseAnimation() { clearInterval(interval); } canvas.addEventListener('focus', startAnimation); canvas.addEventListener('blur', pauseAnimation); canvas.focus();

## Ready for the next lesson?

Next up, the "Stage: 5" lesson >