Pre-requisite: it is assumed
that you have read through
the prior tutorials, and are familiar with the concepts covered in those
tutorials.
Goals:
In this tutorial, we will:
Examine a nested for loop, which consists of one for loop nested inside
another for loop. Nested for loops are useful for, amongst other
things, dealing with two-dimensional situations and data.
1. Obtain the example code
Here is the zip file to the source
files and compiled executable of this example.
Download and unzip the zip file and you will see an ExampleProgram folder. Open the ExampleProgram
folder, the EXE folder contains the compiled program and you can double click on the .sln
file to work with the source code.
When the game starts, you'll see a screen that
looks similar to this:
By using the left thumbstick, you can adjust both the
horizontal space between the circles, and the vertical space between the
circles.
2. Examining The Program:
Let's examine the
C# source code that produces the behavior we see on-screen
CreateABallAt():
///
<summary>
///
Allocates and initialize the memory for a ball at
///
xPos and xPos and with aTex texture
///
</summary>
///
<param name="xPos">x-location
to create the new ball</param>
///
<param name="yPos">y-location
to create the new ball</param>
private
void
CreateABallAt(float
xPos, float
yPos)
{
XNACS1Circle aBall =
new
XNACS1Circle();
aBall.Center =
new
Vector2(xPos,
yPos);
aBall.Radius =
BALL_RADIUS;
// aBall.Label = xPos +"/" + yPos;
aBall.Label =
String.Format("{0:0},{1:0}",
xPos, yPos);
}
This method creates a new circle object, so that the new circle's center
is at (xPos, yPos). Just about everything should be
extremely familiar, except for the code that formats the
numbers that are being displayed on the circles:
aBall.Label =
String.Format("{0:0},{1:0}",
xPos, yPos);
If you replace this line with the commented-out line above it, you'll
notice that the numbers are displayed exactly on the screen. Since each
number tends to have a lot of digits after the decimal place, this means
that you commonly get labels like
"4.899999,6.200001",
which tend to overlap with the circles on either side of the one that has
the label.
By using the
String.Format method, and formatting
strings like {0:0}, we can specify not only which parameter we want (the
zeroth one - xPos), but the number after the semi-colon tells us what sort
of format to use (in this case, the :0 means just the digits to the left of
the decimal point, and no digits after)
UpdateWorld():
RemoveAllFromDrawSet();
//
remove previously created balls
The relevant, new code is shown above. This program uses the same
two-step approach that most of the other tutorials in this chapter have
used: first remove everything from the screen, then create new objects on
the screen (as a hack that allows us to avoid using arrays, yet still create
dynamic programs). The following line accomplishes that first step by
removing all prior objects:
RemoveAllFromDrawSet();
// remove
previously created balls
After having removed all the current circles from the screen, the
program then goes on to adjust the horizontal distance between circles (m_XSpace),
and the vertical distance (m_YSpace), based on user input.
// update x/y space with
left thumbstick
Having done that, the program then proceeds onwards to the nested loops.
While these may look tricky, they're essentially just a composition of a for
loop (which you've seen, and understand) inside another for loop.
Nothing about how the for loops execute changes, just because one is
composed with another.
In order to work through this concept, it may help to think of the inner
loop as a 'black box' that does work, and is merely repeated by the outer
loop. For example, in the following picture, we've surrounded the
entire, interior loop with the green box, in order
to indicate that no whatever the inner loop does, the outer loop will simply
ensure that the inner loop is repeated.
The primary benefit to thinking about nested loops in this way is that
it will help you to figure out what the inner loop (in green) does independently of
what the outer loop (in blue) does, and then to (somewhat) separately figure out what the outer
loop does.
Looking at the inner loop, we can see that it creates a vertical column
of circles.
You'll see that the inner loop calls the CreateABallAt method, passing
in the xPos and yPos values. After you've examined the
CreateABallAt method, you can see that it creates a new circle whose circle
is at the location (xPos,yPos).
The inner loop starts yPos at zero, and then increases as the inner loop
iterates.
xPos will have some value (determined by the outer loop), but since the
inner loop doesn't change the value of xPos, xPos may as well be a constant
value (as far as the inner loop is concerned).
Thus, at a given value of xPos (say, 10.0f), the inner loop will create
circles at (say) (10.0f, 0.0f), then (10.0f, 7.0f), then (10.0f,
14.0f), etc, etc.
Therefore, we can see that the inner loop will create a vertical column
of circles
Looking at the outer loop, we can see that it repeatedly increases the
value of xPos, and then executes the inner loop.
Since the inner loop used xPos to figure out where to put the vertical
column, we can see that the inner loop creates all the circles for one
vertical column, and the outer loop repeatedly 'moves the column over' by
increasing xPos.
Let's examine the details of how the for loop operates by tracing through
the execution of the first couple of iterations in detail
Just like we did for the normal for loop, the initialization part of
the outer for loop is executed exactly once. It is always executed,
and it it is executed before the first check of the condition:
for (float
xPos = 0; xPos < World.WorldMax.X; xPos += m_XSpace)
Variable Name
Value
xPos
0.0f
Next, the first iteration of the outer loop begins:
At the start of each iteration of the loop, the condition is checked:
for (float
xPos = 0; xPos < World.WorldMax.X; xPos +=
m_XSpace)
This asks "Is the current value of
xPos (which is 0.0f) less than 100.0f?" This is true, so the program will execute the body of the
loop.
The body of the loop consists of the following lines of code:
for
(float
yPos = 0; yPos < World.WorldMax.Y; yPos += m_YSpace)
{
CreateABallAt(xPos, yPos);
}
Let's examine the details of how the inner for loop operates by tracing through
the execution of the first couple of iterations in detail
Just like we did for the normal for loop, the initialization part of
the inner for loop is executed before the loop iterates.
Technically, it is executed only once, except that when the outer loop
causes this code to be run again, that second time will count as a "new
loop", and therefore will be executed again.
Next, the first iteration of the inner loop begins:
At the start of each iteration of the loop, the condition is checked:
for (float
yPos = 0; yPos < World.WorldMax.Y; yPos += m_YSpace)
This asks "Is the current value of
yPos (which is 0.0f) less than 100.0f?" This is true, so the program will execute the body of the
inner loop.
The body of the loop consists of the following line of code:
CreateABallAt(xPos, yPos);
The above code will place a
soccer ball on the screen, so that it's center point is (0.0f, 0.0f).
After the body of the loop has finished executing, then the counting
expression is executed
for (float
yPos = 0; yPos < World.WorldMax.Y; yPos += m_YSpace)
which
increase the value stored in yPos by m_YSpace (which is 5.0f, unless the
user has changed it):
Variable Name
Value
xPos
0.0f
yPos
5.0f
Next, the second iteration of the inner loop begins:
At the start of each iteration of the loop, the condition is checked:
for (float
yPos = 0; yPos < World.WorldMax.Y; yPos += m_YSpace)
This asks "Is the current value of
yPos (which is 5.0f) less than 100.0f?" This is true, so the program will execute the body of the
inner loop.
The body of the loop consists of the following line of code:
CreateABallAt(xPos, yPos);
The above code will place a
soccer ball on the screen, so that it's center point is (0.0f, 5.0f).
As you can see, the Y value is higher up than the circle we created in the
previous loop. This is as we predicted - the inner loop is creating
a vertical column of circles.
After the body of the loop has finished executing, then the counting
expression is executed
for (float
yPos = 0; yPos < World.WorldMax.Y; yPos += m_YSpace)
which
increase the value stored in yPos by m_YSpace (which is 5.0f, unless the
user has changed it):
Variable Name
Value
xPos
0.0f
yPos
10.0f
The loop continues to iterate, until the vertical column has gone all
the way to the top of the screen, at which point the inner loop stops
repeating.
After the inner loop (which is body of the outer loop) has finished executing, then the counting
expression is executed
for
(float
xPos = 0; xPos < World.WorldMax.X; xPos += m_XSpace)
which
increases the value stored in xPos by the value stored in m_XSpace (7.0f,
unless the user has changed it)
Variable Name
Value
xPos
7.0f
yPos
< inaccessible >
You'll notice that yPos is now marked as being
< inaccessible >.
This is because the yPos variable is declared inside the inner loop, and
therefore cannot be accessed except by code inside the inner loop.
Next, the SECOND iteration of the outer loop begins:
At the start of each iteration of the outer loop, the condition is checked:
for (float
xPos = 0; xPos < World.WorldMax.X; xPos +=
m_XSpace)
This asks "Is the current value of
xPos (which is 7.0f) less than 100.0f?" This is true, so the program will execute the body of the
loop.
The body of the loop consists of the following lines of code:
for
(float
yPos = 0; yPos < World.WorldMax.Y; yPos += m_YSpace)
{
CreateABallAt(xPos, yPos);
}
Let's examine the details of how the inner for loop operates by tracing through
the execution of the first couple of iterations in detail
Just like we did for the normal for loop, the initialization part of
the inner for loop is executed before the loop iterates. We
will execute this code again, because we need to reinitialize the code for
this particular run of the loop. We will execute it only once for
this set of iterations, just like we only executed it once for the
previous time we ran the inner loop:
Next, the first iteration of the inner loop begins:
At the start of each iteration of the loop, the condition is checked:
for (float
yPos = 0; yPos < World.WorldMax.Y; yPos += m_YSpace)
This asks "Is the current value of
yPos (which is 0.0f) less than 100.0f?" This is true, so the program will execute the body of the
inner loop.
The body of the loop consists of the following line of code:
CreateABallAt(xPos, yPos);
The above code will place a
soccer ball on the screen, so that it's center point is (7.0f, 0.0f).
After the body of the loop has finished executing, then the counting
expression is executed
for (float
yPos = 0; yPos < World.WorldMax.Y; yPos += m_YSpace)
which
increase the value stored in yPos by m_YSpace (which is 5.0f, unless the
user has changed it):
Variable Name
Value
xPos
7.0f
yPos
5.0f
Next, the second iteration of the inner loop begins:
At the start of each iteration of the loop, the condition is checked:
for (float
yPos = 0; yPos < World.WorldMax.Y; yPos += m_YSpace)
This asks "Is the current value of
yPos (which is 5.0f) less than 100.0f?" This is true, so the program will execute the body of the
inner loop.
The body of the loop consists of the following line of code:
CreateABallAt(xPos, yPos);
The above code will place a
soccer ball on the screen, so that it's center point is (7.0f, 5.0f).
As you can see, the Y value is higher up than the circle we created in the
previous loop. Further, since the outer loop moved xPos over by
7.0f, the new circles are just to the right of the first column. We
can see that the inner loop does, indeed create a column of circles, and
the outer loop does, indeed, ensure that each column has been 'moved over'
from the previous column.
After the body of the loop has finished executing, then the counting
expression is executed
for (float
yPos = 0; yPos < World.WorldMax.Y; yPos += m_YSpace)
which
increase the value stored in yPos by m_YSpace (which is 5.0f, unless the
user has changed it):
Variable Name
Value
xPos
7.0f
yPos
10.0f
The loop continues to iterate, until the vertical column has gone all
the way to the top of the screen, at which point the inner loop stops
repeating.
After the inner loop (which is body of the outer loop) has finished executing, then the counting
expression is executed
for
(float
xPos = 0; xPos < World.WorldMax.X; xPos += m_XSpace)
which
increases the value stored in xPos by the value stored in m_XSpace (7.0f,
unless the user has changed it)
Variable Name
Value
xPos
14.0f
yPos
< inaccessible >
The THIRD iteration of the outer loop will then cause the third column
to be drawn on the screen.
The outer (and inner) loops will be executed a substantial number of
times; we will not trace through the entire execution here.
FURTHER EXERCISES::
Start from a blank starter project (1000.201, if you need it), and re-do
the code from memory as much as possible. On your first try, do what
you can, and keep the above code open so that when you get stuck, you can
quickly look up what you forgot (and that after you finish a line, so that
you can compare your line to the 'correct' line). On the next try, do
the same thing, but try to use the finished code less. Repeat this
until you can type everything, without refering the tutorial's code.
Repeat this exercise daily for several days, so that you really get the
hang of this. As you go on, periodically review this by re-doing this
exercise.
Finding & Fixing Bugs In The Program
For this exercise, you should use the same project that was explained in
the above tutorial.
What happens if you keep telling the program to decrease the horizontal (or
vertical) space with left thumb stick? Why is this happening? How
can you fix this problem from happening?
Practicing With Nested Loops: Reimplement the inner loop as a normal
while loop
For this exercise, you should use the same project that was explained in
the above tutorial.
You've previously seen that you can take any for loop, and transform it back
into a while loop. You should do this with the inner loop, so
that you have a while loop nested inside a for loop.
Practicing With Nested Loops: Reimplement the inner loop as a do while
loop
For this exercise, you should use the same project that was explained in
the above tutorial.
You've previously seen that you can take any for loop, and transform it back
into a while loop. From there, you can transform it into a do...while
loop that will work correctly for this particular program. You should do this with
the inner loop, so that you have a do while loop nested inside a for
loop.
Practicing With Nested Loops: Display the row and column, instead of the
location
For this exercise, you should use the same project that was explained in
the above tutorial.
Modify the provided, example code so that the labels shows the row and
column, instead of the location. Once you're done, the lower left ball
will show 0,0, and all balls on the second column will show 1,0, then 1,1,
then 1,2, etc.