CSC 1012

Introduction to Computer Science

More Loops

Review Olymic Rings with Shifting Colors; more on drawing; introduction to arrays or lists; subscripts or indexes

Review of Olympic Rings With Shifting Colors

Rewrite Olympic.java so that the colors of the rings shift constantly.

The key to doing exercise 1 is to combine the random number generator with a call to Color with random arguments in the range of 0-255, or to use a random selection from an array of colors. Let us look at the second option.

The standard colors we can use in java are:

black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white and yellow for 13 colors in all. However, with a white background, a white circle would vanish, so we only use 12.

//File:	Olympic2.java
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class Olympic2 extends Applet
{
       // An array of colors to use at random
       Color [] myColors = { Color.black, Color.blue, Color.cyan,
                  Color.darkGray, Color.gray, Color.green,
                  Color.lightGray, Color.magenta, Color.orange,
                  Color.pink, Color.red, Color.yellow};


        // Use init to set to background white
        // to contract with the colors of the rings
        public void init()
        {
           setBackground (Color.white);
        }

	//The paint() method is called by the browser whenever the applet must be redrawn
	public void paint (Graphics g)
	{
		g.setColor (
                  myColors[(int)(Math.random()*(myColors.length-1.0+.5))]);
		g.drawOval (10, 10, 75, 75);
		
		g.setColor (
                  myColors[(int)(Math.random()*(myColors.length-1.0+.5))]);
		g.drawOval (95, 10, 75, 75);
		
		g.setColor (
                  myColors[(int)(Math.random()*(myColors.length-1.0+.5))]);
		g.drawOval (180, 10, 75, 75);
		
		g.setColor (
                  myColors[(int)(Math.random()*(myColors.length-1.0+.5))]);
		g.drawOval (53, 42, 75, 75);
		
		g.setColor (
                  myColors[(int)(Math.random()*(myColors.length-1.0+.5))]);
		g.drawOval (137, 42, 75, 75);
                try
                {
                  Thread.sleep(500);   // sleep for .5 sec
                }
                catch (InterruptedException t)
                {
                }
                repaint();  // calls update() to CLEAR THE WINDOW
                            // then calls paint()
	 }
}

Review of Exploding Balloons

Change the animations in Surprise2.java so that the circle grows larger and then grows smaller and disappears.  Spend some time on this one.  

Change the animations in Surprise3.java so that as the circle grows larger and then grows smaller and disappears, a label is applied which says "Small", "Medium", "Large", "XL", "XXL", as appropriate.  Hint: it can help to use an array. Spend some time on this one.

The answers to the other two exercises build on each other. We will only examine the combined answer:

//File: Surprise4.java import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Surprise4 extends Applet
{
//Exploding and imploding balloons
//With Small, Medium, Large, .. added

public void init()
{
  setBackground (Color.white);
}

public void paint (Graphics g)
{
// Added array of size_labels
String [] size_labels = {
  " Small ", "Medium", "Large", "XL", "XXL" };
// Note the extra blanks on Small
Dimension size = getSize();
int size_bin; // Added index for size_labels

int diameter = 0,
  w = size.width,
  h = size.height,
  cmin = (int)Math.min(w, h);

// g.setColor(Color.yellow); // was here
while (diameter < cmin)
{
int ws, hf; // needed to position labels
Font current; // note the different style
FontMetrics metrics; // when we do this later

g.setColor(Color.yellow); //now here
g.fillOval((w-diameter)/2, (h-diameter)/2,
  diameter, diameter);
// Note it is important to do this
size_bin = (5*diameter)/cmin;
// Before this
diameter++;

// Added code for the size labels
current = getFont();
metrics = getFontMetrics (current);
ws = metrics.stringWidth(size_labels[size_bin]);
hf = metrics.getHeight();

g.setColor(Color.blue);
g.drawString(size_labels[size_bin],
(w-ws)/2, (h+hf)/2);

try
{
  Thread.sleep(10); // sleep for 10 msec
}
catch (InterruptedException t)
{
}
}

g.setColor(Color.yellow); //need to draw oval again
//to overwrite old labels
g.fillOval((w-diameter)/2, (h-diameter)/2,
diameter, diameter);
Font current = getFont();
FontMetrics metrics = getFontMetrics (current);
  int ws = metrics.stringWidth("Surprise!");
  int hf = metrics.getHeight();

g.setColor(Color.red);
g.drawString("Surprise!", (w-ws)/2, (h+hf)/2);

try
{
Thread.sleep(1000); // sleep for 1 sec
}
catch (InterruptedException t)
{
}

while (diameter > 1)
{
//The following draws circles in yellow,
//pauses, then redraws them in white (the background color),
//thus effectively erasing them. It then changes the color
//back to yellow and repeats the process for a circle
//of diameter one less than the previous one.

//Note that we have to rewrite the labels in white
//or medium will show behind small

// int ws, hf; // already declared above
// Font current;
// FontMetrics metrics;

g.setColor(Color.yellow);
g.fillOval((w-diameter)/2, (h-diameter)/2,
diameter, diameter);
// Note the -1
size_bin = (5*(diameter-1))/cmin;

// Added code for the size labels again
current = getFont();
metrics = getFontMetrics (current);
  ws = metrics.stringWidth(size_labels[size_bin]);
  hf = metrics.getHeight();

g.setColor(Color.blue);
g.drawString(size_labels[size_bin],
(w-ws)/2, (h+hf)/2);
try
{
  Thread.sleep(10); // sleep for 10 msec
}
catch (InterruptedException t)
{
}

g.setColor (Color.white);
g.fillOval((w-diameter)/2, (h-diameter)/2,
diameter, diameter);
g.drawString(size_labels[size_bin],
(w-ws)/2, (h+hf)/2);

diameter--;

}

repaint(); //calls update() which CLEARS THE WINDOW,
  //then calls paint()
}
}

More on Drawing

Java has solid basic drawing capabilities defined in the Graphics class. Let us look at an applet to draw a crude expanding head instead of a balloon to understand some of the graphics methods and see two problems: image flicker caused by partial updates and the non-intuitive coordinate system used.

//File: Head.java import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Head extends Applet {
//Surprise with an exploding balloon
// Author: rachel McDermott, September 27, 1996
//Modified to be a head
// H. J. Bernstein, 11 November 2001

public void init() {
  setBackground (Color.white);
}

public void paint (Graphics g) {
Dimension size = getSize();
int diameter = 0,
  cx = size.width,
  cy = size.height,
  cmin = (int)(.8*Math.min(cx, cy));
  // note: we adjust the size of the
  // head to leave room for the ears

while (diameter < cmin) {

// Draw hair
g.setColor(Color.red);
g.fillOval(cx/2-diameter/6,(cy+diameter)/2-diameter/6,
  diameter/3, diameter/3);

// Draw the head itself

g.setColor(Color.yellow);
g.fillOval((cx-diameter)/2, (cy-diameter)/2,
  diameter, diameter);

// Draw two ears

g.fillOval((cx-diameter)/2-diameter/10,cy/2-diameter/8,
  diameter/5,diameter/4);
g.fillOval((cx+diameter)/2-diameter/10,cy/2-diameter/8,
  diameter/5,diameter/4);

// Draw brow line and smile

g.setColor(Color.blue);
g.drawArc((cx-3*diameter/4)/2,(cy-3*diameter/4)/2,
  3*diameter/4,3*diameter/4, 50, 80);
g.drawArc((cx-3*diameter/4)/2,(cy-3*diameter/4)/2,
  3*diameter/4,3*diameter/4, -50, -80);

// Now draw two eyes
g.setColor(Color.blue);
g.drawOval((cx-3*diameter/8-diameter/5)/2,(cy-diameter/10)/2,
  diameter/5,diameter/10);
g.fillOval((cx-3*diameter/8-diameter/10)/2,(cy-diameter/10)/2,
  diameter/10,diameter/10);
g.drawOval((cx+3*diameter/8-diameter/5)/2,(cy-diameter/10)/2,
  diameter/5,diameter/10);
g.fillOval((cx+3*diameter/8-diameter/10)/2,(cy-diameter/10)/2,
  diameter/10,diameter/10);

diameter++;
try {
  Thread.sleep(20); // sleep for 20 msec
} catch (InterruptedException t){}
}

Font current = getFont();
FontMetrics metrics = getFontMetrics (current);
int ws = metrics.stringWidth("Surprise!");
int hf = metrics.getHeight();

g.setColor(Color.red);
g.drawString("Surprise!", (cx-ws)/2, (cy+hf)/2);
try {
Thread.sleep(1000); // sleep or 1 sec
} catch (InterruptedException t) {}
repaint();
}
}

The hair has been drawn as a beard. That is because java uses a default coordinate system which is upside-down compared to the one commonly used in when laying out drawings by hand or drawing graphics in a physics class. The point with the coordinates (0,0) is at the top left and increasing y-coordinates go down the screen. This arises from the natural conflict between laying out images, for which it is common to place (0,0) at the bottom left corner, and laying out text, in which the first character of the first line is normally placed at the top left. Unless we introduce special transformations, when writing java, we have to use the text-oriented convention.

On most computers, the applet will show a disturbing flicker of partially drawn images. In the next applet, we cure the inverted image by changing a + to a -, and we cure the flicker by drawing using a Graphics object in memory and then transferring all the pixels at once to the displayed Graphics object:

//File: BufferedHead.java
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.applet.*;

public class BufferedHead extends Applet {
//Surprise with an exploding balloon
// Author: rachel McDermott, September 27, 1996
//Modified to be a head
//Modified to use an off-screen buffer
// H. J. Bernstein, 11 November 2001

public void init() {
  setBackground(Color.white);
}

public void paint (Graphics g_real) {
Image image_g;
Graphics g;
Dimension size = getSize();
int diameter = 0,
  cx = size.width,
  cy = size.height,
  cmin = (int)(.8*Math.min(cx, cy));
  // note: we adjust the size of the
  // head to leave room for the ears

image_g = createImage(cx,cy);
g = image_g.getGraphics();

while (diameter < cmin) {

// Draw hair
g.setColor(Color.red);
g.fillOval(cx/2-diameter/6,(cy-diameter)/2-diameter/6,
  diameter/3, diameter/3);

// Draw the head itself
g.setColor(Color.yellow);
g.fillOval((cx-diameter)/2, (cy-diameter)/2,
  diameter, diameter);

// Draw two ears

g.fillOval((cx-diameter)/2-diameter/10,cy/2-diameter/8,
  diameter/5,diameter/4);
g.fillOval((cx+diameter)/2-diameter/10,cy/2-diameter/8,
  diameter/5,diameter/4);

// Draw brow line and smile

g.setColor(Color.blue);
g.drawArc((cx-3*diameter/4)/2,(cy-3*diameter/4)/2,
  3*diameter/4,3*diameter/4, 50, 80);
g.drawArc((cx-3*diameter/4)/2,(cy-3*diameter/4)/2,
  3*diameter/4,3*diameter/4, -50, -80);

// Now draw two eyes
g.setColor(Color.blue);
g.drawOval((cx-3*diameter/8-diameter/5)/2,(cy-diameter/10)/2,
  diameter/5,diameter/10);
g.fillOval((cx-3*diameter/8-diameter/10)/2,(cy-diameter/10)/2,
  diameter/10,diameter/10);
g.drawOval((cx+3*diameter/8-diameter/5)/2,(cy-diameter/10)/2,
  diameter/5,diameter/10);
g.fillOval((cx+3*diameter/8-diameter/10)/2,(cy-diameter/10)/2,
  diameter/10,diameter/10);
g_real.drawImage(image_g,0,0,this);
diameter++;
try {
Thread.sleep(20);   // sleep for 20 msec
} catch (InterruptedException t){}
}

Font current = getFont();
FontMetrics metrics = getFontMetrics (current);
  int ws = metrics.stringWidth("Surprise!");
  int hf = metrics.getHeight();

g.setColor(Color.red);
g.drawString("Surprise!", (cx-ws)/2, (cy+hf)/2);
g_real.drawImage(image_g,0,0,this);
try {
Thread.sleep(1000); // sleep or 1 sec
} catch (InterruptedException t) {}
repaint();
}
}

Arrays

We began with a motivational example.  Suppose you want to calculate the average of a list of numbers (int or double) followed by a calculation of the standard deviation.  You quickly realize that by the time you're ready to do the standard deviation, you only have access to the last number in the list.  The solution is provided by arrays since they allow you to name a collection of memory locations with one identifier.  Subscripts or indexes then allow you to access each individual memory location.  The following example illustrates this.

//File:    NumberList.java
//This class will gather together in one place
//functions (or methods) which process lists of
//numbers (int or double).

import iostuff.*;
public class NumberList
{
    //The following two methods calculate the average of a list
    //of int's or double's respectively
    public static int average (int [] a)
    {
        int sum=0;
        for (int k=0; k<a.length; k++)
        {
            sum = sum + a [k];
        }
        return sum/a.length;
    }
   
    public static double average (double [] a)
    {
        double sum=0;
        for (int k=0; k<a.length; k++)
        {
            sum = sum + a [k];
        }
        return sum/a.length;
    }
}


01: //File:  Average2.java
02: import CSLib.*;
03: import java.text.DecimalFormat;
04: public class Average2
05: {
06:         public static void main (String [] args)
07:         {
08:                 InputBox in;
09:                 OutputBox out;
10:                 double number [];
11:                 int n;
12: 
13:                 in = new InputBox();
14:                 out = new OutputBox();
15:         
16:                 in.setPrompt ("How many numbers? ");
17:                 n = in.readInt();
18:         
19:                 number = new double [n];
20:                 
21:                 //Inputs the numbers into the array
22:                 for (int k=0; k<n; k++)
23:                 {
24:                         in.setPrompt("Next score please: ");
25:                         number [k]= in.readInt();
26:                 }
27: 
28:                 //The following code displays the list to the screen
29:                 for (int k=0; k<number.length; k++)
30:                 {
31:                         out.println (number[k]);
32:                 }
33:                 out.println();
34: 
35:                 double avg = NumberList.average(number);
36:                 
37:                 DecimalFormat output = new DecimalFormat("0.00");
38:                 out.println ("The average is: " + output.format(avg));
39:         }
40: }

For those using older versions of the text, this is a client program:
//File:    Average.java
import iostuff.*;
import java.text.DecimalFormat;
public class Average
{
    public static void main (String [] args)
    {
        double number [];
   
        System.out.print ("How many numbers? ");
        int n = Keyboard.readInt();
   
        number = new double [n];
       
       //inputs the numbers into the array
        for
(int k=0; k<n; k++)
        {
            System.out.print("Next score please: ");
            number [k]= Keyboard.readInt();
        }

        //The following code displays the list to the screen
        for (int k=0; k<number.length; k++)
        {
            System.out.println (number[k]);
        }
        System.out.println();

        double avg = NumberList.average(number);
       
        DecimalFormat output = new DecimalFormat("0.00");
        System.out.println ("The average is: " + output.format(avg));
    }
}

Note that arrays "know" their length.  That is, it is not necessary to pass the number of items as a parameter to any of the methods in NumberList.   Instead, when that information is required, the length of the array is retrieved from the array itself; e.g., a.length or number.length.


Lab Exercise 1.  Add a method that displays a list of int's to the NumberList class.  The signature for the method will look like:

public static void display (int [] a)

HINT:  This method should incorporate the section of Average.java (above) that "displays the list to the screen".


Lab Exercise 2.  Add a method that calculates the standard deviation of a list of int's to the NumberList class.  The signature for the method will look like:

public static int sd (int [] a, int average)

Feel free to consult Prof. Steinmetz's version if you get exasperated.


Adapted from Bill Steinmetz's course summary by H. J. Bernstein, 12 Nov 01

We follow up here