Solution for
Programming Exercise 6.7


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

Exercise 6.7: Section 3.7 discussed SimpleAnimationApplet2, a framework for writing simple animations. You can define an animation by writing a subclass and defining a drawFrame() method. It is possible to have the subclass implement the MouseListener interface. Then, you can have an animation that responds to mouse clicks.

Write a game in which the user tries to click on a little square that jumps erratically around the applet. To implement this, use instance variables to keep track of the position of the square. In the drawFrame() method, there should be a certain probability that the square will jump to a new location. (You can experiment to find a probability that makes the game play well.) In your mousePressed method, check whether the user clicked on the square. Keep track of and display the number of times that the user hits the square and the number of times that the user misses it. Don't assume that you know the size of the applet in advance.


Discussion

Here is the applet:

In order to respond to mouse events, this applet must implement the MouseListener interface. This applet, JumpingSquareGame, has SimpleAnimationApplet2 as its direct superclass, rather than JApplet, so the source code begins:

    public class JumpingSquareGame extends SimpleAnimationApplet2
                                         implements MouseListener {

Several instance variables are needed to keep track of the state of the applet. Since the square can move, we need to keep track of its position. I do this with two integer instance variables, squareLeft and squareTop. Since the applet displays the number of hits and the number of misses, these will have to be stored in instance variables. Each time the user clicks on the applet, one or the other of these variables will be incremented.

The class includes an init() method which registers the applet to listen for mouse events on itself. It also places the square at the center of the applet. The size of the square is given by a named constant, SQUARE_SIZE, so the init() method says:

       public void init() {
             // Put the square in the center of the applet, and
             // set the applet to listen for mouse clicks.
          squareLeft = getSize().width / 2 - SQUARE_SIZE / 2;
          squareTop = getSize().height / 2 - SQUARE_SIZE / 2;
          addMouseListener(this);
       }

The reasoning behind the formulas for squareLeft and squareTop is this: The center point of the applet has coordinates (getSize().width/2, getSize().height/2). We want to put the center of the square at this point. The value we want for squareLeft is the position of the left edge of the square. To get this, we start in the middle of the applet, getSize().width/2, and back up by subtracting the size of half of the square, SQUARE_SIZE/2. The same reasoning works for squareTop.

There could be a problem in this init() method. We are writing a subclass of SimpleAnimationApplet2, which could have its own init() method. If so, we've just replaced it with our own init() method in the subclass. When our applet runs, our init() method will be executed, and the one in the superclass will not be executed. What if the init() method in the SimpleAnimationClass does important initializations? They won't be executed, and the animation won't work. Now, as a matter of fact, I looked at the source code of SimpleAnimationApplet2 and saw that it does not define an init() method. So we are OK. (We are just replacing the init(). method from the Applet class, which doesn't do anything.) But I should be able to write a subclass without looking at the source code of the superclass. In that case, I should make sure that my init() method calls the init() method from the superclass. This can be done through the special variable super, with the command super.init(). Then, the init() method would be written:

       public void init() {
             // Put the square in the center of the applet, and
             // set the applet to listen for mouse clicks.
          super.init();  // Call the init() method
                         //    of the superclass.
          squareLeft = getSize().width / 2 - SQUARE_SIZE / 2;
          squareTop = getSize().height / 2 - SQUARE_SIZE / 2;
          addMouseListener(this);
       }

The point of the SimpleAnimationApplet2 is that it calls the method, drawFrame, repeatedly, several times per second. This method has to draw one frame of the animation. It can also change the state of the applet to get ready for the next frame. It's easy enough to draw the frame. (I spent a little time trying to choose colors that look nice.) The change of state that we have to worry about is that the square might jump between the current frame and the next frame. The exercise suggests that there should be a certain small probability that the square will jump. In my drawFrame method, I implement this by saying

            if (Math.random() < 0.2)
                jump();

where jump() is a method that I have defined to move the square to a random point. Since Math.random() is a random number between 0 and 1, there is a 1 in 5 chance that it is between 0 and 0.2. So, the square will jump, on the average, in one frame out of 5.

The mousePressed routine has to respond when the user clicks on the applet. It checks whether the (x,y) point where the user clicked is inside the current position of the square. If so, then the number of hits goes up by one. If not, then the number of misses goes up by one. I also make the square jump if the user hits it:

       public void mousePressed(MouseEvent evt) {
              // When the user clicks the mouse, determine whether the user
              // clicked the square.  If so, add one to hits.  If not, add
              // one to misses.  Note that if the user hits the square, it
              // jumps so that the user will not be able to get multiple
              // hits by clicking the same place over and over.
           int x = evt.getX();
           int y = evt.getY();
           if (x >= squareLeft && x < squareLeft + SQUARE_SIZE
                    && y >= squareTop && y < squareTop + SQUARE_SIZE) {
               hits++;
               jump();
           }
           else {
               misses++;
           }
       }

The complete source code is given below.


The Solution


    /*
       A little game in which a square jumps around in the applet and
       the user tries to click on it.  The number of times the user
       hits the square and the number of times the user misses are
       reported.  This applet is based on SimpleAnimationApplet2,
       and it requires that class.
    */
    
    import java.awt.*;
    import java.awt.event.*;
    
    
    public class JumpingSquareGame extends SimpleAnimationApplet2
                                              implements MouseListener {
    
    
       static final Color appletBorderColor = new Color(180, 70, 0);
       static final Color appletFillColor = new Color(255, 240, 180);
    
       static final Color squareBorderColor = new Color(0, 0, 160);
       static final Color squareFillColor = new Color(220,220,255);
       
       static final int SQUARE_SIZE = 30;  // Length of side of the square.
       
    
       int squareLeft, squareTop;  // Top-left corner of the square.
       
       int hits = 0;    // Number of times user has hit the square.
       int misses = 0;  // Number of times user has missed the square.
       
    
       public void init() {
             // Put the square in the center of the applet, and
             // set the applet to listen for mouse clicks.
          squareLeft = getSize().width / 2 - SQUARE_SIZE / 2;
          squareTop = getSize().height / 2 - SQUARE_SIZE / 2;
          addMouseListener(this);
       }
       
       
       public void drawFrame(Graphics g) {
             // Draw the game, showing the square in its current position
             // and displaying the hits and misses.  There is a small
             // chance that the square will jump.
       
           g.setColor(appletFillColor);   // Fill in the background.
           g.fillRect(0, 0, getSize().width, getSize().height);
           g.setColor(appletBorderColor);
           g.drawRect(0, 0, getSize().width - 1, getSize().height - 1);
           g.drawRect(1, 1, getSize().width - 3, getSize().height - 3);
           
           g.setColor(squareFillColor);   // Draw the square.
           g.fillRect(squareLeft, squareTop, SQUARE_SIZE, SQUARE_SIZE);
           g.setColor(squareBorderColor);
           g.drawRect(squareLeft, squareTop,  SQUARE_SIZE - 1, SQUARE_SIZE - 1);
           g.drawRect(squareLeft + 1, squareTop + 1, SQUARE_SIZE - 3, SQUARE_SIZE - 3);
           
           g.setColor(appletBorderColor);   // Report hits and misses.
           g.drawString( hits + " Hits", 6, 16);
           g.drawString( misses + " Misses", 6, 28);
           
           if (Math.random() < 0.2)  // A 1/5 chance that the square will jump.
              jump();
           
       }  // end drawFrame()
    
    
       void jump() {
              // Move the square to a new random location.  The range of possible
              // values will prevent the square from going outside the applet
              // or from overlapping the 2-pixel border around the applet.
              // Note that it is not necessary to call repaint, since this
              // is an animation that is redrawn continually.
          squareLeft = 2 + (int)(Math.random() * (getSize().width - 4 - SQUARE_SIZE));
          squareTop = 2 + (int)(Math.random() * (getSize().height - 4 - SQUARE_SIZE));
       }
       
    
       public void mousePressed(MouseEvent evt) {
              // When the user clicks the mouse, determine whether the user
              // clicked the square.  If so, add one to hits.  If not, add
              // one to misses.  Note that if the user hits the square, it
              // jumps so that the user will not be able to get multiple
              // hits by clicking the same place over and over.
           int x = evt.getX();
           int y = evt.getY();
           if (x >= squareLeft && x < squareLeft + SQUARE_SIZE
                    && y >= squareTop && y < squareTop + SQUARE_SIZE) {
               hits++;
               jump();
           }
           else {
               misses++;
           }
       }
    
    
       public void mouseReleased(MouseEvent evt) { }
       public void mouseClicked(MouseEvent evt) { }
       public void mouseEntered(MouseEvent evt) { }
       public void mouseExited(MouseEvent evt) { }
    
    
    } // end class JumpingSquareGame


[ Exercises | Chapter Index | Main Index ]