[ Exercises | Chapter Index | Main Index ]

Solution for Programming Exercise 4.7


This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.


Exercise 4.7:

This is another Mosaic exercise, (using Mosaic.java and MosaicCanvas.java as discussed in Section 4.6). While the program does not do anything particularly interesting, it's interesting as a programming problem. The program will do the same thing as the following applet:

The program will show a square that grows from the center of the applet to the edges. As it grows, the part added around the edges gets brighter, so that in the end the color of the square fades from white at the edges to dark gray at the center.

The whole picture is made up of the little rectangles of a mosaic. You should first write a subroutine that draws the outline of a rectangle on a Mosaic window. More specifically, write a subroutine named outlineRectangle such that the subroutine call statement

outlineRectangle(top,left,height,width,r,g,b);

will call Mosaic.setColor(row,col,r,g,b) for each little square that lies on the outline of a rectangle. The topmost row of the rectangle is specified by top. The number of rows in the rectangle is specified by height (so the bottommost row is top+height-1). The leftmost column of the rectangle is specified by left. The number of columns in the rectangle is specified by width (so the rightmost column is left+width-1.) For the specific program that you are writing, the width and the height of the rectangle will always be equal, but it's nice to have the more general-purpose routine.

The animation loops through the same sequence of steps over and over. In each step, the outline of a rectangle is drawn in gray (that is, with all three color components having the same value). There is a pause of 200 milliseconds so the user can see the picture. Then the variables giving the top row, left column, size, and color level of the rectangle are adjusted to get ready for the next step. In my applet, the color level starts at 50 and increases by 10 after each step. When the rectangle gets to the outer edge of the applet, the loop ends, and the picture is erased by filling the mosaic with black. Then, after a delay of one second, the animation starts again at the beginning of the loop. You might want to make an additional subroutine to do one loop through the steps of the basic animation.

The main() routine simply opens a Mosaic window and then does the animation loop over and over until the user closes the window. There is a 1000 millisecond delay between one animation loop and the next. Use a Mosaic window that has 41 rows and 41 columns. (I advise you not to use named constants for the numbers of rows and columns, since the problem is complicated enough already.)


Discussion

According to the exercise, the first task is to write a subroutine that draws a rectangle in a Mosaic window. The name and seven parameters of the subroutine are specified. So the first line of the subroutine definition will look like this:

static void outlineRectangle(int left, int top, 
                        int height, int width, int r, int g, int b) {

The subroutine has to draw the four lines that make up the outline of the rectangle. Each square that lies along one of these lines will have its color set by a call to Mosaic.setColor(row,col,r,g,b). We just have to make sure that row and col take on all the correct values that are needed to hit all the necessary squares. For the topmost line of the rectangle, for example, row is given by the value of the parameter, top. And, as the exercise explains, the value of col varies from left to left+width-1. So the topmost line of the rectangle can be drawn with the for loop:

for ( col = left;  col <= left+width-1;  col++ )
    Mosaic.setColor(top,col,r,g,b);

The bottommost row can be drawn by a similar for loop, except that the value of row along the bottommost row is top+height-1, as noted in the exercise. We can combine the two for loops to draw the top and bottom lines at the same time:

for ( col = left;  col <= left+width-1;  col++ ) {
    Mosaic.setColor(top,col,r,g,b);           // A square on the top line.
    Mosaic.setColor(top+height-1,col,r,g,b);  // A square on the bottom line.
}

Drawing the leftmost and rightmost lines of the rectangle is similar. The row number along these lines varies from top to top+height-1. The column number of the leftmost line is given by left, and the column number of the rightmost line is left+width-1. So, these two lines can be drawn with the for loop:

for ( row = top;  row <= top+height-1;  row++ ) {
    Mosaic.setColor(row,left,r,g,b);           // A square on the left line.
    Mosaic.setColor(row,left+width-1,r,g,b);   // A square on the right line.
}

When I wrote my program, I used the test "row < top+height" in the first for loop in place of "row <= top+height-1", and similarly for the second loop. The meaning is the same, and the form that I used would probably be preferred by most Java and C++ programmers. Putting this all together gives the outlineRectangle() subroutine:

static void outlineRectangle(int top, int left, 
                        int height, int width, int r, int g, int b) {
   int row, col;
   for ( row = top;  row < top + height;  row++ ) {
      Mosaic.setColor(row, left, r, g, b);
      Mosaic.setColor(row, left + width - 1, r, g, b);
   }
   for ( col = left;  col < left + width; col++ ) {
      Mosaic.setColor(top, col, r, g, b);
      Mosaic.setColor(top + height - 1, col, r, g, b);
   }
}  // end rectangle()

We still have the problem of designing the complete program. The main() routine plays the same animation over and over as long as the window is still open. A pseudocode algorithm for this is given by

Open a mosaic window
while the window remains open:
    Delay for 1000 milliseconds
    Play once through the animation

I will write a subroutine named strobe that gradually fills in the big square. After that subroutine has finished, I want to erase the picture by filling the mosaic with black. You could do that with a nested for loop that sets the color of every rectangle in the Mosaic individually. However, the Mosaic class already includes a subroutine Mosaic.fill(r,g,b) that does exactly that. Using the fill subroutine, we can call Mosaic.fill(0,0,0) to fill the entire mosaic with black. So, the body of the main() routine becomes:

Mosaic.open(41,41,5,5);
while ( Mosaic.isOpen() ) {
    Mosaic.delay(1000);
    strobe();
    Mosaic.fill(0,0,0);
}

The final stage in the design is to write the strobe() routine. The outline of an algorithm is already given in the exercise. It can be written more formally as

Initialize variables top,left,size,brightness for the first step
Repeat until the rectangle is as big as the whole window:
    Draw the rectangle outline in gray
    Delay 200 milliseconds
    Update the variables for the next step

The window has 41 columns and 41 rows of squares. We want the rectangle to start at the middle of the window. That will be in row 20 and column 20, so we can initialize top and left to 20. The rectangle starts off as small as possible, that is with rectSize equal to 1. The value of rectSizeis used as both the width and the height of the rectangle. The brightness is initialized to 50. The value of brightness is used for each of the color components, r, g, and b. So, the rectangle can be drawn in gray with the subroutine call statement:

outlineRectangle(top,left,rectSize,rectSize,brightness,brightness,brightness);

As for updating the variables to get ready for the next iteration of the loop, brightness increases by 10 at each step. The topmost line of the rectangle moves up one row. This means that the value of top decreases by 1. Similarly, the value of left decreases by 1. The rectangle grows by one row on each side, so its size increases by 2.

There are several ways to check whether the animation should continue. We could check whether its size is <= 41. Or whether left is >= 0. Or whether top is >= 0. Alternatively, we could notice that there are 21 steps in the animation, and we could just use a for loop to count from 1 to 21. Picking one of these methods more or less at random, the algorithm for the strobe() becomes

left = 20
top = 20
size = 1
brightness = 50
while left is >= 0:
    draw the rectangle outline
    delay 200 milliseconds
    left -= 1
    top -= 1
    size += 2
    brightness += 10

This translates easily into Java code. One more note: In my implementation of the subroutine, I changed the condition in the loop to "while (left >= 0 && Mosaic.isOpen())", since there is no reason to continue with the animation if the user has closed the window. However, the program will work without this extra test. It just might take an extra second or so for the program to end after the user closes the window.


The Solution

/**
 * This program shows an animation in which a small square grows from the 
 * center of the window until it fills the whole mosaic.  The filled square
 * is built up by a series of outlines of squares, which get brighter as
 * they get bigger.  After the entire mosaic is filled, the square is 
 * erased, there is a one-second delay, and the process repeats.  This
 * continues until the user closes the window.  This program depends on 
 * the non-standard classes Mosaic and MosaicCanvas.
 */

public class MosaicStrobe {

   
   /**
    * Opens a mosaic window then play the "strobe" animation over and over
    * as long as the window is still open.
    */
   public static void main(String[] args) {
      Mosaic.open(41,41,6,6);
      while ( Mosaic.isOpen() ) {
          Mosaic.delay(1000);
          strobe();
          Mosaic.fill(0,0,0);
      }
   }  // end main()


   /**
    * Draw the animation, showing a square that starts at the center of the
    * mosaic and grows to fill the whole window.  The outline added to the
    * square at each step square gets brighter as the square grows.  Note 
    * that the animation ends immediately if the user closes the window.
    */
   static void strobe() {

      int rectSize;    // The number of rows (and columns) in the part of the square
                       // that has been drawn so far.
      int left;        // The leftmost column in the next square outline to be drawn.
      int top;         // The topmost row in the next square outline.
      int brightness;  // The brightness of the outline, which increases from
                       //   50 to a maximum of 250 as the square grows.  This
                       //   quantity is used for all three color components,
                       //   giving a gray color that brightens to white.

      left = 20;       // Start at the center of the 41-by-41 mosaic.
      top = 20;
      
      rectSize = 1;  
      brightness = 50;

      while (left >= 0 && Mosaic.isOpen()) {

         /* Draw the outline in gray, pause so the user can see it. */

         outlineRectangle(top,left,rectSize,rectSize,brightness,brightness,brightness);
          Mosaic.delay(200);
          
          /*  Now, adjust the parameters to get ready to draw the next outline. */

          left--;
          top--;
          rectSize += 2;
          brightness += 10;
      }
      
   }
   

   /**
    * Draws the outline of a rectangle in the mosaic window by setting the color
    * of each little square on that outline. 
    * @param top gives the starting row, at the top edge of the rectangle
    * @param left gives the starting column, at the left edge of the rectangle
    * @param height gives the number of rows in the rectangle
    * @param width gives the number of columns in the rectangle
    * @param red the red component of the color, in the range 0 to 255
    * @param green the green component of the color
    * @param blue the blue component of the color
    */
   static void outlineRectangle(int top, int left, int height, int width, int r, int g, int b) {
      int row, col;
      for ( row = top;  row < top + height;  row++ ) {
         Mosaic.setColor(row, left, r, g, b);
         Mosaic.setColor(row, left + width - 1, r, g, b);
      }
      for ( col = left;  col < left + width; col++ ) {
         Mosaic.setColor(top, col, r, g, b);
         Mosaic.setColor(top + height - 1, col, r, g, b);
      }
   }  // end outlineRectangle()


}  // end class MosaicStrobe

[ Exercises | Chapter Index | Main Index ]