Stage 4

Overview

In the fourth stage of the Cityscape Challenge, we will create three rows of buildings. The buildings in the foreground will be larger and lighter in color. The buildings in the background will be smaller and darker in color. This will create the illusion of 3D depth.

Overviewvisual1
 

Lesson: Draw a Row of Buildings

When we wanted to draw a row of windows in Stage 2 Challenge 2, we used a for loop. A for loop is great when we want to draw something over and over again a specific number of times.

The difference between drawing a row of windows and drawing a row of buildings is: we know exactly how many windows to draw, but we don't know how many buildings to draw because every building has a different width.

So, to draw a row of buildings, we are going to use a while loop instead of a for loop.

In this example, we draw a white rectangle that is 400 pixels wide, and then use a while loop to draw pink squares along the bottom of the white rectangle until the last square reaches the end of the rectangle.

Before the while loop, we assign the variable x = 0. We are using x to calculate the x-coordinate of the next building, and we will keep running through the while loop as long as x < 400. Once the x-coordinate of the next building is greater than or equal to 400, the while loop stops.

To calculate the x-coordinate of the next building, we use x = x + w. So, if x = 0 at the start of the first loop and we draw a 32x32 square, we add 32 to x and at the start of the next loop, x = 32.

Press "Run" a few times and count the number of squares drawn. The number of squares will change depending on the size of the squares.

Quick Reference: Functions Variables random() While Loops

Editor (write code below)
var canvas = document.getElementById('granular_basic_cityscape_stage4_lesson1'); var context = canvas.getContext('2d'); function randomInteger(min, max) { var i = min + Math.floor((max - min + 1) * Math.random()); return i; } function drawSquare(s) { context.save(); context.translate(0, -s); context.strokeRect(0, 0, s, s); context.restore(); } var x = 0; context.save(); context.fillStyle = '#FFFFFF'; context.fillRect(0, 0, 400, 100); context.strokeStyle = '#FF1493'; context.translate(0, 100); while (x < 400) { var s = randomInteger(10, 50); context.save(); context.translate(x, 0); drawSquare(s); context.restore(); x = x + s; } context.restore();
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)

Challenge 1

Create a drawBuildingRow() function to draw a row of random buildings.

Because we are drawing a row of buildings, we will set groundY for the entire row and then position each building at (x, 0).

To set the groundY for the entire row, we can either apply context.translate(x, y) before calling drawBuildingRow(), or we can call drawBuildingRow(x, y) and apply the context.translate(x, y) inside of the function. Either approach works.

Inside the drawBuildingRow() function, draw the first building at x = 0, add a 12-pixel space between buildings, and keep drawing buildings while the x-coordinate of the next building is less than 800.

Note: In the example above with the random squares, we use x = x + s to update the value of x for the next loop. This works because both x and s are defined within the "scope" of the while loop. However, when drawing buildings, we can't use the variable w inside the while loop because w is only defined inside of the drawBuilding() function. What we can do instead is use the variable units, which is the number of office units on a floor in the building, to calculate the value of x for the next loop.

Challenge1visual1
Challenge 1 Sample Solution

When your drawBuildingRow() function is ready, draw a row of buildings starting at (0, 320). Press "Run" multiple times to make sure it is working.

The row of buildings should cover the length of the red line on the canvas and look similar to the image above. Obviously, the buildings will be random. If your drawBuildingRow() function seems to be working, mark the challenge as complete by selecting "Yes, it looks good".

Quick Reference: Functions Variables random() While Loops

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

Code Missing: You have not yet entered any code in to the previous challenge: Stage 3 Challenge 4
Stage 3 Challenge 4
Editor (write code below)
var canvas = document.getElementById('granular_basic_cityscape_stage4_challenge1'); var context = canvas.getContext('2d'); function drawBuilding(leftX, groundY, units, floors, windowType, roofType) { // YOUR CODE FOR DRAWING A BUILDING HERE } function drawWindow(windowType) { // YOUR CODE FOR DRAWING THE FOUR DIFFERENT TYPES OF WINDOWS HERE } function drawRoof(w, roofType) { // YOUR CODE FOR DRAWING THE FOUR DIFFERENT TYPES OF ROOFS HERE } function randomInteger(min, max) { // YOUR CODE FOR GENERATING A RANDOM INTEGER BETWEEN MIN AND MAX, INCLUDING MIN AND MAX, HERE } function drawBuildingRow() { // YOUR CODE FOR DRAWING A ROW OF RANDOM BUILDINGS HERE } // DRAW A ROW OF RANDOM BUILDINGS SITTING ON THE GROUND STARTING AT (0, 320) HERE
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)
Challenge1

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'); function drawBuilding(leftX, groundY, units, floors, windowType, roofType) { context.save(); var width = (units * 16) + (4 * 2); var height = (floors * 16) + (4 * 2); context.translate(leftX, groundY - height); context.fillStyle = '#999999'; context.fillRect(0, 0, width, height); context.save(); context.translate(4, 4); context.fillStyle = '#666666'; for (var j=0; j < floors; ++j) { context.save(); for (var i=0; i < units; ++i) { drawWindow(windowType); context.translate(16, 0); } context.restore(); context.translate(0, 16); } context.restore(); drawRoof(width, roofType); context.restore(); } function drawWindow(windowType) { switch(windowType) { case 0: context.fillRect(4, 2, 8, 10); break; case 1: context.fillRect(2, 3, 5, 8); context.fillRect(9, 3, 5, 8); break; case 2: context.fillRect(0, 3, 16, 8); break; case 3: context.fillRect(5, 1, 6, 14); break; } } function drawRoof(w, roofType) { context.save(); switch(roofType) { case 1: context.translate((16 / 2), -16); context.fillRect(0, 0, w - 16, 16); break; case 2: context.translate((16 / 2), -24); context.fillRect(0, 0, w - 16, 24); context.translate(((w - 16) / 2) - (32 / 2), -24); context.fillRect(0, 0, 32, 24); context.translate((32 / 2) - (8 / 2), -32); context.fillRect(0, 0, 8, 32); break; case 3: context.translate((w - 64) / 2, -16); context.fillRect(0, 0, 64, 16); context.translate((64 - 32) / 2, 0); context.beginPath(); context.moveTo(0, 0); context.lineTo(16, -64); context.lineTo(32, 0); context.closePath(); context.fill(); break; } context.restore(); } function randomInteger(min, max) { return min + Math.floor(Math.random() * (max - min)); } var x = 0; while (x < 800) { var units = randomInteger(4, 10); var floors = randomInteger(5, 18); var windowType = randomInteger(0, 4); var roofType = randomInteger(0, 4); drawBuilding(x, 320, units, floors, windowType, roofType); x += (units * 16) + (4 * 2) + 12; }
 

Lesson: Draw a Smaller Row of Random Buildings

To create a 3D effect, we are going to draw two more rows of buildings behind the first row. Because objects get smaller in the distance, we will draw the other rows of buildings slightly smaller using the context.scale() function.

In this example, we use the context.scale() function to change the size of four random flags.

By using context.scale(0.6, 0.6), we are drawing everything at 60% scale. If we use context.scale(1, 1), then we are drawing everything at normal size.

Try context.scale(1.5, 1.5) and see what happens. The first value changes the scale in the x-direction. The second value changes it in the y-direction. The two values do not have to be the same.

Note: The scale of the context gets saved and restored with context.save() and context.restore().

Quick Reference: Functions Variables random() While Loops scale()

Editor (write code below)
var canvas = document.getElementById('granular_basic_cityscape_stage4_lesson2'); var context = canvas.getContext('2d'); function randomInteger(min, max) { var i = min + Math.floor((max - min + 1) * Math.random()); return i; } function drawFlag(country) { context.save(); switch (country) { case 0: context.fillStyle = '#0055A4'; context.fillRect(0, 0, 30, 60); context.fillStyle = '#FFFFFF'; context.fillRect(30, 0, 30, 60); context.fillStyle = '#EF4135'; context.fillRect(60, 0, 30, 60); break; case 1: context.fillStyle = '#FCD116'; context.fillRect(0, 0, 90, 30); context.fillStyle = '#003893'; context.fillRect(0, 30, 90, 15); context.fillStyle = '#CE1126'; context.fillRect(0, 45, 90, 15); break; case 2: context.fillStyle = '#ED1C24'; context.fillRect(0, 0, 90, 60); context.fillStyle = '#FFFFFF'; context.fillRect(0, 10, 90, 40); context.fillStyle = '#241D4F'; context.fillRect(0, 20, 90, 20); break; } context.restore(); } context.save(); context.scale(0.6, 0.6); context.translate(20, 20); for (var i = 0; i < 4; i = i + 1) { var flagType = randomInteger(0, 2); drawFlag(flagType); context.translate(100, 30); } context.restore();
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)

Challenge 2

Update the drawBuildingRow() function so we can draw rows with different scales.

In the definition of the drawBuildingRow() function, add a parameter called scale: drawBuildingRow(scale).

Then, inside the drawBuildingRow() function, after saving the context for the first time, change the scale of the context using context.scale(scale, scale).

If you try to draw a row of buildings at 0.6 (or 60%) scale now, you will see something interesting. The buildings are smaller, but the row of buildings is not at least 800 pixels long on the canvas. In fact, the row of buildings is 800 pixels long in the context, but only 480 pixels long on the canvas. That's because 60% of 800 is 480.

To draw a row of buildings at 0.6 scale that is at least 800 pixels long on the canvas, we need to adjust the condition inside our while loop. Instead of x < 800, we should keep drawing buildings as long as x < 800 / scale. For 0.6 scale, that works out to x < 1333.

Challenge2visual1
Challenge 2 Sample Solution

Draw a row of buildings at 0.6 scale where the first building is sitting on the ground at (0, 280) and the row of buildings is at least 800 pixels long on the canvas.

The row of buildings should cover the length of the red line on the canvas and look similar to the image above. Obviously, the buildings will be random. If your drawBuildingRow() function seems to be working, mark the challenge as complete by selecting "Yes, it looks good".

Quick Reference: Functions Variables random() While Loops scale()

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
Editor (write code below)
var canvas = document.getElementById('granular_basic_cityscape_stage4_challenge2'); var context = canvas.getContext('2d'); function drawBuilding(leftX, groundY, units, floors, windowType, roofType) { // YOUR CODE FOR DRAWING A BUILDING HERE } function drawWindow(windowType) { // YOUR CODE FOR DRAWING THE FOUR DIFFERENT TYPES OF WINDOWS HERE } function drawRoof(w, roofType) { // YOUR CODE FOR DRAWING THE FOUR DIFFERENT TYPES OF ROOFS HERE } function randomInteger(min, max) { // YOUR CODE FOR GENERATING A RANDOM INTEGER BETWEEN MIN AND MAX, INCLUDING MIN AND MAX, HERE } function drawBuildingRow() { // YOUR CODE FOR DRAWING A ROW OF RANDOM BUILDINGS HERE } // DRAW A ROW OF RANDOM BUILDINGS AT 0.6 SCALE SITTING ON THE GROUND STARTING AT (0, 280) HERE
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)
Challenge2

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'); function drawBuilding(leftX, groundY, units, floors, windowType, roofType) { context.save(); var width = (units * 16) + (4 * 2); var height = (floors * 16) + (4 * 2); context.translate(leftX, groundY - height); context.fillStyle = '#999999'; context.fillRect(0, 0, width, height); context.save(); context.translate(4, 4); context.fillStyle = '#666666'; for (var j=0; j < floors; ++j) { context.save(); for (var i=0; i < units; ++i) { drawWindow(windowType); context.translate(16, 0); } context.restore(); context.translate(0, 16); } context.restore(); drawRoof(width, roofType); context.restore(); } function drawWindow(windowType) { switch(windowType) { case 0: context.fillRect(4, 2, 8, 10); break; case 1: context.fillRect(2, 3, 5, 8); context.fillRect(9, 3, 5, 8); break; case 2: context.fillRect(0, 3, 16, 8); break; case 3: context.fillRect(5, 1, 6, 14); break; } } function drawRoof(w, roofType) { context.save(); switch(roofType) { case 1: context.translate((16 / 2), -16); context.fillRect(0, 0, w - 16, 16); break; case 2: context.translate((16 / 2), -24); context.fillRect(0, 0, w - 16, 24); context.translate(((w - 16) / 2) - (32 / 2), -24); context.fillRect(0, 0, 32, 24); context.translate((32 / 2) - (8 / 2), -32); context.fillRect(0, 0, 8, 32); break; case 3: context.translate((w - 64) / 2, -16); context.fillRect(0, 0, 64, 16); context.translate((64 - 32) / 2, 0); context.beginPath(); context.moveTo(0, 0); context.lineTo(16, -64); context.lineTo(32, 0); context.closePath(); context.fill(); break; } context.restore(); } function randomInteger(min, max) { return min + Math.floor(Math.random() * (max - min)); } function drawBuildingRow(scale) { context.save(); context.scale(scale, scale); var x = 0; var end = (800 / scale); while (x < end) { var units = randomInteger(4, 10); var floors = randomInteger(5, 18); var windowType = randomInteger(0, 4); var roofType = randomInteger(0, 4); drawBuilding(x, (280 / scale), units, floors, windowType, roofType); x += (units * 16) + (4 * 2) + 12; } context.restore(); } drawBuildingRow(0.6);
 

Lesson: Draw a Smaller and Darker Row of Random Buildings

In addition to making the rows in the back smaller, we will also make them darker.

There are several ways to define colors when using context.fillStyle. So far, we have been using the color #999999 to draw our buildings. Another way to write #999999 is rgb(153, 153, 153). The number 153 in base 10 is actually 99 in base 16.

When using rgb() to define a color, we are describing the amount of red (r), green (g), and blue (b) in the color, where 0 is none and 255 is the maximum value. For example, black is rgb(0, 0, 0), which is no red, no green, and no blue. White is rgb(255, 255, 255), which is maximum red, maximum green, and maximum blue.

In this example, we draw a rectangle with a random color by selecting and combining random amounts of red, green, and blue.

There are a few things to keep in mind when using rgb() to define a color. First, the red, green, and blue values have to be integers between 0 and 255. No decimals. Second, the rgb() definition is a string of text. So, we add pieces of text together to get what we need.

Press "Run" to change the color of the rectangle.

Quick Reference: Functions Variables random() fillStyle While Loops scale()

Editor (write code below)
var canvas = document.getElementById('granular_basic_cityscape_stage4_lesson3'); var context = canvas.getContext('2d'); function drawText(text, x, y) { context.save(); context.fillStyle = 'rgb(0, 0, 0)'; context.font = '16px sans-serif'; context.textAlign = 'center'; context.fillText(text, x, y); context.restore(); } function randomInteger(min, max) { var i = min + Math.floor((max - min + 1) * Math.random()); return i; } var r = randomInteger(0, 255) // Choose a random amount of red var g = randomInteger(0, 255) // Choose a random amount of green var b = randomInteger(0, 255) // Choose a random amount of blue var color = 'rgb(' + r + ', ' + g + ', ' + b + ')'; // Combine the red, green, and blue in a text string context.save(); context.fillStyle = color; // Use the color as the context's fillStyle context.fillRect(40, 40, 360, 240); drawText(color, 220, 300); // Print the text string stored in the variable color context.restore();
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)

Challenge 3

Update the drawBuildingRow() and drawBuilding() functions to draw darker buildings as the scale gets smaller.

Inside the drawBuildingRow() function, use Math.round(153 * scale) to calculate the amount of red, green, and blue in the building's color. (For the building's color, the amount of red, green, and blue are all the same.) This will make the color darker as the scale gets smaller.

Create a text string from the red, green, and blue values. Store the text string in a variable called buildingColor. Store the text string 'rgb(102, 102, 102)' and store it in a variable called windowColor. The window color for all three rows is the same.

Pass the variables buildingColor and windowColor into the drawBuilding() function. Make sure you update the drawBuilding() function definition to include buildingColor and windowColor as parameters.

Inside the drawBuilding() function, set the context.fillStyle to buildingColor when drawing the building and roof, and to windowColor when drawing the windows.

Challenge3visual1
Challenge 3 Sample Solution

Then, draw a row of buildings at 0.6 scale where the first building is sitting on the ground at (0, 280). The buildings should be smaller and darker than the row of buildings in Challenge 1.

The row of buildings should cover the length of the red line on the canvas and look similar to the image above, especially the building color. Obviously, the buildings will be random. If your drawBuildingRow() function seems to be working, mark the challenge as complete by selecting "Yes, it looks good".

Quick Reference: Functions Variables fillStyle While Loops scale()

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
Editor (write code below)
var canvas = document.getElementById('granular_basic_cityscape_stage4_challenge3'); var context = canvas.getContext('2d'); function drawBuilding(leftX, groundY, units, floors, windowType, roofType) { // YOUR CODE FOR DRAWING A BUILDING HERE } function drawWindow(windowType) { // YOUR CODE FOR DRAWING THE FOUR DIFFERENT TYPES OF WINDOWS HERE } function drawRoof(w, roofType) { // YOUR CODE FOR DRAWING THE FOUR DIFFERENT TYPES OF ROOFS HERE } function randomInteger(min, max) { // YOUR CODE FOR GENERATING A RANDOM INTEGER BETWEEN MIN AND MAX, INCLUDING MIN AND MAX, HERE } function drawBuildingRow() { // YOUR CODE FOR DRAWING A ROW OF RANDOM BUILDINGS HERE } // DRAW A ROW OF RANDOM BUILDINGS AT 0.6 SCALE SITTING ON THE GROUND STARTING AT (0, 280) HERE
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)
Challenge3

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'); function drawBuilding(leftX, groundY, units, floors, windowType, roofType) { var windowColor = 'rgb(102, 102, 102)'; context.save(); var width = (units * 16) + (4 * 2); var height = (floors * 16) + (4 * 2); context.translate(leftX, groundY - height); context.fillRect(0, 0, width, height); context.save(); context.translate(4, 4); context.fillStyle = windowColor; for (var j=0; j < floors; ++j) { context.save(); for (var i=0; i < units; ++i) { drawWindow(windowType); context.translate(16, 0); } context.restore(); context.translate(0, 16); } context.restore(); drawRoof(width, roofType); context.restore(); } function drawWindow(windowType) { switch(windowType) { case 0: context.fillRect(4, 2, 8, 10); break; case 1: context.fillRect(2, 3, 5, 8); context.fillRect(9, 3, 5, 8); break; case 2: context.fillRect(0, 3, 16, 8); break; case 3: context.fillRect(5, 1, 6, 14); break; } } function drawRoof(w, roofType) { context.save(); switch(roofType) { case 1: context.translate((16 / 2), -16); context.fillRect(0, 0, w - 16, 16); break; case 2: context.translate((16 / 2), -24); context.fillRect(0, 0, w - 16, 24); context.translate(((w - 16) / 2) - (32 / 2), -24); context.fillRect(0, 0, 32, 24); context.translate((32 / 2) - (8 / 2), -32); context.fillRect(0, 0, 8, 32); break; case 3: context.translate((w - 64) / 2, -16); context.fillRect(0, 0, 64, 16); context.translate((64 - 32) / 2, 0); context.beginPath(); context.moveTo(0, 0); context.lineTo(16, -64); context.lineTo(32, 0); context.closePath(); context.fill(); break; } context.restore(); } function randomInteger(min, max) { return min + Math.floor(Math.random() * (max - min)); } function drawBuildingRow(scale) { context.save(); context.scale(scale, scale); var color = Math.round(153 * scale); var buildingColor = 'rgb(' + color + ',' + color + ',' + color + ')'; context.fillStyle = buildingColor; var x = 0; var end = (800 / scale); while (x < end) { var units = randomInteger(4, 10); var floors = randomInteger(5, 18); var windowType = randomInteger(0, 4); var roofType = randomInteger(0, 4); drawBuilding(x, (280 / scale), units, floors, windowType, roofType); x += (units * 16) + (4 * 2) + 12; } context.restore(); } drawBuildingRow(0.6);
 

Lesson: Draw Three Rows of Buildings and a Horizon

We are almost done. The last step is to assemble your final drawing.

In this example, we draw a cake for celebrating! Press "Run" to find your favorite cake.

Quick Reference: Functions Variables random() round() / floor() / ceil() fillStyle fillRect()

Editor (write code below)
var canvas = document.getElementById('granular_basic_cityscape_stage4_lesson4'); var context = canvas.getContext('2d'); function randomInteger(min, max) { var i = min + Math.floor((max - min + 1) * Math.random()); return i; } function color(i) { switch (i) { case 0: c = '#90EE90'; break; case 1: c = '#E6E6FA'; break; case 2: c = '#FFFACD'; break; case 3: c = '#FFB6C1'; //c = '#FFE4E1'; break; case 4: c = '#B0E0E6'; break; case 5: c = '#1E90FF'; break; case 6: c = '#9932CC'; break; case 7: c = '#BA55D3'; break; case 8: c = '#FF1493'; break; case 9: c = '#3CB371'; break; } return c; } function drawCandle() { context.save(); context.translate(0, -80); context.fillStyle = '#FFFFFF'; context.fillRect(-5, 30, 10, 50); context.fillStyle = '#FFFF00'; context.fillRect(-6, 0, 12, 30); context.fillStyle = '#FF8C00'; context.fillRect(-5, 10, 10, 20); context.fillStyle = '#FF0000'; context.fillRect(-4, 20, 8, 10); context.restore(); } function drawDecoration(icingColor, accentColor) { context.save(); context.rotate(0.25 * Math.PI); context.fillStyle = accentColor; context.fillRect(-8, -8, 16, 16); context.rotate(0.25 * Math.PI); context.fillStyle = icingColor; context.fillRect(-8, -8, 16, 16); context.rotate(0.25 * Math.PI); context.fillStyle = accentColor; context.fillRect(-3, -3, 6, 6); context.restore(); } function drawTier(cakeColor, icingColor) { context.save(); context.fillStyle = cakeColor; context.translate(-100, 0); context.fillRect(0, 0, 200, 50); context.translate(1, 45); context.fillStyle = icingColor; for (var i = 0; i < 19; i = i + 1) { context.save(); context.rotate(0.25 * Math.PI); context.fillRect(0, 0, 10, 10); context.restore(); context.translate(11, 0); } context.restore(); } function drawCake() { var cakeColor = color(randomInteger(0, 4)); var icingColor = color(randomInteger(5, 9)); context.save(); context.fillStyle = '#000000'; context.fillRect(0, 0, canvas.width, canvas.height); context.translate(200, 300); for (var i = 0; i < 4; i = i + 1) { context.translate(0, -50); drawTier(cakeColor, icingColor); } context.translate(-80, 0); for (var j = 0; j < 5; j = j + 1) { drawCandle(); context.translate(40, 0); } context.restore(); } drawCake();
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)

Challenge 4

In the drawBuildingRow() function, change the while loop to run as long as x < canvas.width. This will ensure each row of buildings will cover the entire width of the canvas.

Challenge4visual1
Challenge 4 Sample Solution

Draw a gray rectangle (color #CCCCCC) at (0, 220) with a width equal to canvas.width and a height of 100. This rectangle is the ground.

Draw a row of buildings with a scale of 0.6 at (0, 280). This is the back row of buildings.

Draw a row of buildings with a scale of 0.8 at (0, 300). This is the middle row of buildings.

Draw a row of buildings with a scale of 1.0 at (0, 320). This is the front row of buildings.

Press "Run" multiple times to make sure you are drawing a random cityscape with three rows buildings. Each row should cover the width of the canvas, and as the rows get farther away, the buildings should get smaller and darker. Once you feel satisfied with your drawings, mark the challenge as complete by selecting "Yes, it looks good".

Quick Reference: Functions Variables random() round() / floor() / ceil() fillStyle fillRect()

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

Code Missing: You have not yet entered any code in to the previous challenge: Stage 4 Challenge 3
Stage 4 Challenge 3
Editor (write code below)
var canvas = document.getElementById('granular_basic_cityscape_stage4_challenge4'); var context = canvas.getContext('2d'); function drawBuilding(leftX, groundY, units, floors, windowType, roofType) { // YOUR CODE FOR DRAWING A BUILDING HERE } function drawWindow(windowType) { // YOUR CODE FOR DRAWING THE FOUR DIFFERENT TYPES OF WINDOWS HERE } function drawRoof(w, roofType) { // YOUR CODE FOR DRAWING THE FOUR DIFFERENT TYPES OF ROOFS HERE } function randomInteger(min, max) { // YOUR CODE FOR GENERATING A RANDOM INTEGER BETWEEN MIN AND MAX, INCLUDING MIN AND MAX, HERE } function drawBuildingRow() { // YOUR CODE FOR DRAWING A ROW OF RANDOM BUILDINGS HERE } // DRAW THE GROUND AND THREE ROWS OF BUILDINGS HERE
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
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_challenge4'); var context = canvas.getContext('2d'); function drawBuilding(leftX, groundY, units, floors, windowType, roofType) { var windowColor = 'rgb(102, 102, 102)'; context.save(); var width = (units * 16) + (4 * 2); var height = (floors * 16) + (4 * 2); context.translate(leftX, groundY - height); context.fillRect(0, 0, width, height); context.save(); context.translate(4, 4); context.fillStyle = windowColor; for (var j=0; j < floors; ++j) { context.save(); for (var i=0; i < units; ++i) { drawWindow(windowType); context.translate(16, 0); } context.restore(); context.translate(0, 16); } context.restore(); drawRoof(width, roofType); context.restore(); } function drawWindow(windowType) { switch(windowType) { case 0: context.fillRect(4, 2, 8, 10); break; case 1: context.fillRect(2, 3, 5, 8); context.fillRect(9, 3, 5, 8); break; case 2: context.fillRect(0, 3, 16, 8); break; case 3: context.fillRect(5, 1, 6, 14); break; } } function drawRoof(w, roofType) { context.save(); switch(roofType) { case 1: context.translate((16 / 2), -16); context.fillRect(0, 0, w - 16, 16); break; case 2: context.translate((16 / 2), -24); context.fillRect(0, 0, w - 16, 24); context.translate(((w - 16) / 2) - (32 / 2), -24); context.fillRect(0, 0, 32, 24); context.translate((32 / 2) - (8 / 2), -32); context.fillRect(0, 0, 8, 32); break; case 3: context.translate((w - 64) / 2, -16); context.fillRect(0, 0, 64, 16); context.translate((64 - 32) / 2, 0); context.beginPath(); context.moveTo(0, 0); context.lineTo(16, -64); context.lineTo(32, 0); context.closePath(); context.fill(); break; } context.restore(); } function randomInteger(min, max) { return min + Math.floor(Math.random() * (max - min)); } function drawBuildingRow(scale, ground) { context.save(); context.scale(scale, scale); var color = Math.round(153 * scale); var buildingColor = 'rgb(' + color + ',' + color + ',' + color + ')'; context.fillStyle = buildingColor; var x = 0; var end = (canvas.width / scale); while (x < end) { var units = randomInteger(4, 10); var floors = randomInteger(5, 18); var windowType = randomInteger(0, 4); var roofType = randomInteger(0, 4); drawBuilding(x, (ground / scale), units, floors, windowType, roofType); x += (units * 16) + (4 * 2) + 12; } context.restore(); } context.save(); context.fillStyle = '#CCCCCC'; context.fillRect(0, canvas.height - 100, canvas.width, 100); context.restore(); drawBuildingRow(0.6, 280); drawBuildingRow(0.8, 300); drawBuildingRow(1, 320);