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
]