Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Java Fun and Games: Tips from the Java grab bag

Help yourself to some useful Java SE tips

  • Print
  • Feedback

Ten years of exploring Java Platform, Standard Edition (Java SE) has provided me with a grab bag loaded with useful tips for enhancing games and other Java programs. This installment of Java Fun and Games shares some of these tips with you. Later in this article, I use all of them to enhance an application that grabs images from Webpages.

The simplest sound API

No matter how fast our computers run, it seems we always have to wait for certain tasks to complete, such as downloading large files, performing exhaustive searches, or making extensive mathematical calculations. After one of these lengthy tasks completes, many Java programs alert the user in some fashion, with audible alerts being common.

Java provides several sound APIs for creating audible alerts. You can use the Java Speech API to tell the user that the task has finished. If you prefer to accomplish this task with sound effects or music, the Java Sound API is a good choice. However, because Java Speech requires extra distribution files, and because Java Sound requires fairly complex code, you might prefer to use the Audio Clip API.

The Audio Clip API is based on java.applet.AudioClip and java.applet.Applet methods such as public static final AudioClip newAudioClip(URL url). Although this API is simpler to use than Java Speech and Java Sound, it is overkill if you only want the computer to emit a simple sound. For this task, consider using Java's simplest sound API.

The simplest sound API consists of java.awt.Toolkit's public abstract void beep() method. When this method is called, it emits a simple beep-like sound. To demonstrate beep()'s usefulness, I've created a CalcPi application that calculates pi to a specific number of digits. Check out Listing 1.

Listing 1. CalcPi.java

 // CalcPi.java
import java.awt.Toolkit;

import java.math.BigDecimal;

public class CalcPi
{
    /* constants used in pi computation */

    private static final BigDecimal ZERO = BigDecimal.valueOf (0);

    private static final BigDecimal ONE = BigDecimal.valueOf (1);

    private static final BigDecimal FOUR = BigDecimal.valueOf (4);

    /* rounding mode to use during pi computation */

    private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN;

    /* digits of precision after the decimal point */

    private static int digits;

    public static void main (String [] args)
    {
       if (args.length != 1)
       {
           System.err.println ("usage: java CalcPi digits");
           return;
       }

       int digits = 0;

       try
       {
           digits = Integer.parseInt (args [0]);
       }
       catch (NumberFormatException e)
       {
           System.err.println (args [0] + " is not a valid integer");
           return;
       }

       System.out.println (computePi (digits));
       Toolkit.getDefaultToolkit ().beep ();
    }

    /*
     * Compute the value of pi to the specified number of 
     * digits after the decimal point.  The value is 
     * computed using Machin's formula:
     *
     *          pi/4 = 4*arctan(1/5) - arctan(1/239)
     *
     * and a power series expansion of arctan(x) to 
     * sufficient precision.
     */

    public static BigDecimal computePi (int digits)
    {
       int scale = digits + 5;
       BigDecimal arctan1_5 = arctan (5, scale);
       BigDecimal arctan1_239 = arctan (239, scale);
       BigDecimal pi = arctan1_5.multiply (FOUR).
                       subtract (arctan1_239).multiply (FOUR);

       return pi.setScale (digits, BigDecimal.ROUND_HALF_UP);
    }

    /*
     * Compute the value, in radians, of the arctangent of 
     * the inverse of the supplied integer to the specified
     * number of digits after the decimal point.  The value
     * is computed using the power series expansion for the
     * arc tangent:
     *
     * arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 + 
     *     (x^9)/9 ...
     */

    public static BigDecimal arctan (int inverseX, int scale) 
    {
       BigDecimal result, numer, term;
       BigDecimal invX = BigDecimal.valueOf (inverseX);
       BigDecimal invX2 = BigDecimal.valueOf (inverseX * inverseX);

       numer = ONE.divide (invX, scale, roundingMode);

       result = numer;
       int i = 1;

       do
       {
          numer = numer.divide (invX2, scale, roundingMode);
          int denom = 2 * i + 1;
          term = numer.divide (BigDecimal.valueOf (denom), scale, roundingMode);
          if ((i % 2) != 0)
              result = result.subtract (term);          else
              result = result.add (term);
          i++;
       }
       while (term.compareTo (ZERO) != 0);

       return result;
    }
}

Listing 1 calculates pi via an algorithm developed in the early 1700s by English mathematician John Machin. This algorithm first computes pi/4 = 4*arctan(1/5)-arctan(1/239) and then multiplies the result by 4 to achieve the value of pi. Because the arc (inverse) tangent is computed using a power series of terms, a greater number of terms yields a more accurate pi (in terms of digits after the decimal point).

Note
Listing 1 excerpts much of its code from the "Creating a Client Program" section of Sun's Remote Method Invocation tutorial.

This algorithm's implementation relies on java.math.BigDecimal and an arc-tangent method. Although the Java SE 5.0 and higher versions of BigDecimal include constants ZERO and ONE, these constants are not present in Java 1.4. Also, the number-of-digits command line argument determines the number of arc-tangent power series terms and pi's accuracy:

 java CalcPi 0
3

java CalcPi 1
3.1

java CalcPi 2
3.14

java CalcPi 3
3.142

java CalcPi 4
3.1416

java CalcPi 5
3.14159

More important to this article is Toolkit.getDefaultToolkit ().beep ();, which emits a beep-like sound when the calculation ends. Because larger digit arguments result in longer computations, this single beep lets you know when pi's computation finishes. If a one-beep audible alert is not sufficient, you can create additional beeps, as shown below:

 Toolkit tk = Toolkit.getDefaultToolkit ();
for (int i = 0; i < NUMBER_OF_BEEPS; i++)
{
     tk.beep ();

     // On Windows platforms, beep() typically 
     // plays a WAVE file. If beep() is called 
     // before the WAVE sound finishes, the 
     // second WAVE sound will not be heard. A 
     // suitable delay solves this problem. 
     // (I'm not sure if this problem occurs 
     // on other platforms.)

     try
     {
         Thread.sleep (BEEP_DELAY);
     }
     catch (InterruptedException e)
     {
     }
}

Window centering

Add a touch of professionalism to your Java programs by having them center their modal dialog box windows (an "about" dialog box, for example) within parent windows. Accomplish this task with java.awt.Window's public void setLocationRelativeTo(Component c) method, which centers a window relative to its component argument—null centers the window on the screen. Check out Listing 2.

Listing 2. AboutBox1.java

 // AboutBox1.java

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.border.*;

public class AboutBox1
{
   public static void main (String [] args)
   {
      final JFrame frame = new JFrame ("AboutBox1");
      frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);

      JPanel panel = new JPanel ()
                     {
                        {
                           JButton btnWindow = new JButton ("Window center");
                           ActionListener l = new ActionListener ()
                           {
                               public void actionPerformed (ActionEvent e)
                               {
                                  new AboutBox (frame, "W").setVisible (true);
                               }
                           };
                           btnWindow.addActionListener (l);
                           add (btnWindow);

                           JButton btnScreen = new JButton ("Screen center");
                           l = new ActionListener ()
                           {
                               public void actionPerformed (ActionEvent e)
                               {
                                  new AboutBox (frame, "S").setVisible (true);
                               }
                           };
                           btnScreen.addActionListener (l);
                           add (btnScreen);
                        }
                     };
      frame.getContentPane ().add (panel);

//      frame.setLocationRelativeTo (null);
      frame.pack ();
//      frame.setLocationRelativeTo (null);
      frame.setVisible (true);
   }
}

class AboutBox extends JDialog
{
   AboutBox (JFrame frame, String centerMode)
   {
      super (frame, "AboutBox", true /* modal */);

      final JButton btnOk = new JButton ("Ok");
      btnOk.addActionListener (new ActionListener ()
                               {
                                   public void actionPerformed (ActionEvent e)
                                   {
                                      dispose ();
                                   }
                               });
      getContentPane ().add (new JPanel () {{ add (btnOk); }});

      pack ();

      setLocationRelativeTo (centerMode.equals ("W") ? frame : null);
   }
}

Listing 2's AboutBox1 application creates a GUI whose two buttons establish an about dialog box centered relative to the application's main window or the screen via setLocationRelativeTo(). The commented-out line before frame.pack (); does not center the main window on the screen because the main window's size has yet to be determined. However, the second commented-out line centers this window.

The getContentPane ().add (new JPanel () {{ add (btnOk); }}); statement might look a bit strange because of its nested pairs of braces. Essentially, I create an object from an anonymous inner class that subclasses javax.swing.JPanel, add a button to this object via the object block initializer identified by the inner braces pair, and add the object to the dialog box's content pane.

Drop shadows

If you want to make an about dialog box's title text stand out, consider using a drop shadow—background text drawn at an offset from, and in a specific "shadow" color to give the effect of being behind, the foreground text. Choose a suitable color for the background text to achieve good contrast with foreground text and the background. And use antialiasing to smooth jagged edges. The result appears in Figure 1.

Figure 1. A drop shadow can enhance an about dialog box's title

Figure 1 reveals an about dialog box with blue text drawn over a black drop shadow and a white background. This dialog box was created within an AboutBox2 application's AboutBox(JFrame frame, String centerMode) constructor. Because this application's source code is practically identical to AboutBox1.java, I present only the constructor:

 AboutBox (JFrame frame, String centerMode)
{
   super (frame, "AboutBox", true /* modal */);

   // Add a panel that presents some text to the dialog box's content pane.

   getContentPane ().add (new JPanel ()
                          {
                              final static int SHADOW_OFFSET = 3;

                              {
                                 // Establish the drawing panel's preferred
                                 // size.

                                 setPreferredSize (new Dimension (250, 100));

                                 // Create a solid color border that both
                                 // surrounds and is part of the drawing
                                 // panel. Select the panel background
                                 // color that is appropriate to this look
                                 // and feel.

                                 Color c = 
                                   UIManager.getColor ("Panel.background");
                                 setBorder (new MatteBorder (5, 5, 5, 5, c));
                              }

                              public void paintComponent (Graphics g)
                              {
                                 // Prevent jagged text.

                                 ((Graphics2D) g).setRenderingHint
                                   (RenderingHints.KEY_ANTIALIASING,
                                    RenderingHints.VALUE_ANTIALIAS_ON);

                                 // Because the border is part of the panel,
                                 // we need to make sure that we don't draw
                                 // over it.

                                 Insets insets = getInsets ();

                                 // Paint everything but the border white. 

                                 g.setColor (Color.white);
                                 g.fillRect (insets.left, insets.top,
                                             getWidth ()-insets.left-
                                             insets.right,
                                             getHeight ()-insets.top-
                                             insets.bottom);

                                 // Select an appropriate text font and 
                                 // obtain the dimensions of the text to be 
                                 // drawn (for centering purposes). The
                                 // getStringBounds() method is used instead
                                 // of stringWidth() because antialiasing is
                                 // in effect -- and the documentation for
                                 // stringWidth() recommends use of this
                                 // method whenever the antialiasing or
                                 // fractional metrics hints are in effect.

                                 g.setFont (new Font ("Verdana",
                                                      Font.BOLD,
                                                      32));
                                 FontMetrics fm = g.getFontMetrics ();
                                 Rectangle2D r2d;
                                 r2d = fm.getStringBounds ("About Box", g);
                                 int width = (int)((Rectangle2D.Float) r2d)
                                             .width;
                                 int height = fm.getHeight ();

                                 // Draw shadow text that is almost
                                 // horizontally and vertically (the
                                 // baseline) centered within the panel.

                                 g.setColor (Color.black);
                                 g.drawString ("About Box",
                                               (getWidth ()-width)/2+
                                               SHADOW_OFFSET,
                                               insets.top+(getHeight()-
                                               insets.bottom-insets.top)/2+
                                               SHADOW_OFFSET);

                                 // Draw blue text that is horizontally and
                                 // vertically (the baseline) centered
                                 // within the panel.

                                 g.setColor (Color.blue);
                                 g.drawString ("About Box",
                                               (getWidth ()-width)/2,
                                               insets.top+(getHeight()-
                                               insets.bottom-insets.top)/2);
                              }
                          }, BorderLayout.NORTH);

   final JButton btnOk = new JButton ("Ok");
   btnOk.addActionListener (new ActionListener ()
                            {
                                public void actionPerformed (ActionEvent e)
                                {
                                   dispose ();
                                }
                            });
   getContentPane ().add (new JPanel () {{ add (btnOk); }},
                          BorderLayout.SOUTH);

   pack ();

   setLocationRelativeTo (centerMode.equals ("W") ? frame : null);
}

  • Print
  • Feedback

Resources