Solution for
Programming Exercise 6.1


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

Exercise 6.1: Write an applet that shows a pair of dice. When the user clicks on the applet, the dice should be rolled (that is, the dice should be assigned newly computed random values). Each die should be drawn as a square showing from 1 to 6 dots. Since you have to draw two dice, its a good idea to write a subroutine, "void drawDie(Graphics g, int val, int x, int y)", to draw a die at the specified (x,y) coordinates. The second parameter, val, specifies the value that is showing on the die. Assume that the size of the applet is 100 by 100 pixels. Here is a working version of the applet. (My applet plays a clicking sound when the dice are rolled. See the solution to see how this is done.)


Discussion

We need a subclass of JApplet to make the applet. That class will contain a nested class, declared as a subclass of JPanel, to make the drawing surface on which the dice will actually be drawn. We will need an object to respond to mouse events. That could be either the applet object or the drawing surface object (or even another object created just for the purpose.) I will use the applet as a MouseListener.

The drawing class in the applet is named DiceCanvas. Whenever two classes are involved in solving one problem, there is some question of how to divide up responsibilities between the two classes. In this case, I decided to make the canvas responsible only for drawing. The DiceCanvas class defines only one method, paintComponent(). All the other programming (that is, rolling the dice and interacting with the user) is in the main applet class.

The applet must be declared to "implement MouseListener", and it must define the mousePressed() method to roll the dice. (The other methods of the MouseListener interface will be empty.) The applet's init() method creates an object named dice of type DiceCanvas. This object is set to be the "content pane" of the applet with the command

            setContentPane(dice);

It would have been OK to add it to the applet's existing content pane instead, but since the applet will contain only one component, it makes sense to completely replace the content pane with that component. The applet is supposed listen for mouse events from this component, so the applet is registered with dice using the command

            dice.addMouseListener(this);

where "this" is, of course, a name for the applet object itself.

Looking at the rest of the applet class, I defined the mousePressed() routine simply as

            public void mousePressed(MouseEvent evt) {
               roll();
            }

and I defined the roll() subroutine to roll the dice. It would be reasonable to put the code for rolling the dice in the mousePressed() routine, but writing a subroutine to do it makes the program a little easier to modify for the next two exercises. Anyway, when you can identify a self-contained, meaningful task to be performed, it's never a bad a idea to write a subroutine to do it. It will make the program more readable, if nothing else. My roll subroutine assigns random values to the dice and calls dice.repaint() so that the new values will be shown. It also plays the clicking sound with the command

            play(getCodeBase(), "click.au");

The play() method is a predefined instance method in the JApplet class. The sound comes from a file. The second parameter, "click.au", is the name of the file that contains the sound. The first parameter, getCodeBase(), specifies the directory that contains the sound file. This parameter is actually a URL, but all you need to know is that getCodeBase() specifies that the sound file is in the same directory that contains the compiled class file of the applet. Unfortunately, not all sound files can be played by Java, and some sound files might work on one platform but not on another. A sound file with a name ending in ".au" is most likely to work. An interesting thing about the play() method is that it only starts the sound playing. It doesn't wait for the playback to finish. So, the applet continues to run while the sound plays. Here is a link that you can use to download the click sound: click.au.

The hardest part of this exercise is drawing the dice. I made each die 35 pixels wide, leaving a 10 pixel border on each side and 10 pixels between the dice. The top left corner of the left die is at (10,10), the top left corner of the right die is at (55,55). The 55 includes the 10 pixel border on the left, the 35 pixel width of the other die, and the 10 pixels between the dice. The paint method calls a drawDie() routine to draw each die, using the commands:

                drawDie(g, die1, 10, 10);
                drawDie(g, die2, 55, 55);

where die1 is the numerical value shown on the first die and die2 is the numerical value of the second die.

As for the drawDie routine, there are two quite different algorithms that could have been used for drawing the dots. Either:

              if the value shown is 1
                  draw 1 dot (in the center)
              else if the value shown is 2
                  draw 2 dots (in the top-left and bottom-right corners)
                .
                .
                .
              else if the value shown is 6
                  draw 6 dots (along the left and right edges)

Or:

              if the value has a dot in the top-left corner
                   draw the top-left dot
              else if the value has a dot in the top-right corner
                   draw the top-right dot
                 .
                 .
                 .
              else if the value has a dot in the bottom-right corner
                   draw the bottom-right dot

Although the first algorithm is more obvious, the second requires much less typing. (The first algorithm ends up using 21 drawOval() commands, while the second uses only 7.) Furthermore, after drawing the dice on paper, I found that the conditions for testing when a given dot needs to be drawn are simpler than I expected. For example, the values that need a dot in the top-left position are all the values greater than 1. The algorithm leads to my drawDice() routine:

      void drawDie(Graphics g, int val, int x, int y) {
             // Draw a die with upper left corner at (x,y).  The die is
             // 35 by 35 pixels in size.  The val parameter gives the
             // value showing on the die (that is, the number of dots).
          g.setColor(Color.white);
          g.fillRect(x, y, 35, 35);
          g.setColor(Color.black);
          g.drawRect(x, y, 34, 34);
          if (val > 1)  // upper left dot
             g.fillOval(x+3, y+3, 9, 9);
          if (val > 3)  // upper right dot
             g.fillOval(x+23, y+3, 9, 9);
          if (val == 6) // middle left dot
             g.fillOval(x+3, y+13, 9, 9);
          if (val % 2 == 1) // middle dot (for odd-numbered val's)
             g.fillOval(x+13, y+13, 9, 9);
          if (val == 6) // middle right dot
             g.fillOval(x+23, y+13, 9, 9);
          if (val > 3)  // bottom left dot
             g.fillOval(x+3, y+23, 9, 9);
          if (val > 1)  // bottom right dot
             g.fillOval(x+23, y+23, 9,9);
       }

It took some care to figure out the numbers to use in the fillOval commands. The individual dots have a diameter of 9 pixels. There are three rows of dots, which have a combined height of 27 pixels. That leaves 35-27, or 8 pixels for spacing. I use 3 pixels between the dots and the edge of the die, and 1 pixel between rows. This puts the tops of the rows at 3, 3+9+1, and 3+9+1+9+1, that is, at 3, 13, and 23. The columns use the same numbers. (If you believe that I got all this right the first time, I won't disillusion you!)

The nested DiceCanvas class is fairly simple. It's paintComponent method just draws the two dice and the border around the applet:

     class DiceCanvas extends JPanel {
            // A nested class to represent the drawing surface
            // on which the dice are displayed.
          public void paintComponent(Graphics g) {
                 // The paint method draws a blue border and then
                 // draws the two dice.
             super.paintComponent(g);  // fill with background color.
             g.setColor( Color.blue );
             g.drawRect(0,0,99,99);
             g.drawRect(1,1,97,97);
             drawDie(g, die1, 10, 10);
             drawDie(g, die2, 55, 55);
          }
       }

Note that this nested class calls the drawDie routine which is an instance method in the main applet class. Non-static nested classes always have access to the instance variables and instance methods of the class in which they are nested.

There are, of course, many different basic designs for this applet. We could move most of the programming into the DiceCanvas class, leaving the main class with nothing more than an init() method. We could use an anonymous class for the canvas or for the mouse listener or both. Instead of using the Math.random() function, we could use the Random class that was introduced in Section 5.3. I have provided an alternative solution that implements some of these ideas. In this alternative version, two anonymous classes are created in the init() method. You'll find the alternative solution below, after my original solution.


The Solution


    /*
       Shows a pair of dice that are rolled when the user clicks on the
       applet.  It is assumed that the applet is 100-by-100 pixels.  A clicking
       sound is played when the dice are rolled.
    */
    
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    
    public class ClickableDice extends JApplet implements MouseListener {
    
       int die1 = 4;  // The values shown on the dice.
       int die2 = 3;
       
       DiceCanvas dice;  // An object belonging to the nested class
                         // DiceCanvas, which is used as the drawing
                         // surface on which the dice are displayed.
    
       public void init() {
              // To initialize the applet, create the drawing surface
              // object and use it as the applet's content pane.
              // Set the applet to listen for events from the DiceCanvas,
              // and set the background color of the DiceCanvas.
          dice = new DiceCanvas();
          setContentPane(dice);
          dice.addMouseListener(this);
          dice.setBackground( new Color(200,200,255) );  // light blue
       }
       
    
       void drawDie(Graphics g, int val, int x, int y) {
             // Draw a die with upper left corner at (x,y).  The die is
             // 35 by 35 pixels in size.  The val parameter gives the
             // value showing on the die (that is, the number of dots).
          g.setColor(Color.white);
          g.fillRect(x, y, 35, 35);
          g.setColor(Color.black);
          g.drawRect(x, y, 34, 34);
          if (val > 1)  // upper left dot
             g.fillOval(x+3, y+3, 9, 9);
          if (val > 3)  // upper right dot
             g.fillOval(x+23, y+3, 9, 9);
          if (val == 6) // middle left dot
             g.fillOval(x+3, y+13, 9, 9);
          if (val % 2 == 1) // middle dot (for odd-numbered val's)
             g.fillOval(x+13, y+13, 9, 9);
          if (val == 6) // middle right dot
             g.fillOval(x+23, y+13, 9, 9);
          if (val > 3)  // bottom left dot
             g.fillOval(x+3, y+23, 9, 9);
          if (val > 1)  // bottom right dot
             g.fillOval(x+23, y+23, 9,9);
       }
    
    
       void roll() {
             // Roll the dice by randomizing their values.  Tell the
             // system to repaint the applet, to show the new values.
             // Also, play a clicking sound to give the user more feedback.
          die1 = (int)(Math.random()*6) + 1;
          die2 = (int)(Math.random()*6) + 1;
          play(getCodeBase(), "click.au");
          dice.repaint();
       }
       
    
       public void mousePressed(MouseEvent evt) {
             // When the user clicks the applet, roll the dice.
          roll();
       }
       
    
       public void mouseReleased(MouseEvent evt) { }  // Required for the
       public void mouseClicked(MouseEvent evt) { }   //    MouseListener
       public void mouseEntered(MouseEvent evt) { }   //       interface.
       public void mouseExited(MouseEvent evt) { }
       

       class DiceCanvas extends JPanel {
            // A nested class to represent the drawing surface
            // on which the dice are displayed.
          public void paintComponent(Graphics g) {
                 // The paint method draws a blue border and then
                 // draws the two dice.
             super.paintComponent(g);  // fill with background color.
             g.setColor( Color.blue );
             g.drawRect(0,0,99,99);
             g.drawRect(1,1,97,97);
             drawDie(g, die1, 10, 10);
             drawDie(g, die2, 55, 55);
          }
       }
    
    } // end class ClickableDice


An Alternative Solution

    /* An alternative version of the Clickable Dice applet
       that uses two anonymous classes and an object of 
       type java.util.Random.
    */

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.util.Random;  // Get access to the Random class.
    
    public class ClickableDiceAlternative extends JApplet {
    
       static Random rand = new Random();  // A source of random numbers.
    
       int die1 = 4;  // The values shown on the dice.
       int die2 = 3;
       
       JPanel dice;   // The drawing surface on which the dice are drawn.
    
       public void init() {
              // To initialize the applet, create the drawing surface
              // object and use it as the applet's content pane.
              // Set the applet to listen for events from the DiceCanvas,
              // and set the background color of the DiceCanvas.
          dice = new JPanel() {
                    // A JPanel to act as the drawing surface on which
                    // the dice are displayed.
                public void paintComponent(Graphics g) {
                   super.paintComponent(g);  // fill with background color.
                   g.setColor( Color.blue );
                   g.drawRect(0,0,99,99);
                   g.drawRect(1,1,97,97);
                   drawDie(g, die1, 10, 10);
                   drawDie(g, die2, 55, 55);
                }
             };
          setContentPane(dice);
          dice.setBackground( new Color(200,200,255) );  // light blue
          dice.addMouseListener( new MouseAdapter() {
                    // A mouse listener that will roll the dice
                    // when the user clicks the dice canvas.
                public void mousePressed(MouseEvent evt) {
                   roll();
                }
             });
       }
       
    
       void drawDie(Graphics g, int val, int x, int y) {
             // Draw a die with upper left corner at (x,y).  The die is
             // 35 by 35 pixels in size.  The val parameter gives the
             // value showing on the die (that is, the number of dots).
          g.setColor(Color.white);
          g.fillRect(x, y, 35, 35);
          g.setColor(Color.black);
          g.drawRect(x, y, 34, 34);
          if (val > 1)  // upper left dot
             g.fillOval(x+3, y+3, 9, 9);
          if (val > 3)  // upper right dot
             g.fillOval(x+23, y+3, 9, 9);
          if (val == 6) // middle left dot
             g.fillOval(x+3, y+13, 9, 9);
          if (val % 2 == 1) // middle dot (for odd-numbered val's)
             g.fillOval(x+13, y+13, 9, 9);
          if (val == 6) // middle right dot
             g.fillOval(x+23, y+13, 9, 9);
          if (val > 3)  // bottom left dot
             g.fillOval(x+3, y+23, 9, 9);
          if (val > 1)  // bottom right dot
             g.fillOval(x+23, y+23, 9,9);
       }
    
    
       void roll() {
             // Roll the dice by randomizing their values.  Tell the
             // system to repaint the applet, to show the new values.
             // Also, play a clicking sound to give the user more feedback.
          die1 = rand.nextInt(6) + 1;
          die2 = rand.nextInt(6) + 1;
          play(getCodeBase(), "click.au");
          dice.repaint();
       }
       
        
    } // end class ClickableDiceAlternative

[ Exercises | Chapter Index | Main Index ]