RoboCatz.com
About FLL | Navigation | Playbook | Strategy | Models | Techniques | RobotC | Programming | Robot News |

Roblox For and While Loops

This lesson is a continuation of previous lessons in Roblox programming. If you are not familiar with how to create the genericShapes module, please review the other Roblox lessons first. For this lesson, you should start a new project. Create a genericShapes module in your ReplicatedStorage area. Then copy-and-paste the code from the website into the genericShapes module. The source code for the genericShapes module is linked below.

genericShapes Module (genericShapes-ModuleScript.htm)
Copy the code from this page into the genericShapes module in your program.

The next step is to go to the Workspace in Roblox and add a Script. Rename that script: startingScript and paste the code in the example below.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)
We will be working with this starting script.

For Loops

For loops allow the computer to generate hundreds, thousands, or even tens of thousands of parts for your Roblox world. For example, we will create a world where there are 20 squares aligned in a row. Copy the code below to your startingScript.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local numRows = 20
local size=10
for i=1,numRows do
	s.square( { x=(i-numRows/2)*size, y=size*0.8, z=size, size=size, thickness=1, color=s.randomColor(), rotateBy=Vector3.new(0,0,0) })
end
Run the program and try to jump onto the array of squares.

Next, make the computer draw and equal number of columns.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local numRows = 20
local numCols = 20
local size=10
for i=1,numRows do
	for j=1, numCols do
		s.square( { x=(i-numRows/2)*size, y=size*0.8, z=(j-numCols/2)*size, size=size, thickness=1, color=s.randomColor(), rotateBy=Vector3.new(0,0,0) })
	end
end
Run the program and try to jump onto the array of squares.

Next, make the computer draw 10 rows, 10 columns, and 10 layers tall. This will draw 1000 squares. If you stay with the value of 20 for the rows, columns and layers, the computer will have to draw 8000 squares. So, to save some time, we will limit the for loops to just 10 iterations.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local numRows = 10
local numCols = 10
local numLayers = 10
local size=10
for i=1,numRows do
	for j=1, numCols do
		for k=1, numLayers do			
			s.square( { x=(i-numRows/2)*size, y=k*size*0.8, z=(j-numCols/2)*size, size=size, thickness=1, color=s.randomColor(), rotateBy=Vector3.new(0,0,0) })
		end
	end
end
Run the program and try to jump onto the array of squares.

Next, instead of using random colors for the squares. Try to make the colors related to the positionin the array. Since computer colors can be defined as a combination of Red, Green and Blue, create a value for each color component based on the incrementing variable used in the for loops.

Try to determine the change needed to the algorithm before going on to the next page. Then you can check your answer to see if you coded it the same way I did.

Hint: Use the Color3.new() function. This function takes 3 parameters (r, g, b). The expressions you create need to result in values that range from 0 to 1 for each of the 3 colors (r, g, b). If you are using the incrementing variables, you could divide them by their maximum values to obtain a value that ranges from 0 to 1. For example, if "i" is zero and "numRows" is ten, then the expression: i/numRows would be equivalent to 0/10 which would be 0. And if "i" is ten and "numRows" is also ten, then the expression: i/numRows would be equivalent to 10/10 which would be 1. And since "i" only goes from 1 to 10 in the for loop, the expression of i/numRows should result in a value that falls within the range of 0 and 1.

Don't move to the next page until you have tried to make the array of squares look like this image:



My solution was to use the expression:

Example:
color=Color3.new(i/numRows, j/numCols, k/numLayers)
There are other ways of doing this. For example, you could have created additional incrementing variables that could have incremented by 0.10.

I try to write expressions that utilize variables that already exist in the code. So writing the expression: i/numRows seems more efficient since both variables are used in the for loop.

The example below is my solution to this challenge.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local numRows = 10
local numCols = 10
local numLayers = 10
local size=10
for i=1,numRows do
	for j=1, numCols do
		for k=1, numLayers do			
			s.square( { x=(i-numRows/2)*size, y=k*size*0.8, z=(j-numCols/2)*size, size=size, thickness=1, color=Color3.new(i/numRows, j/numCols, k/numLayers), rotateBy=Vector3.new(0,0,0) })
		end
	end
end
Try jumping to higher layers. You may notice that it is difficult to jump beyond the lowest layer. This is because the avatar needs to stand on the edge of the square. Jumping from this point causes the avatar to bump its head on the next layer.

As one possible solution to this problem, try adding discs to the squares. See example below.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local numRows = 10
local numCols = 10
local numLayers = 10
local size=10
for i=1,numRows do
	for j=1, numCols do
		for k=1, numLayers do			
			s.square( { x=(i-numRows/2)*size, y=k*size*0.8, z=(j-numCols/2)*size, size=size, thickness=1, color=Color3.new(i/numRows, j/numCols, k/numLayers), rotateBy=Vector3.new(0,0,0) })
			s.disc( { x=(i-numRows/2)*size, y=k*size*0.8, z=(j-numCols/2)*size, size=size*0.8, thickness=1, color=s.randomColor(), rotateBy=Vector3.new(0,0,90) })
		end
	end
end
Try jumping to higher layers. You may notice that it is impossible to jump beyond the lowest layer. This is because the discs are now blocking the ability to jump higher.

Is there a way that you can draw discs in some of the squares (but not in every single square)?

How can you tell the computer to do something for some of the time but not all of the time?

Hint: Create a "conditional" expression so that the discs will be drawn -- but only if the condition is true. Next, try to create an algorithm that will determine under what circumstances the condition should be true.

There are two approaches you can take when writing the algorithm that will determine under what circumstances the condition should be true.

Approach 1: Use a random number function to randomly determine of the condition is true. For example: the expression math.random() < 0.33 tells the computer to evaluate the expression as true (only 1/3rd of the time). This is because 0.33 is the decimal equivalent of 1/3rd (i.e., the fraction 1/3).

Approach 2: Use an expression with the incrementing variables and modulus. For example: the expression (i + j + k) % 5 == 0 tells the computer to evaluate the expression as true (only 1/5th of the time). This is because n % 5 will evaluate to 0 only when n is evenly divisible by 5.

Don't move to the next page until you have figured out how to draw some discs in the squares.


Approach 1: Using math.random()

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local numRows = 10
local numCols = 10
local numLayers = 10
local size=10
for i=1,numRows do
	for j=1, numCols do
		for k=1, numLayers do			
			s.square( { x=(i-numRows/2)*size, y=k*size*0.8, z=(j-numCols/2)*size, size=size, thickness=1, color=Color3.new(i/numRows, j/numCols, k/numLayers), rotateBy=Vector3.new(0,0,0) })
			if math.random()<0.1 then
				s.disc( { x=(i-numRows/2)*size, y=k*size*0.8, z=(j-numCols/2)*size, size=size*0.8, thickness=1, color=s.randomColor(), rotateBy=Vector3.new(0,0,90) })
			end
		end
	end
end

Approach 2: Using (i+j+k) % n

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local numRows = 10
local numCols = 10
local numLayers = 10
local size=10
for i=1,numRows do
	for j=1, numCols do
		for k=1, numLayers do			
			s.square( { x=(i-numRows/2)*size, y=k*size*0.8, z=(j-numCols/2)*size, size=size, thickness=1, color=Color3.new(i/numRows, j/numCols, k/numLayers), rotateBy=Vector3.new(0,0,0) })
			if (i+j+k) % 5 == 0 then
				s.disc( { x=(i-numRows/2)*size, y=k*size*0.8, z=(j-numCols/2)*size, size=size*0.8, thickness=1, color=s.randomColor(), rotateBy=Vector3.new(0,0,90) })
			end
		end
	end
end
Can you get the avatar to jump to higher layers now?

Roman Aqueduct

Your next project is to re-create a Roman aqueduct. The Romans built 11 major aqueducts between 300 B.C. and 300 A.D. The purpose of the aqueduct was to bring water to a city of over 1 million people. Some of these aqueducts were as long as 60 miles.

For this project you will use a while loop. The while and for loops are very similar. Both require a condition that must be true to remain in the loop. The main difference between them is the for loop usually specifies an incrementing variable whereas the while loop may use a variety of (and/or combinations of) conditions to remain in the loop.

For this project we will use a variable to keep track of the x-coordinate of our aqueduct section. We will initialize the variable to the value of our starting x-coordinate. Then we will remain in the loop until the variable is less than the ending x-coordinate. While in the loop we will decrement the variable by the width of the aqueduct section.

Use the example startingScript (below) to get started.

Do you see a section of the aqueduct?

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

s.archway(  { x=0, y=13, z=15, size=10, height=50, width=30, color="gray", material=Enum.Material.Cobblestone  })
Now we will add the variable to represent the x-Coordinate of the aquedcut part. Notice that the xCoord variable is also used in the parameters passed to the s.archway() function.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local xCoord = 300

s.archway(  { x=xCoord, y=13, z=15, size=10, height=50, width=30, color="gray", material=Enum.Material.Cobblestone  })
Add a loop and be sure to decrement the variable.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local xCoord = 300

while xCoord > -300 do
	s.archway(  { x=xCoord, y=13, z=15, size=10, height=50, width=30, color="gray", material=Enum.Material.Cobblestone  })
	xCoord = xCoord - 30
end
What does the expression while xCoord > -300 mean?

Will there ever be a time that the xCoord will be less than -300?

Can you build a second layer of arches? You may have to use trial-and-error to determine the appropriate y-coordinate for the second layer. Don't forget, you need to reset the xCoord back to 300 before starting the second layer's while loop.

Think about how you would add a second layer before moving on to the next page.


Hint: If a while loop can add one layer, a second while loop could add a second layer. The aqueduct should be constructed one layer at a time. You may have to experiment with different values for the y-coordinate to get them to stack on top of each other.

Here's my solution (see below). Notice that the arches in each subsequent layer get smaller. And because the arches are smaller, the decrement to the xCoord also decreases to match the size of the archway.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local xCoord = 300
while xCoord > -300 do
	s.archway(  { x=xCoord, y=13, z=15, size=10, height=50, width=30, color="gray", material=Enum.Material.Cobblestone  })
	xCoord = xCoord - 30
end

xCoord = 300
while xCoord > -300 do
	s.archway(  { x=xCoord, y=40, z=16, size=5, height=20, width=15, color="gray", material=Enum.Material.Cobblestone  })
	xCoord = xCoord - 15
end

xCoord = 300
while xCoord > -300 do
	s.archway(  { x=xCoord, y=55, z=16, size=5, height=10, width=8, color="gray", material=Enum.Material.Cobblestone  })
	xCoord = xCoord - 8
end
Travelling along the x-axis was easy. You only had to modify the value of one coordinate (X). Can you draw the aqueduct in a diagonal path (say from: x=300, z=300 to x=-300, z=-300)?

With diagonal arches, you will need to modify both the X and the Z coordinates as well as the rotation of the archway. In the previous example, the xCoord was decremented by the width of the archway. When moving on a diagonal, if you decrease the X-coordinate by the width of the archway, there will be gaps in the arches. Instead, you need to decrease by a fraction of the width.

How can you figure out what fraction you need to decrease the X-coordinate by?

Look at the figure of the triangle.

Around the year 500 B.C. a Greek mathematician (Pythagoras) discovered that the lengths of the sides of right triangles have a specific algorithm:

A2 + B2 = C2

With this formula, it is possible to determine the lengths of the various sides of a right triangle.

In our Roblox diagonal aqueduct, we will move at a 45-degree angle exactly as it is shown as line "C" in the figure here. Line B will represent the amount of distance we need to move on the x-axis and line A will represent the amount of distance we need to move on the z-axis.

As you can see, both A and B are the same length and both are shorter than C and since we are moving at a 44-degree angle, the lengths of A and B are the same.

Therefore, we could re-write the formula as:

2 * B2 = C2

Remember, the length of C is the width of the aqueduct section and the length of B is the distance to move on the x-axis and on the z-axis.

Since our goal is to solve for B, we will need to get the square root of both sides of the equation. This will become:

sqrt(2) * B = C

To get B on one side of the equation, we will divide both sides of the equation by sqrt(2). This will become:

B = C / sqrt(2)

The square root of 2 is: ___????___

Ask the computer before continuing.

The square root of 2 is 1.414

Therefore,

B = C / 1.414

If our aqueduct section is 30 Roblox units wide, the formula becomes:

B = 30 / 1.414

What does B equal?

Ask the computer before continuing.

B = 21

This means that you will need to adjust the X and Z coordinates by 21 for each iteration through the while loop.

I could use a numeric literal in the expression to decrement the xCoord (see below).

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local xCoord = 300
local zCoord = 300

while xCoord > -300 do
	s.archway(  { x=xCoord, y=13, z=zCoord, size=10, height=50, width=30, color="gray", material=Enum.Material.Cobblestone  })
	xCoord = xCoord - 21
	zCoord = zCoord - 21
end
But it might make more sense (programatically) to use a variable instead. See below:

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local xCoord = 300
local zCoord = 300

local archwayWidth = 30
while xCoord > -300 do
	s.archway(  { x=xCoord, y=13, z=zCoord, size=10, height=archwayWidth*1.6, width=archwayWidth, color="gray", material=Enum.Material.Cobblestone  })
	xCoord = xCoord - archwayWidth / math.sqrt(2)
	zCoord = zCoord - archwayWidth / math.sqrt(2)
end
Using a variable allows me to change the archwayWidth value once and all of the expressions in the loop will be evaluated accordingly.

The next step is to rotate the archways so that they form a straight line. Use the rotateBy property where you would type the expression:

rotateBy = Vector3.new(0, 45, 0)

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local xCoord = 300
local zCoord = 300

local archwayWidth = 30
while xCoord > -300 do
	s.archway(  { x=xCoord, y=13, z=zCoord, size=10, height=archwayWidth*1.6, width=archwayWidth, rotateBy=Vector3.new(0, 45, 0), color="gray", material=Enum.Material.Cobblestone  })
	xCoord = xCoord - archwayWidth / math.sqrt(2)
	zCoord = zCoord - archwayWidth / math.sqrt(2)
end
Next, add the additional layers of archways before continuing. Don't forget...You need to reset the xCoord back to the starting value before each subsequent while loop. And also, you will need to reset the zCoord back to its starting value as well since the while loop will make adjustments to that variable.


Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)

local xCoord = 300
local zCoord = 300

local archwayWidth = 30
while xCoord > -300 do
	s.archway(  { x=xCoord, y=13, z=zCoord, size=10, height=archwayWidth*1.6, width=archwayWidth, rotateBy=Vector3.new(0,45,0), color="gray", material=Enum.Material.Cobblestone  })
	xCoord = xCoord - archwayWidth / math.sqrt(2)
	zCoord = zCoord - archwayWidth / math.sqrt(2)
end

-- Layer 2
xCoord = 300
zCoord = 300
archwayWidth = 15
while xCoord > -300 do
	s.archway(  { x=xCoord, y=40, z=zCoord, size=10, height=archwayWidth*1.6, width=archwayWidth, rotateBy=Vector3.new(0,45,0), color="gray", material=Enum.Material.Cobblestone  })
	xCoord = xCoord - archwayWidth / math.sqrt(2)
	zCoord = zCoord - archwayWidth / math.sqrt(2)
end

-- Layer 3
xCoord = 300
zCoord = 300
archwayWidth = 8
while xCoord > -300 do
	s.archway(  { x=xCoord, y=55, z=zCoord, size=10, height=archwayWidth*1.6, width=archwayWidth, rotateBy=Vector3.new(0,45,0), color="gray", material=Enum.Material.Cobblestone  })
	xCoord = xCoord - archwayWidth / math.sqrt(2)
	zCoord = zCoord - archwayWidth / math.sqrt(2)
end
The Roman Colesseum was built around the year 600 B.C. and is about 600 feet in diameter.

Build a replica of the Colesseum by creating the archways in a circle using sine and cosine to determine the coordinates of each section.

Before you begin, you need to determine the number of archway sections needed. So, calculate the circumference of the Colesseum, then divide that by the width of the archway.

How many archways will be needed to create the circle of the Colesseum?

Now divide 360 degrees by the number of archways to find out how many degrees of the circle will be used by each archway.

Use a for loop with an incrementing variable. Increment from 0 to 360 by the number of degrees for each archway. In Roblox, you can specify the increment.
circumference = pi * diameter

Multiply the diameter of the Colesseum by pi to get its circumference. Then divide that circumference by the width of the archway (30 Roblox units).

One other important variable that we will need to use is: radius which represents the distance from the center of the circle to the edge of the circle. the radius is always 1/2 of the diameter.

If the Colesseum has a diameter of 600, what is its radius?

The basic code for your program should begin to look like the following:

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)
local radius = 300
local numArches = 63
for i=1,numArches do


end
Next, we will need to figure out how to position the archways using the sine and cosine functions in Roblox. These functions in Roblox use radians for input. A radian is related to degrees but there is a specific formula used to calculate radians. See below.

DO NOT JUST COPY AND PASTE THE CODE BELOW. INSTEAD, COPY-AND-PASTE THE CODE FROM THE ABOVE BOX AND THEN POSITION THE CURSOR IN THE LOOP AND TYPE ONLY THE FOLLOWING:

local radians =

The computer should recognize that you are trying to create an algorithm to calculate radians. The computer should then use its intelligence to guide you in the creation of the algorithm. Look at what the computer suggests. If you approve of the computer's suggestion, press the TAB key.

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)
local radius = 300
local numArches = 63
for i=1,numArches do
	local radians = i*math.pi*2/numArches
	local x = math.cos(radians)*radius
	local z = math.sin(radians)*radius
	s.archway(  { x=x, y=13, z=z, size=10, height=50, width=30, color="gray", material=Enum.Material.Cobblestone  })


end
The above code is very close to creating the Colesseum. We now need to rotate the archway parts. Add the following property assignment:

rotateBy=Vector3.new(0, 90 + i*360/numArches, 0)

The complete first layer of the Colesseum should appear as:

Example:
--Workspace.startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local s = require(ReplicatedStorage.genericShapes)
local radius = 300
local numArches = 63
for i=1,numArches do
	local radians = i*math.pi*2/numArches
	local x = math.cos(radians)*radius
	local z = math.sin(radians)*radius
	s.archway(  { x=x, y=13, z=z, rotateBy=Vector3.new(0, 90+ i*360/numArches, 0), size=10, height=50, width=30, color="gray", material=Enum.Material.Cobblestone  })
end
If the archways in the second layer have a width of 15, how many archways are needed to circle the Colesseum?

Add the new value for numArches and add another for loop. Don't forget to adjust the y-coordinate and the width, height, and size properties of the arches.

Your Turn


End of Document