Solution for
Programming Exercise 8.3


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

Exercise 8.3: A polygon is a geometric figure made up of a sequence of connected line segments. The points where the line segments meet are called the vertices of the polygon. The Graphics class includes commands for drawing and filling polygons. For these commands, the coordinates of the vertices of the polygon are be stored in arrays. If g is a variable of type Graphics then

Write a little applet that lets the user draw polygons. As the user clicks a sequence of points, count them and store their x- and y-coordinates in two arrays. These points will be the vertices of the polygon. Also, draw a line between each consecutive pair of points to give the user some visual feedback. When the user clicks near the starting point, draw the complete polygon. Draw it with a red interior and a black border. The user should then be able to start drawing a new polygon. When the user shift-clicks on the applet, clear it.

There is no need to store information about the contents of the applet. The paint() method can just draw a border around the applet. The lines and polygons can be drawn using a graphics context, g, obtained with the command "g = getGraphics();".

You can try my solution. Note that nothing is drawn on the applet until you click the second vertex of the polygon. You have to click within two pixels of the starting point to see a filled polygon.


Discussion

The program needs several instance variables to store information about the sequence of points that the user has clicked:

        int[] xCoord, yCoord;  // Arrays to hold he coordinates.
        int pointCt;           // Number of points in the arrays.

The arrays xCoord and yCoord are examples of "partially full arrays", as covered in Section 8.3. The variable pointCt keeps track of how many spaces in the array are used. The instance variables are initialized in the applet's init() method with the commands:

        xCoord = new int[500];
        yCoord = new int[500];
        pointCt = 0;

The size of the arrays allows for polygons with up to 500 vertices, which should certainly be enough. When we start, there are not yet any points in the array, so pointCt is zero. (The command to set pointCt to zero is not really necessary, since pointCt, as an instance variable, is automatically initialized to zero in any case. However, including this command seemed to me to make my intentions clearer to the reader. That should always be a consideration.)

In my solution, I included a subroutine to draw a line and a subroutine to draw a polygon. These subroutines are called in the mousePressed() routine. Since the lines and polygons are drawn directly on the applet, rather than in the paint() method, these subroutines are responsible for obtaining graphics contexts to do the drawing. The putLine method is simple:

       private void putLine(int x1, int y1, int x2, int y2) {
              // Draw a line from (x1,y1) to (x2,y2) directly onto the
              // applet, without going through the paint() method.
           Graphics g = getGraphics();
           g.drawLine(x1,y1,x2,y2);
           g.dispose();
       }

The putPolygon() method uses the data for the polygon that is stored in the instance variables xCoord, yCoord, and pointCt. It is complicated a little by the fact that three points are required to draw a polygon. If there are only two points stored in the arrays, then I just draw a line between the two points. Note that the two points are stored in the first two locations in the xCoord and yCoord arrays. The coordinates of the two points are (xCoord[0],yCoord[0]) and (xCoord[1],yCoord[1]). If there are fewer than two points, I don't draw anything. If there are three or more points, then I first draw the interior of the polygon in red and then draw the outline of the polygon in black:

       private void putPolygon() {
              // Draw the polygon described by the arrays xCoord and yCoord
              // and the integer pointCt.  A filled polygon with a black 
              // outline is drawn.  If pointCt is 0 or 1, nothing is drawn.
              // If pointCt is 2, only a black line is drawn.
           if (pointCt < 2)
              return;
           Graphics g = getGraphics();
           if (pointCt == 2) {
              g.drawLine(xCoord[0], yCoord[0], xCoord[1], yCoord[1]);
           }
           else {
              g.setColor(Color.red);
              g.fillPolygon(xCoord, yCoord, pointCt);
              g.setColor(Color.black);
              g.drawPolygon(xCoord, yCoord, pointCt);
           }
           g.dispose();
       }

The main logic of the applet is in the mousePressed() method. An algorithm for this method is:

        if the user was holding down the Shift key:
            Clear the applet, and start a new polygon
        else if the user clicked near the first point:
            Draw the current polygon and start a new one
        else
            Add the point (evt.getX(), evt.getY()) to the coordinate arrays
            if this is not the first point in the arrays:
                Draw a line between the previous point and this one

Actually, in my solution, I added another case: If the user right-clicks the applet or if the number of points reaches 500, then I draw the current polygon and start a new one. Also, there is a flaw in the algorithm as stated, where it tests "if the user clicked near the first point". This test doesn't make sense unless there actually is a first point, that is unless pointCt is greater than zero. The test should really read "if pointCt > 0 and if the user clicked near (xCoord[0],yCoord[0])."

Where the algorithm says "start a new polygon", it is only necessary to say "pointCt = 0;" since that will indicate that the coordinate arrays contain no valid data. The command "draw the current polygon" can be translated as "putPolygon()". Adding a point to the coordinate arrays means putting the coordinates of the point in the next available location of the arrays and incrementing pointCt to record the fact that the number of valid data in the arrays has increased by one. This is done with the commands

          xCoord[pointCt] = evt.getX();
          yCoord[pointCt] = evt.getY();
          pointCt++;

Then, if pointCt is greater than 1, we have to draw a line between the last two points in the array. The coordinates of these points are (xCoord[pointCt-2],yCoord[pointCt-2]) and (xCoord[pointCt-1],yCoord[pointCt-1]), so this can be done with the command

          putLine(xCoord[pointCt-2], yCoord[pointCt-2],
                           xCoord[pointCt-1], yCoord[pointCt-1] );

The only thing in the algorithm that still needs implementing is to test whether the user clicks "near the starting point". The starting point has coordinates (xCoord[0],yCoord[0]) and the point where the user clicked has coordinates (evt.getX(),evt.getY()). In my applet, I check whether the x-coordinates of these points are two pixels or less apart and the y-coordinates are also two pixels or less apart. This is done by checking whether "Math.abs(xCoord[0] - evt.getX()) <= 2 && Math.abs(yCoord[0] - evt.getY()) <= 2". If you are uncomfortable with absolute values, you can use the equivalent test "(evt.getX() - 2 <= xCoords[0]) && (xCoords[0] <= evt.getX() + 2) && (evt.getY() - 2 <= yCoords[0]) && (yCoords[0] <= evt.getY() + 2)".

The complete program for the applet is shown below.

(By the way, there are versions of the drawPolygon() and fillPolygon() methods that work with objects belonging to a class called Polygon, instead of with arrays. So, you don't absolutely need arrays to work with polygons.)


The Solution

    /*
      This applet lets the user draw colored polygons.
      The user inputs a polygon by clicking a series of points.
      Clicking near the starting point (within 2 pixels) or
      right-clicking (or Command-clicking) will complete the
      polygon, so the user can begin a new one.  Shift-clicking
      will clear the screen.  Up to 500 points are allowed in a
      polygon.  This applet does not keep a copy of the image
      on the screen, so it will not reappear if the applet is
      covered and then uncovered.
    */
    
    
    import java.awt.*;
    import java.awt.event.*;
    import java.applet.*;
    
    
    public class SimplePolygons extends Applet implements MouseListener {
            
            
       /* Variables for implementing polygon input. */
       
       private int[] xCoord, yCoord;  // Arrays containing the points of 
                                      //   the polygon.  Up to 500 points 
                                      //   are allowed.
                                      
       private int pointCt;  // The number of points that have been input.
       
       private final static Color polygonColor = Color.red;  
                            // Color that is used to draw the polygons.  
    
       public void init() {
             // Initialize the applet.  The applet listens for mouse events.
             // Arrays are created to hold the points.
          setBackground(Color.white);
          addMouseListener(this);
          xCoord = new int[500];
          yCoord = new int[500];
          pointCt = 0;
       }
       
       
       public void paint(Graphics g) {
       
             // The paint() routine does nothing but draw a 1-pixel black 
             // border around the applet.  Polygons drawn on the applet
             // are not permanent.
       
          g.setColor(Color.black);
          g.drawRect(0, 0, getSize().width - 1, getSize().height - 1);
                
       }  // end paint()
       
       
       private void putLine(int x1, int y1, int x2, int y2) {
              // Draw a line from (x1,y1) to (x2,y2) directly onto the
              // applet, without going through the paint() method.
           Graphics g = getGraphics();
           g.drawLine(x1,y1,x2,y2);
           g.dispose();
       }
       
       
       private void putPolygon() {
              // Draw the polygon described by the arrays xCoord and yCoord
              // and the integer pointCt.  A filled polygon with a black 
              // outline is drawn.  If pointCt is 0 or 1, nothing is drawn.
              // If pointCt is 2, only a black line is drawn.
           if (pointCt < 2)
              return;
           Graphics g = getGraphics();
           if (pointCt == 2) {
              g.drawLine(xCoord[0], yCoord[0], xCoord[1], yCoord[1]);
           }
           else {
              g.setColor(Color.red);
              g.fillPolygon(xCoord, yCoord, pointCt);
              g.setColor(Color.black);
              g.drawPolygon(xCoord, yCoord, pointCt);
           }
           g.dispose();
       }
       
    
       public void mousePressed(MouseEvent evt) { 
             // Process a user mouse-click.
       
          if (evt.isShiftDown()) {
                 // Clear the applet. (This only requires a repaint.)
                 // Also, set pointCt to zero to start a new polygon.
              pointCt = 0;
              repaint();
          }
          else if ( pointCt > 0 && (Math.abs(xCoord[0] - evt.getX()) <= 2)
                              && (Math.abs(yCoord[0] - evt.getY()) <= 2) ) {
                 // User has clicked near the starting point.
                 // Draw the polygon and reset pointCt so that the 
                 // user can start a new polygon.
             putPolygon();
             pointCt = 0;
          }
          else if (evt.isMetaDown() || pointCt == 500) {
                 // Draw the polygon and reset pointCt so that the 
                 // user can start a new polygon.
             putPolygon();
             pointCt = 0;
          }
          else {
                 // Add the point where the user clicked to the list of
                 // points in the polygon, and draw a line between the
                 // previous point and the current point.
             xCoord[pointCt] = evt.getX();
             yCoord[pointCt] = evt.getY();
             pointCt++;
             if (pointCt >= 2) {
                putLine(xCoord[pointCt-2], yCoord[pointCt-2], 
                             xCoord[pointCt-1], yCoord[pointCt-1]); 
             }
          }
          
       } // end mousePressed()
       
       public void mouseReleased(MouseEvent evt) { }
       public void mouseClicked(MouseEvent evt) { }
       public void mouseEntered(MouseEvent evt) { }
       public void mouseExited(MouseEvent evt) { }
    
    }  // end class SimplePolygons


[ Exercises | Chapter Index | Main Index ]