CS 2, Winter 2009
Programming for Interactive Digital Arts

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);
screenshot[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
}
screenshot[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);
}
screenshot[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);
}
screenshot[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
}
screenshot[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);
  }
}
screenshot[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;
  }
}
screenshot[applet]

Programming notes

While loop
A while loop has the form
initialization
while (continuation test) {
  statements, typically including update
}
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.
For loop
A for loop has the form
for (initialization; continuation test; update) {
  statements
}
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.
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.