Thursday 11 December 2008

Chunk 16 - With Code


The Chunk 16 application draws a random picture to the screen about every second. It weighs in at a little over 100 lines and can be run as an applet or an application. by simply clicking on an appropriate link. There is no need to struggle with applet permissions, digitally signing jar files or setting up a jnlp file for Java WebStart.
This chapter should help clarify the basics of programming, so you are ready to tackle the special features of Processing. We will go through each topic in order of difficulty, exploring each structure as we go.

Variables
Variables declared within a code block are only usable within that block. The local variables declared on lines 33 and 34 are usable at any point within the draw function. None of the other functions are aware of their existence.

Functions
Functions are extremely useful structures that allow us to use the same code in various places by calling the function's signature. The signature of a function is its first line. If we have a function with the signature:
void drawCircles(int a)
then we call it like so:
drawCircles(400);
If there is a function with the same name but a different signature it is known as an overloaded function:
drawCircles(int a, String col)
we could call the second overloaded function like so:
drawCircles(400, "red");

Code that is repeated frequently is an indication that a function is required. The repetitive portion of the code should be moved into a function with any parameters required. This helps with code reuse, making the program easier to maintain and cuts down the program's length. Any errors/modifications are confined to one place and can be easily corrected.
A simple function is usually used to change some global variable's value. It doesn't accept any parameters and returns no value. Here is an example of a simple Function .

//function changeColour: to change the stroke to a random colour, no parameters, no return value
void changeColour(){
stroke(random(255),random(255),random(255));}

It has a void return type and empty parameter brackets.
Some functions can be passed messages, informing them of value/s to change or use. drawCircles3(int) on line 118 accepts an integer which it uses to draw a circle using the ellipses method.
A function can be used to perform some calculation and return a value. These type of functions must contain the return type in their signature. The getSW function on line 135 returns an int value which is used to set the stroke weight.

//returns integer value for stroke weight depending on framecount
136: int fc=round(random(frameRate*500)/5);
137: //case statement with default value
138: switch(fc){
139: case 1:return 1;
140: case 2:return 2;
141: case 3:return 3;
142: case 4:return 4;
143: case 5:return 5;
144: case 6:return 6;
145: case 7:return 7;
146: case 8:return 8;
147: case 9:return 9;
148: default :return round(random(1,4));}}


Some functions can also accept parameters and return a value.
As well as user defined functions there are also inbuilt functions:
The size function accepts two integers which are used to set the size of the display window.
The rect(posx, posy, width, height) function accepts four parameters, which it uses to draw a rectangle.
The random function is an overloaded function because it can accept either one or two parameters and can return a float or an integer. In chunk 16 we mostly use integers so we need to round down any floats. Examples of using round(random(int, int)) can be found in many places in Chunk 16; as it outputs randomized patterns, by varying the values of stroke width, colour, height. shapes to use, width, interval and patternsPerPicture.

Code Blocks and the if statement
If there were no loops in our code, then our
programs wouldn't last very long, or they would
have to contain a huge amount of repetitive code.

Looping structures all consist of the same basic
format. A conditional statement followed by the
block of code to repeat.

The if block isn't really a looping construct, but as
it follows the same type of format we will discuss it
here.

The basic form of an if statement is as follows:

int r= round(random(1,4));
rectMode(RADIUS);
if(r==1){
noStroke();
rectMode(CENTER);
}

The functionality of an if block can be extended
by adding an else block:

else{

diameter=diameter/20;

stroke(round(random(255)),round(random(255)),round(random(255)),round(random(255)));

}

It can be even further extended with else if blocks.

void drawSquares(int diameter){
98: int r= round(random(1,4));
99: rectMode(RADIUS);
100: //if/if else/else block
101: if(r==1){
102: noStroke();
103: rectMode(CENTER);
104: }
105: else if(r==2){
106: diameter=diameter/20;
107: }
108: else if(r==3){
109: changeColour();
110: }
111: else{
112: diameter=diameter/20;
113: stroke(round(random(255)),round(random(255)),round(random(255)),round(random(255)));
114: }
116: rect(xpos,ypos,diameter,diameter);
117:}

The else and else if blocks cant be used on their own and must be preceeded by an if statement.
Examples of using if, else if and else consructs are on lines 36 and line 124.

While Loops

while blocks allow us to repeat a section of code until a condition or conditions become true or false. The opening clause contains the condition and this is followed by a statement block. Statement blocks, in looping structures, are always enclosed by curly brackets, unless there is only one such statement. Sometimes a while statement is used to increment a simple counter.
Here the loop is repeated 10 times and prints out the numbers 0-9.

Int count=0;
while(count 10){
This while loop calls the getInput function until the value of input is equal to the value of valid.

while(input!=valid)input=getInput();
Examples of while loops are on lines: 42, 51 and 57.
39: design=round(random(1,9)); //a random number for which design to use in the switch
40: switch(design){
41: case 1:
42: while(interval
43: drawCircle2(interval-round(random(40,width/2)));//call function drawCircle2 with a random parameter
44: interval+=round(random(3,70)); }
45: break;
46: case 2:
47: drawCircles(round(random(20,round(random(1,50))))); //call the drawCircles function
48: break;
49: case 3:
50: strokeWeight(getSW());//set the strokewright to the number returned by the getSW function
51: while(interval
52: drawCircle2(interval-round(random(1,width/2)));//use the drawCircle2 function
53: interval+=round(random(1,width/2));}
54: break;
55: case 4:
56: strokeWeight(getSW());
57: while(interval
58: drawCircle3(interval-round(random(2,width/4)));
59: interval+=round(random(1,30));}
60: break;

The draw method has a lot of while methods that seem to be performing a similar function.
A do while loop is very much like an upside down while loop. Do while loops are used when we need a block of code to execute at least once. This means that the statement block, is executed once, before the conditional is checked.
The layout of a do while loop has the following structure:
We haven't used a do while loop but it would be simple to implement if needed:
do{
do this statement//any amount of statements can be repeated in a statement block
do that statement
}
while(condition)


do{
drawCircle();
drawSquare();
}
while(count ,,,,

For Loops

The for loop allows us to specify the number of times a code block will repeat. We use an int to state the amount of iterations. A for loop takes a start value, an exit loop conditional clause and the amount the looping integer will be changed by. For loops can be incremented or decremented.
It sounds a lot more complex than it is in practice!

for(int n=0;n==10;n++) //starts at 0 - stops at 10 - increments by 1
{ //code block to be repeated is contained within curly brackets
function1(n);//performs some operation
a=+n; //performs some other operation (can contain many statements)
} // for loop ends


The statement block performs some operation/s until the exit condition is met.
In this example the integer is declared and initialised within the loop, making the integers scope local in the loop. However the integer can be declared elsewhere, thus changing its scope.
int n;
for (n=10;n <= 100;n=n+10){

function1(n); }
println("number's final value: "+n);
We use a for loop to control how many patterns are used to make a picture:

for(count=0;count
a great many statements to repeat!}


As long as count's value, is less than patternsPerPicture the statements in the for loop will execute. Each time the loop is repeated the value of n is increased by one. If we changed the loop to start at one, then we would need to change the conditional to count<=patternsPerPicture+1; When the for loop is finished the value of patternsPerPicture is changed to a random number between 2 and 50, the variables count and design are reset to zero before the for loop is entered again. Another example of the for loop can be seen in the drawCircles(int) function. Here the value of n is increased by the gap parameter. This for loop stops at a random number between the centre and edge of the display window.

Switch Blocks
Another branching structure uses a switch to determine which block of code to run. In Java the switch value must be an integer, a byte, a short, char or enumerated type.
Its basic outline follows this pattern:
switch(int){
case 1:
statements;
break;
case 2:
case 3:
statements;
break;
case 4:
statements;
return(someValue);
case 9:
case 5:
default:
statements;
}

In this example, if the switch statement has a value of 1, 3, or 4 then the statements in the code blocks for 1, 3 or 4 will execute. To stop them continuing and executing the code in the other cases, break is used to exit the switch. In the case of the switch having a value of 4, the loop is exited by returning a value. break and return allow the switch block to be exited cleanly.
If switch has a value of 2 it doesn't have a code block of its own, so it executes the case 3 code block before exiting with break.
If the switch has a value of 5, 9 or some number that doesn't have a case statement then the default case will be carried out before the block is exited.
In chunk 16 we have a large case statement (Line 40) which has cases for switch values of 1-9.
The design variable used in the switch is a random number between 1 and 9 inclusive.
In this section we will make chunk 16 more maintainable.
Firstly, it would be better if the pattern always started with a plain black background! Remove the statement on line 36 and add background(0) at line 35:
We can remove the code in case 5 to its own function(line 63). Lets try omitting the break statement so the lines function is always covered up by the pattern in case 6.
Much better, we will leave lines in now!
case 5: //lovely lines!
doLines();
case 6:
...

Place the doLines function with the other functions
void doLines(){
strokeWeight(getSW());
for(int b=1;b
int down=round(random(width/2));
int across=round(random(width/2));
line(xpos,ypos,across-b,down-b);
line(xpos,ypos,across+b+xpos,down-b);
line(xpos,ypos,across-b,width-down+b);
line(xpos,ypos,across+b+xpos,width-down+b);}}

Looking at our case statements we can see that cases 1,3,4,6,7 and 9 all use duplicate code. Our switch block would be much neater and easier to maintain, if we could encapsulate duplications in a function.
We will move case 1's code to a function called repeatShapes and place it with the other functions
case 1:
repeatShapes();
break;

The only difference between case 1 and case 3 is the interval variable. We can accommodate this by passing it as a parameter to the function.
It is now possible to remove all the while loops that use the drawCircles2 function with the new function. We still have while loops, that use the drawSquares and drawCircle3 functions. Lets deal with those cases too.
void repeatShapes(){
while(interval
drawCircle2(interval-round(random(40,width/2)));
interval+=round(random(3,70)); }}
We can place a noFill() statement to the start of the for loop and remove all noFill() statements from the switch block.
If the case that makes filled circles didn't change colour so often, we might see some more interesting shapes created.
By adjusting the repeatShapes function and removing some repetitive code from the case statements, our switch block now looks like this:
switch(design){
case 1:
repeatShapes(round(random(3,70)),'2');
break;
case 2:
drawCircles(round(random(20,round(random(1,50)))));
break;
case 3:
repeatShapes(round(random(1,width/2)),'2');
break;
case 4:
repeatShapes(width-round(random(2,width/4)),'3');

break;
case 5: //lovely lines!
doLines();
case 6:
repeatShapes(round(random(2,200)),'5');
break;
case 7:
repeatShapes(round(random(1,40)),'2');
break;
case 8:
default:
repeatShapes(width-round(random(1,width/2)),'4');
}

The one remaining change we could attempt is to do something about the cases where large filled shapes are drawn over the top of our funky patterns. Later in the book, we will learn how to use transparent colours, so for the moment lets just leave our switch statement as it is.
Examine the switch block in the getSW function, you should notice something strange... The whole switch block can be replaced by one statement!
The getSW function now looks like this:
int getSW(){
int fc=round(random(frameRate*500)/5);
return fc%10;
}

Our code is now much leaner and easier to maintain and this will help us when we start the next modification which will involve arrays.

Additional Function - inplementing an Array
An array can be thought of as a container, for a collection of objects, of the same type. This container, has an index, so that each object it holds can be fetched. An array may be declared like this:
int [] midxy;
Arrays can be filled in a variety of ways. The capacity of the array can be set when it is declared and initialized:
boolean [] recs=new boolean[9];
It can also be initialized by listing its members:
int [] midxy={100,300,500};
A quick way of populating an array is to use a for loop:

for(int r=0;r <>

recs[r]=false;}

The array recs contains 9 boolean values and we are going to use it to design a new function.
So far, all our patterns are positioned around the centre point of the screen.
We are going to use the midxy array to draw out a symetrical pattern,
in a grid like the tic tac toe game.
Copy the following code into processing and run it.
int [] midxy;

boolean [] recs;
void setup(){
size(600, 600);
drawA();}

void drawA(){
rectMode(CENTER);
recs=new boolean[9];
resetRecs();
int [] midxy={(width/3)/2, (width/3)/2+width/3, (width/3)/2+width/3*2 };
for(int n=0;n ,,,,,

As you can see this is a quick sketch of our new shape function.
Experiment by commenting and uncommenting the code between the asterisks.
The statement after the last line of asterisks, is drawing the square at the centre of the screen.
It is using the value in midxy[1] as its centre point.
Change one of the first 2 values in the rect function to a zero. Change both values to 2.
See if you can place the square in each position in the grid.
It would get tedious typing values like (width/3)/2+width/3*2 so we will put them in an array.
The values in midxy will find the centre of each of the 9 squares no matter what the size of the
display window is. (But it must be a square!) Try changing the size of the display window in the
setup method.
In order to keep our pattern symmetrical we are going to place the 9 squares into 3 groups.
There will be a corner group, a side group and the central square.
The 9 squares don't all have to be used in the pattern, so we are going to use boolean values,
to decide whether the squares are used. A boolean value can be true or false but not null.










Try to make a new function that uses the recs array to help manage drawing the 9 squares in
our grid. Call the function repeatSectors or any name you like.
We already have a function for populating the recs array with default values at the start of the
function.
Use random numbers to decide whether each of the 3 groups are populated.
Write some code that sets the value of the relevant array position to true if a shape will be
drawn there.
Write some code that uses the recs array to control where shapes will be drawn.
Use the rect(int,int,int,int)function to begin with.
Place a call to the repeatSectors function in the draw function.
Add some randomisation to make the patterns vary as much as possible.
If you get stuck use the reference section in Processing, Google what you are trying to do or
check the solution provided at the end of this section.
This is a very challenging function to write and could take a good few hours to solve but its
early days yet and this will get you thinking like a programmer.
When your super duper function is completed you can use your artistic flair to make it dazzling!


Author's Function
173:void drawSectors(){
174: resetRecs();
175: int [] midxy={(width/3)/2, (width/3)/2+width/3, (width/3)/2+width/3*2 };
177: //decide on groups to use
178: int corners=round(random(0,1));
179: if(corners==1){
180: recs[0]=recs[2]=recs[6]=recs[8]=true;}
182: int sides=round(random(0,1));
183: if(sides==1){
184: recs[1]=recs[3]=recs[5]=recs[7]=true;}
186: int centre=round(random(0,1));
187: if(centre==1){
193: rectMode(CENTER);
194: ellipseMode(CENTER);
195: int f=round(random(1,5));
196: if(f==1){
197: fill(random(255),random(255),random(255));}
198: else{
199: getSW();
200: changeColour();}
202: int d=round(random(width/3));
203: int sType=round(random(2));
204: for(int n=0;n<9;n++){ mod="n%3;" down="0;">5)down=2;
209: else if (n>2){down=1;}
211: if(sType==1){
212: rect(midxy[mod], midxy[down], d, d); }
213: else{
214: ellipse(midxy[mod], midxy[down], d, d);}}}

1 comment:

  1. Sorry about the stupid line spaces in this post but I fixed it earlier. After logging on again the formatting has gone really strange!
    It must be in the html but I dont have time to fix it tonight

    ReplyDelete