Lecture 10

Announcements

Continuing Action

So far, our programs have exhibited behavior only in direct reaction to user actions with the mouse. In particular, each method resulted in a short burst of action and then stopped.

Sometimes we would like to initiate an action, but then have the action continue for a while. The following program gives an example of such behavior. Click to start the snow falling. Don't worry about the program details yet - that will come later.

Demo: Falling Snow

Programming languages allow us to support such behavior with looping constructs. Looping constructs provide ways of repeating behavior as long as desired. Sometimes we want these actions to be repeated a certain number of times, while other times we want them to occur as long as a certain condition is true.

We begin with a simpler example. Consider a program that draws railroad tracks, as in the following demo:

Demo: Railroad Built by Clicking

Using the Java we have seen so far, we have two options:

    public void begin() {
        . . .
        tiePosition = 5;
    } // end begin
    
    public void onMouseClick( Location point ) {
        if ( tiePosition < SCREEN_WIDTH ) {
            new FilledRect(tiePosition, TIE_TOP, TIE_WIDTH, TIE_LENGTH,
                           canvas);
            tiePosition = tiePosition + TIE_WIDTH + TIE_SPACING;
        } // end if
    } // end onMouseClick

Notice that the method stops drawing new ties once the variable tiePosition represents an x-value that would be off the screen.

It is painful to have to click repeatedly to get the ties drawn. Instead we would like the computer to continue drawing ties while they are still on the screen. Java provides the while statement, or while loop, to perform repeated actions. Java includes other looping constructs that we will see later in the semester.

The syntax of a while statement is:

    while (condition) {
        ...
    }

The statements between the open and closed curly brackets are known as the body of the loop.

A common way the while loop is used is as follows:

    while (condition) {
        do something
        change some variable so that next time you do
                        something a bit differently
    } // end while

Now we can draw all of our railroad ties using a while loop, right in the begin method:

    public void begin() {
        ...
        tiePosition = 5;
        while ( tiePosition < SCREEN_WIDTH ) {
            new FilledRect(tiePosition, TIE_TOP, TIE_WIDTH, TIE_HEIGHT,
                           canvas);
            tiePosition = tiePosition + TIE_WIDTH + TIE_SPACING;
        } // end  while
    } // end begin

The complete Java program to draw the railroad includes drawing of the rails and setting appropriate colors. The Java source code and the applet to run are available here:

Demo: Railroad Built by While

As above, the condition controlling the while loop will usually involve the variable that's changing. If nothing in the condition changes, then the loop will never terminate. Recall the typical shampoo bottle directions:

    wet hair
    apply shampoo
    lather
    rinse
    repeat

If you followed those directions, you would continue washing your hair forever! This is known as an infinite loop. We avoid this, in general, by ensuring that our loops have a precise stopping condition. While we might be able to look at an algorithm and say "hey, we should stop now", Java will not (and in fact cannot, in general) determine if a loop will not stop.

Active Objects

All of the classes we have defined so far have described "passive" objects. They only do things when they are told to (i.e., because someone invokes one of their methods).

We have now seen how to get one set of commands to be executed repeatedly. But some of the programs we have seen have lots of different things happening simultaneously.

We can also create "active" objects in Java. They can contain instructions (in a special method called run) that run even when the user doesn't do anything with the mouse.

If you put a loop that goes on for a long time in an onMouse... method, the WindowController can't respond well to additional events because it is busy in the mouse-handling method. Instead, we will put such loops in the "run" methods of ActiveObject's.

To create an ActiveObject one must:

  1. define a class that "extends ActiveObject"
  2. define its constructor and say "start()" at the end.
  3. define a "public void run()" method.

The class ActiveObject is part of the ObjectDraw library. It includes a number of instance variables and methods that are used to keep track of objects which can execute in parallel with each other. The method start does some housekeeping which results in the creation of a new "thread of control" (or just "thread"), which then begins running the run method. Thus evaluating start (or equivalently, this.start()), eventually leads to the creation of a thread that executes the run method. When the method terminates, the thread dies, and the object is no longer "active."

Our first example is of a falling ball in a window with a Pong paddle. This is a pretty boring pong game, since the paddle can't actually hit the ball, but it does demonstrate that the paddle can move at the same time that the ball falls. There is always one thread that handles the mouse motion methods (e.g., onMouseMove), so this program will have two threads operating concurrently: one to move the paddle and the other to move the ball.

Demo: Pathetic Pong

Consider the ActiveObject in this program - the FallingBall. We see that it does all the things we said an ActiveObject is supposed to do. Its class header tells Java that this class extends ActiveObject. We have several constants and an instance variable to hold the FilledOval that will be the actual ball.

The constructor includes the same functionality as things like the Tshirt examples - it constructs the objects that make up an instance of this class (in this case, just a FilledOval). The difference is that it must end with a start() statement.

The run method contains a simple while loop that defines the ongoing "activity" of a FallingBall:

    public void run() {
        while (ball.getY() < BOTTOM ) {
            ball.move(0, Y_SPEED);
            pause(DELAY_TIME);
        }
        ball.removeFromCanvas();
    }

The condition ball.getY() < BOTTOM is true as long as the ball is on the screen. Notice that the body of the while loop contains the statement pause(DELAY_TIME). When this is executed, the thread pauses execution for DELAY_TIME milliseconds (thousandths of a second) before going around to the next iteration of the loop to move the ball by another Y_SPEED pixels down the screen. If the pause statement were not in the loop the animation would take place so fast that we would not be able to see it. For each of our applications we will play with the value of DELAY_TIME until we get a speed of animation that looks the best. For this particular application, we chose a value of 33. If the value of DELAY_TIME were 66, the ball would fall half as fast (there would be twice as much delay between movements), while a value of 11 would have the ball falling 3 times as fast.

There is another, more technical, reason for including a pause statement in the loop. Most personal computers only have a single processor. Thus if two threads are active, they are both being run by the same processor. In order to make it look as through both are being run simultaneously, they take turns. Different computer operating systems have different ways of taking turns. Some automatically trade off after a certain time interval (usually every few milliseconds), while others wait for one thread to pause before releasing control to the other thread. We will always include pauses in every loop in the run method of an ActiveObject in order to ensure that they alternate turns fairly.

Once the ball has finished falling off the screen there is no need keep it around. We call removeFromCanvas, which is different from the hide method in that there is no way to recover the object once it has been removed, to remove the FilledOval from the canvas. The run method then terminates, "deactivating" our FallingBall.

This is everything you'll need to know for the Boxball lab (lab 5).

Drawable Images

Demo: Snowman

In the program above, we drag a picture of a snowman around the screen. The picture comes from a "gif" file named snowman.gif.

The image is certainly too complex to draw using our ObjectDraw primitives. Fortunately, we can read an image from a file and save it as an object with type Image. Image is a built-in Java class from the library java.awt. Hence you need to make sure that any program using Image imports java.awt.Image or java.awt.*.

The first line of the begin method of the Snowman class shows how to do this when given a "gif" file (a particular format for holding images on-line):

    snowManPic = getImage("snowman.gif");

where snowManPic is an instance variable declared to have type Image. Downloading a "gif" file can often be slow, so we usually will want to create an image from the "gif" file at the beginning of a program and save it until we need it. If you download "gif" files in the middle of your program, you may cause a long delay while the computer brings it in from a file on a local disk or fileserver.

While objects of class Image can hold a picture, they can't do much else. We would like to create an object that behaves like our other graphics objects (e.g., FramedRect) so that it can be displayed and moved around on our canvas.

The class VisibleImage from the ObjectDraw library allows you to treat an image roughly as you would a rectangle. In fact, imagine a VisibleImage to be a rectangle with a picture embedded in it. You can do most things you can do with a rectangle, except that there's a neat picture on top.

To create a new VisibleImage:

    new VisibleImage( anImage, xLocation, yLocation, canvas);

For example, new VisibleImage(snowManPic, 10, 10, canvas); would create an object of type VisibleImage from the image in snowManPic and place it on canvas at location (10,10), with size equal to the size of the image it contains.

If you associate a name with your VisibleImage, you can manipulate it using some familiar methods:

    VisibleImage snowMan;

And then later:

    snowMan = new VisibleImage(snowManPic, 10, 10, canvas);

    snowMan.setWidth(124);
    snowMan.setHeight(144);

Our original snow man image is large: 619x718 pixels, but we shrunk him down to a more reasonable size.

What do you think happens if we say:

    snowMan.setColor(Color.green);

Nothing! It's not an error, but nothing is done for you either! Because the picture already has its own colors, it wouldn't make sense to change it to a solid color. Similarly, the value returned by snowMan.getColor() is always Color.black, no matter what colors are in the image! Think about replacing snowman.gif with a more colorful image to see why. A file bender.gif is included with the Snowman demo.

The rest of the code for the Snowman class is essentially identical to our earlier programs which allowed us to drag around squares and T-shirts.