Jan 23: Iteration
Loops
Suppose we wanted to draw a row of "stepping stones" from left to right across the screen. Here's a simple way to do that:
ellipse(0,50,10,10); ellipse(25,50,10,10); ellipse(50,50,10,10); ellipse(75,50,10,10); ellipse(100,50,10,10);
[applet]
This approach suffers from several problems. First off, what if we change the size of the window? Or what if we want them be to spaced closer together? Furthermore, it's pretty silly to type almost the same code again and again, with just a minor variation. What if we were drawing something more complex than a circle?
A programming construct called a "loop" helps us out. A loop executes a particular piece of code again and again, until some condition tells it to stop. For the stepping stones example, we could use a variable to keep track of where to put the next stone, and on each iteration, draw the stone and advance to the next position.
Here's how to do that using a while loop (as usual, see the Programming notes for syntax details).
int x = 0; // initial value while (x <= 100) { // keep going as long as this is true ellipse(x,50,10,10); // do something for this iteration x = x+25; // update for next iteration }
[applet]
If we "unroll" the loop, it's essentially doing the following:
int x = 0; // First iteration: x = 0 <= 100 so continue ellipse(x,50,10,10); x = x+25; // Second iteration : x = 25 <= 100 so continue ellipse(x,50,10,10); x = x+25; // Third iteration : x = 50 <= 100 so continue ellipse(x,50,10,10); x = x+25; // Fourth iteration : x = 75 <= 100 so continue ellipse(x,50,10,10); x = x+25; // Fifth iteration : x = 100 <= 100 so continue ellipse(x,50,10,10); x = x+25; // Now x = 125 > 100 so stop
We see three main parts to the loop: initialize variables before the start of the loop; test a boolean condition each iteration, to see whether to continue; perform the body of the loop as long as the condition is true. Typically the body contains some way to make progress (here, incrementing x by 25), so that the test will eventually be false. By varying each of these parts, we can produce different results -- try starting x at 25, stopping when x < 100, incrementing x by 20, drawing a line up from the current x position, etc.
There's an alternative way to express loops, called a for loop, that brings together in a single line the initialization, continuation test, and update. Here's the stepping stone sketch written that way.
for (int x = 0; x <= 100; x = x+25) { ellipse(x,50,10,10); }
[applet]
Here are some more examples of using loops to create various effects. Try your own variations on how to loop and what to do inside the loop.
size(300,300); // Create lines from points; try varying the spacing (R&F 6-05) for (int x=0; x<100; x+=10) { point(x, 5); } for (int x=100; x<200; x+=5) { point(x, 5); } for (int x=200; x<300; x++) { point(x, 5); } // Spacing can be geometric for (float x=1; x<300; x*=1.2) { point(x, 20); } // Add some noise (Greenberg 6-07) for (int x=0; x<100; x++) { point(x, 50+random(-5,5)); } // Spreading (Greenberg 6-08) float h=0, dh=0.2; for (int x=0; x<100; x++) { point(150+x, 50+random(-h,h)); h += dh; } // Gradient -- change color with position (R&F 6-04) for (int x=0; x<256; x+=2) { stroke(x); line(x, 100, x, 150); } // Random-colored bullseye (based on R&F 6-03) stroke(0); smooth(); for (int d=100; d > 0; d -= 20) { fill(random(255),random(255),random(255)); ellipse(150, 230, d, d); }
[applet]
One last example of what we can do by varying the aspects of a loop, based on Greenberg (10-7). To draw a worm, we loop down its body, drawing an ellipse for each segment. The ellipses grow and shrink, and their centers move sinusoidally.
size(500, 200); smooth(); noFill(); background(255); stroke(65, 10, 5); strokeWeight(.2); int dx=2; float da=radians(2.55); // converting x to angle for sine float radius=0, dr=0.35; // worm segment size and step float x=0, y=height/2; while (x<width) { ellipse(x-radius/2, y-radius/2, radius*.75, radius); x += dx; y += 0.5*sin(x*da); radius += dr; if (x==width/2) dr*=-1; // start shrinking segments }
[applet]
Interaction
All the loops so far have been in static sketches. As with other sketches involving multiple elements, all the elements produced by a loop appear simultaneously. This can be contrasted with motion sketches where, for example, we would make a ball move across the screen step by step, perhaps even at the same spacing as in the loops. In that case, the draw() body is essentially doing a loop, with each frame being one iteration, drawn at a separate tick.
We can of course use a loop inside the draw() body (and it's still the case that all elements appear at once). Shiffman 6-9 provides one example, in which vertical rectangles are drawn, with fill color set by distance from the mouse. The following variation works with circles.
void setup() { size(400,400); smooth(); background(0); } void draw() { // Fade away fill(0,5); noStroke(); rect(0,0,width,height); // Circles surrounding mouse position noFill(); for (int d=10; d<200; d+=20) { stroke(255-d); ellipse(mouseX,mouseY, d,d); } }
[applet]
As another example, we can draw a bunch of random points near the mouse press, for a spraypaint-like effect. Note the use of the dist() call to make sure that the points are in a circle; else the brush is a square.
int num=100; // how many points int sz=10; // how big an aerosol boolean rand=false; // all points random colors? void setup() { size(400,400); smooth(); background(0); noFill(); stroke(255); } void draw() { if (mousePressed) { for (int i=0; i<num; i++) { // Generate point float x = mouseX+random(-sz,sz); float y = mouseY+random(-sz,sz); // Is it in the circle if (dist(x,y,mouseX,mouseY) < sz) { if (rand) stroke(random(255),random(255),random(255)); point(x, y); } } } } void keyPressed() { if (key=='c') { // random color rand = false; stroke(random(255),random(255),random(255)); } else if (key=='r') { // random colors rand = true; } else if (key=='Z') { // bigger size if (sz<100) sz++; } else if (key=='z') { if (sz>0) sz--; } else if (key=='P') { // more points if (num<1000) num+=10; } else if (key=='p') { if (num>10) num-=10; } }
[applet]
Programming notes
- While loop
- A while loop has the form
The initialization says what to do before the loop starts. One common initialization step is to declare and initialize a variable that will count how far through the loop we've gone, e.g., "int i=0". By convention, such loop counters are often named "i" and "j". The update is an expression that is intended to make progress through the loop, e.g., "i++". The continuation test is a boolean expression that is tested before conducting the next iteration of the loop, e.g., "i < num". If it is true, the loop body is executed (again), else it is terminated.
initialization while (continuation test) { statements, typically including update } - For loop
- A for loop has the form
The initialization says what to do before the loop starts. It can include multiple steps (separated by commas), but that can get a bit confusing to read. One common initialization step is to declare and initialize a variable that will count how far through the loop we've gone, e.g., "int i=0". By convention, such loop counters are often named "i" and "j". The update is an expression that is intended to make progress through the loop, e.g., "i++". It is performed after the statements in the body of the loop, and before the continuation test. The continuation test is a boolean expression that is tested before conducting the next iteration of the loop, e.g., "i < num". If it is true, the loop body is executed (again), else it is terminated. Any of these parts can be empty; the semicolons must still be there.
for (initialization; continuation test; update) { statements }
Variables declared in a loop initialization are useable only within the loop (and can likewise be reused elsewhere without conflict). Variables can be declared within the body of a loop or a conditional, and are local to that block of code. One way to think about it is as if the variables "live" only within the curly braces where they are declared.