Solution for
Programming Exercise 4.6


THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to the following exercise from this on-line Java textbook.

Exercise 4.6: For this exercise, you will write a program that has the same behavior as the following applet. Your program will be based on the non-standard Mosaic class, which was described in Section 4.6. (Unfortunately, the applet doesn't look too good in many versions of Java.)

The applet shows a rectangle that grows from the center of the applet to the edges, getting brighter as it grows. The rectangle is made up of the little squares of the mosaic. You should first write a subroutine that draws a rectangle on a Mosaic window. More specifically, write a subroutine named rectangle such that the subroutine call statement

            rectangle(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.)

The animation loops through the same sequence of steps over and over. In one step, 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 rectangle. Then the very same rectangle is drawn in black, effectively erasing the gray rectangle. Finally, 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 the applet, the color level starts at 50 and increases by 10 after each step. You might want to make a subroutine that does one loop through all the steps of the 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 used 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 rectangle(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 rectangle subroutine:

       static void rectangle(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 plays the animation once. Using this subroutine, the body of the main() routine becomes

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

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 in gray
              Delay 200 milliseconds
              Draw the rectangle in black
              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 size equal to 1. The value of size is 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:

       rectangle(top,left,size,size,brightness,brightness,brightness);

To draw the rectangle in black, just substitute 0 for brightness in this statement.

To go from one step in the animation to the next, brightness increases by 10. The topmost line of the rectangle move 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 with specified brightness
             delay 200 milliseconds
             draw the rectangle in black
             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

    public class MosaicStrobe {
    
         /* This program shows an animation in which a small square grows
            from the center of the window until it fills the whole square.
            Then, after a short delay, the process repeats.  As the square grows,
            its brightness also increases.  This program depends on the
            non-standard classes Mosaic and MosaicCanvas.
         */
    
       public static void main(String[] args) {
              // Open a Mosaic window, then show the "strobe" animation over
              // and over until the user closes the window.
          Mosaic.open(41,41,5,5);
          while ( Mosaic.isOpen() ) {
              Mosaic.delay(1000);
              strobe();
          }
       }  // end main()
    
       
       static void strobe() {
               // Draw the animation, showing a square that starts at
               // the center of the mosaic and grows to fill the whole window.
               // The square gets brighter as it grows.  Note that the
               // animation ends immediately if the user closes the window.
    
          int rectSize;    // The number of rows (and columns) in the square.
          int left;        // The leftmost column in the square.
          int top;         // The topmost row in the square.
          int brightness;  // The brightness of the square, 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 square in gray, pause so the user can see it, then
                 // redraw the same rectangle in black, effectively erasing the
                 // gray square.
              rectangle(top,left,rectSize,rectSize,brightness,brightness,brightness);
              Mosaic.delay(50);
              rectangle(top,left,rectSize,rectSize,0,0,0);
                 // Now, adjust the parameters to get ready to draw the next square
              left--;
              top--;
              rectSize += 2;
              brightness += 10;
          }
          
       } // end strobe()
       
    
       static void rectangle(int top, int left, int height, int width, int r, int g, int b) {
             // Draws the outline of a rectangle in the mosaic window by setting
             // the color of each little square on that rectangle;  top gives the
             // starting row of the rectangle;  left gives the starting column;
             // height gives the number of rows in the rectangle;  width is the
             // number of columns.  The red, green, and blue components of the 
             // color are specified by r, g, and 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()
    
    }  // end class MosaicStrobe

[ Exercises | Chapter Index | Main Index ]