Second Summer 2002


Lab Notes Thirteen: Applications of Threads.
We start with this program:
class One {
    public static void main(String[] args) {

	Two larry = new Two("Larry Bird", 7000); 
	Two michael = new Two("Michael Jordan", 3000); 

	// Thread one = new Thread(larry); 
	// Thread two = new Thread(michael); 

	System.out.println("Program started on " + new java.util.Date()); 

        larry.run();
	michael.run(); 

	// one.start(); 
	// two.start(); 

    }
}

class Two /*implements Runnable*/ {
    String name; 
    long delay; 
    Two (String name, long delay) {
	this.name = name; 
	this.delay = delay; 
    }
    public void run() {
	try {
	    Thread.sleep(delay); 
	} catch (Exception e) { } 
	System.out.println(name + " at " + new java.util.Date()); 
    }
}
Run this a few times (here's once):
frilled.cs.indiana.edu%javac One.java
frilled.cs.indiana.edu%java One
Program started on Tue Aug 07 19:02:27 EDT 2001
Larry Bird at Tue Aug 07 19:02:34 EDT 2001
Michael Jordan at Tue Aug 07 19:02:37 EDT 2001
frilled.cs.indiana.edu%
Then we make a few changes (in blue).

class One {
    public static void main(String[] args) {
	
	Two larry = new Two("Larry Bird", 7000); 
	Two michael = new Two("Michael Jordan", 3000); 
	
	Thread one = new Thread(larry); 
	Thread two = new Thread(michael); 
	
	System.out.println("Program started on " + new java.util.Date()); 
	
	/* larry.run(); */ 
	/* michael.run(); */
	
	one.start(); 
	two.start(); 
	
    }
}

class Two implements Runnable {
    String name; 
    long delay; 
    Two (String name, long delay) {
	this.name = name; 
	this.delay = delay; 
    }
    public void run() {
	try {
	    Thread.sleep(delay); 
	} catch (Exception e) { } 
	System.out.println(name + " at " + new java.util.Date()); 
    }
}
If you run it, here's what it looks like:
frilled.cs.indiana.edu%java One
Program started on Tue Aug 07 19:08:45 EDT 2001
Michael Jordan at Tue Aug 07 19:08:49 EDT 2001
Larry Bird at Tue Aug 07 19:08:53 EDT 2001
frilled.cs.indiana.edu%
Now time runs simultaneously for both.

So we make one last change.

class One {
    public static void main(String[] args) {
	
	Two larry = new Two("Larry Bird", 7000); 
	Two michael = new Two("Michael Jordan", 3000); 
	
	Thread one = new Thread(larry); 
	Thread two = new Thread(michael); 
	
	System.out.println("Program started on " + new java.util.Date()); 
	
	one.start(); 
	two.start(); 
	
    }
}

class Two implements Runnable {
    String name; 
    long delay; 
    Two (String name, long delay) {
	this.name = name; 
	this.delay = delay; 
    }
    public void run() {
	while (true) { 
	    try {
		Thread.sleep(delay); 
	    } catch (Exception e) { } 
	    System.out.println(name + " at " + new java.util.Date()); 
	}
    }
}
Here's how it runs.

frilled.cs.indiana.edu%java One
Program started on Tue Aug 07 19:13:58 EDT 2001
Michael Jordan at Tue Aug 07 19:14:01 EDT 2001
Michael Jordan at Tue Aug 07 19:14:04 EDT 2001
Larry Bird at Tue Aug 07 19:14:05 EDT 2001
Michael Jordan at Tue Aug 07 19:14:07 EDT 2001
Michael Jordan at Tue Aug 07 19:14:10 EDT 2001
Larry Bird at Tue Aug 07 19:14:12 EDT 2001
Michael Jordan at Tue Aug 07 19:14:13 EDT 2001
Michael Jordan at Tue Aug 07 19:14:16 EDT 2001
Larry Bird at Tue Aug 07 19:14:19 EDT 2001
Michael Jordan at Tue Aug 07 19:14:19 EDT 2001
Michael Jordan at Tue Aug 07 19:14:22 EDT 2001
Michael Jordan at Tue Aug 07 19:14:25 EDT 2001
Larry Bird at Tue Aug 07 19:14:26 EDT 2001
Michael Jordan at Tue Aug 07 19:14:28 EDT 2001
Michael Jordan at Tue Aug 07 19:14:31 EDT 2001
Larry Bird at Tue Aug 07 19:14:33 EDT 2001
Michael Jordan at Tue Aug 07 19:14:34 EDT 2001
Michael Jordan at Tue Aug 07 19:14:37 EDT 2001
Larry Bird at Tue Aug 07 19:14:40 EDT 2001
Michael Jordan at Tue Aug 07 19:14:40 EDT 2001
Michael Jordan at Tue Aug 07 19:14:43 EDT 2001
Michael Jordan at Tue Aug 07 19:14:46 EDT 2001
Larry Bird at Tue Aug 07 19:14:47 EDT 2001
Michael Jordan at Tue Aug 07 19:14:49 EDT 2001
Michael Jordan at Tue Aug 07 19:14:52 EDT 2001
Larry Bird at Tue Aug 07 19:14:54 EDT 2001
Michael Jordan at Tue Aug 07 19:14:55 EDT 2001
Michael Jordan at Tue Aug 07 19:14:58 EDT 2001
frilled.cs.indiana.edu%
So that's another way you can implement threads.

This particular way is quite convenient because

Let's see an example below.

What is a sprite?

frilled.cs.indiana.edu%webster sprite
sprite \'sprai-t\ n
[ME sprit, fr. MF esprit, fr. L spiritus spirit -- more at SPIRIT]
(14c)
1a archaic: SOUL
1b: a disembodied spirit: GHOST
2a: ELF, FAIRY
2b: an elfish person 

frilled.cs.indiana.edu%
A sprite is an elf.

frilled.cs.indiana.edu%webster elf
elf \'elf\ n,  pl elves \'elvz\
[ME, fr. OE aelf; akin to ON alfr elf & prob. to L albus white --
     more at ALB]
(bef. 12c)
1: a small often mischievous fairy
2a: a small lively creature; esp: a mischievous child
2b: a usu. lively mischievous or malicious person
-- elf-ish \'el-fish\ adj 
-- elf-ish-ly adv 

frilled.cs.indiana.edu%
Sprites are

These elements could be

Bitmaps are preformed images that can be pasted on the screen.

Here are ten bitmaps that have been designed with a clear goal in mind.

  1. http://www.cs.indiana.edu/classes/a202-dger/lectures/last/T1.gif
  2. http://www.cs.indiana.edu/classes/a202-dger/lectures/last/T2.gif
  3. http://www.cs.indiana.edu/classes/a202-dger/lectures/last/T3.gif
  4. http://www.cs.indiana.edu/classes/a202-dger/lectures/last/T4.gif
  5. http://www.cs.indiana.edu/classes/a202-dger/lectures/last/T5.gif
  6. http://www.cs.indiana.edu/classes/a202-dger/lectures/last/T6.gif
  7. http://www.cs.indiana.edu/classes/a202-dger/lectures/last/T7.gif
  8. http://www.cs.indiana.edu/classes/a202-dger/lectures/last/T8.gif
  9. http://www.cs.indiana.edu/classes/a202-dger/lectures/last/T9.gif
  10. http://www.cs.indiana.edu/classes/a202-dger/lectures/last/T10.gif
You'd have to agree that the artist did a very good job.

Let's describe a Sprite. Here's a very general description.

abstract class Sprite {

    protected boolean visible; 

    public boolean isVisible() {
        return this.visible; 
    } 

    public void setVisible(boolean visibility) {
        this.visible = visibility; 
    }

    protected boolean active; 

    boolean isActive() {
        return this.active; 
    } 

    void setActive(boolean howActive) {
        this.active = howActive; 
    }

    abstract void paint(Graphics g); 

    abstract void update(); 

    public void suspend() {
        this.setVisible(false); 
        this.setActive(false); 
    }
  
    public void restore() {
        this.setVisible(true); 
        this.setActive(true); 
    }

} 
Now the questions are: An abstract class is an unfinished class.

It can't be instantiated, and can leave methods unimplemented.

For example our old inheritance example could be reworked as follows.

class Example {
    public static void main(String[] args) {
	Shape[] shapes = new Shape[2]; 
	shapes[0] = new Circle(50, 50, 1); 
	shapes[1] = new Rectangle(100, 100, 2, 3); 
	for (int i = 0; i < shapes.length; i++) {
	    System.out.println(shapes[i].area()); 
	}
    }
} 

abstract class Shape {
    int x, y; 
    Shape(int x, int y) {
	this.x = x; this.y = y; 
    } 
    abstract double area(); 
}

class Circle extends Shape {
    int radius;
    Circle(int x, int y, int radius) {
	super(x, y);
	this.radius = radius;
    }   
    double area() { 
	return Math.PI * this.radius * this.radius;
    } 
}

class Rectangle extends Shape {
    int width, height; 
    Rectangle(int x, int y, int width, int height) {
	super(x, y); 
	this.width = width;
	this.height = height; 
    }
    double area() {
	return width * height; 
    }
}
If you want to store circles and rectangles in an array you need to be able to look at them uniformly, so you need to use a class that abstracts the notion of shape (or, if you want, geometrical figure).

Then if you want to be able to simply compute the areas by invoking a method with that name (area) without any casting you need to have a method with that name in class Shape.

Otherwise you'd need to cast or you would get an error.

Note that since you're overriding the area() method in the subclasses (Circle and Rectangle) the method defined in the class of the object (as opposed to the one defined in the class of the reference) will be executed when invoked. So areas are computed properly.

But what should the area() method in class Shape look like?

It should look like "not enough information to compute" and that's what it looks like in our example.

(If don't feel comfortable with the example, please refer to Lecture Notes Twenty-Eight).

Now let's become more concrete in our definitions.

class BitmapSprite extends Sprite {

  protected int locx; 

  protected int locy; 

  protected Image image; 

  protected Applet applet; 

  public BitmapSprite(int x, int y, Image i, Applet a) {
    locx   = x; 
    locy   = y; 
    image  = i; 
    applet = a;
    this.restore(); 
  }

  protected int width, height; 

  public void setSize(int w, int h) {
    width = w; 
    height = h; 
  }

  public void update() {
    /* do nothing */ 
  } 

  public void paint(Graphics g) {
    if (this.visible) {
      if (image != null) 
        g.drawImage(image, locx, locy, applet); 
    }
  } 
} 
Now let's define an interface.

interface Moveable {
  public abstract void setPosition(int x, int y); 
  public abstract void setVelocity(int x, int y); 
  public abstract void updatePosition(); 
} 
And let's put all these definitions to work in our new gizmo.

class BitmapLoop extends BitmapSprite implements Moveable {

    protected Image images[]; 

    protected int currentImage;

    protected boolean foreground; 

    public BitmapLoop (int x, int y, 
                       Image background, 
                       Image foreground[], 
                       Applet applet) 
    {
	super(x, y, background, applet); 
	this.width = applet.getWidth(); 
	this.height = applet.getHeight(); 
	this.images = foreground;
	this.currentImage = 0; 
	if (this.images.length == 0) { 
          this.foreground = false; 
        } else { 
          this.foreground = true; 
        } 
    }

    public void update() {
	if (this.active && this.foreground) {
	    this.currentImage = (this.currentImage + 1) % this.images.length; 
	}
	this.updatePosition();
    }

    public void paint(Graphics g) {
	if (this.visible) {
	    if (this.image != null) 
		g.drawImage(this.image, this.locx, this.locy, this.applet); 
	    if (this.foreground) {
		g.drawImage(this.images[currentImage], 
                            this.locx, 
                            this.locy, 
                            this.applet // the ImageObserver
                           ); 
	    }
	}
    }

    public void setPosition(int x, int y) {
	this.locx = x; 
        this.locy = y; 
    }

    protected int vx, vy;

    public void setVelocity(int x, int y) { 
        this.vx = x; 
        this.vy = y; 
    } 

    public void updatePosition() { 
	this.locx += vx; 
        this.locy += vy; 
        this.vx    = 0; 
        this.vy    = 0; 
    }
}
Now let's put this to use.

import java.applet.*; 
import java.awt.*;
import java.awt.event.*; 

public class UFOControl extends Applet implements Runnable, KeyListener {

    Graphics offscreen;

    Image offscreenImage; 

    static final int NUM_SPRITES = 1; 

    static final int REFRESH_RATE = 80; // in milliseconds... 

    Sprite sprites[]; 

    int width, height; 

    public void init() { 

	System.out.println("*** init *** "); 

	this.setBackground(Color.white); 

	this.width = this.getBounds().width; 
	this.height = this.getBounds().height; 

	this.initSprites(); 

	this.offscreenImage = this.createImage(width, height); 
	this.offscreen = this.offscreenImage.getGraphics(); 

	this.addKeyListener(this); 
    } 

    public void keyPressed (KeyEvent e) { 

	System.out.println("Key Pressed"); 

	switch (e.getKeyCode()) {

 	case KeyEvent.VK_UP: 
	    System.out.println("Up!"); 
	    ((Moveable)this.sprites[0]).setVelocity(0, -3); 
	    break; 

	case KeyEvent.VK_RIGHT: 
	    System.out.println("Right!"); 
	    ((Moveable)this.sprites[0]).setVelocity(3, 0); 
	    break; 

	case KeyEvent.VK_DOWN: 
	    System.out.println("Down!"); 
	    ((Moveable)this.sprites[0]).setVelocity(0, 3); 
	    break; 

	case KeyEvent.VK_LEFT: 
	    System.out.println("Left!"); 
	    ((Moveable)this.sprites[0]).setVelocity(-3, 0); 
	    break; 

	}
    } 

    public void keyReleased(KeyEvent e) { 
        /* do nothing */ 
    } 

    public void keyTyped   (KeyEvent e) { 
        /* do nothing */ 
    } 

    public void initSprites() { 

	sprites = new Sprite[NUM_SPRITES]; 
	Image backImage = null; 
	Image foreImage[] = new Image[10]; // ten Duke bitmaps 
	MediaTracker t = new MediaTracker(this); // need tutorial on this

	try {

	    System.out.println("Getting started."); 

	    for (int i = 1; i <= 10; i++) { 

		String imageName = "images/T" + i + ".gif"; 
		foreImage[i - 1] = getImage(getCodeBase(), imageName); 
		t.addImage(foreImage[i - 1], 0); 

	    }

	    System.out.println("Loading images."); 

	    t.waitForAll(); 

	} catch (Exception e) { 
	    System.out.println(e); 
	    return; 
	} 

	if (t.isErrorAny()) { 

          System.out.println("error"); 

        } else if (t.checkAll()) { 

          System.out.println("successfully loaded."); 

        }
	
	sprites[0] = new BitmapLoop(13, 17, backImage, foreImage, this); 

    }

    Thread animation; 

    public void start() { 

	System.out.println("*** start ***"); 

	this.animation = new Thread(this); 

	if (this.animation != null) {
	    this.animation.start(); 
	}

    } 

    public void run() { 

	while (true) {

	    this.repaint(); 

	    this.updateSprites(); 

	    try {
		Thread.sleep(REFRESH_RATE); 
	    } catch (Exception e) { }
	}

    } 

    public void stop() { 

	System.out.println("*** stop ***"); 

	if (this.animation != null) {
	    this.animation.stop(); // this approach is now deprecated... 
	    this.animation = null; 
	}

    }

    public void updateSprites() { 

	for (int i = 0; i < sprites.length; i++) {
	    this.sprites[i].update(); 
	}

    } 

    public void update(Graphics g) { 

	this.paint(g); 

    }

    public void paint(Graphics g) { 

	offscreen.setColor(Color.white); // why? 

	offscreen.fillRect(0, 0, this.width, this.height);

	for (int i = 0; i < sprites.length; i++) {
	    this.sprites[i].paint(offscreen); 
	}

	g.drawImage(this.offscreenImage, 0, 0, this); 

    } 


} 
Now let's put together the HTML.

<html>
  <head>
    <title>Bitmap Loop</title>
  </head>
  <body bgcolor=white>
    <applet code="UFOControl.class" width=300 height=300>

    </applet>
  </body>
</html>
Also because of initSprites() make sure you have the following:
frilled.cs.indiana.edu%pwd
/nfs/moose/home/user3/dgerman/lect28
frilled.cs.indiana.edu%ls -ld *
drwx------   2 dgerman       512 Aug  5 23:50 images
frilled.cs.indiana.edu%du -a
2	./images/T1.gif
2	./images/T2.gif
2	./images/T3.gif
2	./images/T4.gif
2	./images/T5.gif
2	./images/T6.gif
2	./images/T7.gif
2	./images/T8.gif
2	./images/T9.gif
2	./images/T10.gif
21	./images
22	.
frilled.cs.indiana.edu%
So now you're ready to go.

Create a file UFOControl.java and put everything in it.

Then compile and run the appletviewer on the HTML file.

NOTE: To get the above to compile and run you only need to import the right packages.

(So, do it with care and good luck).


Last updated: Jul 31, 2002 by Adrian German for A201