Section 14.7. Case Study: The Game of Pong


[Page 693 (continued)]

14.7. Case Study: The Game of Pong

The game of Pong was one of the first computer video games and was all the rage in the 1970s. The game consists of a ball that moves horizontally and vertically within a rectangular region, and a single paddle, located at the right edge of the region, that can be moved up and down by the user. When the ball hits the top, left, or bottom wall or the paddle, it bounces off in the opposite direction. If the ball misses the paddle, it passes through the right wall and re-emerges at the left wall. Each time the ball bounces off a wall or the paddle, it emits a pong sound.

14.7.1. A Multithreaded Design

Let's develop a multithreaded applet to play the game of Pong. Figure 14.29 shows how the game's GUI should appear. There are three objects involved in this program: the applet, which serves as the GUI, the ball, which is represented as a blue circle in the applet, and the paddle, which is represented by a red rectangle along the right edge of the applet. What cannot be seen in the figure is that the ball moves autonomously, bouncing off the walls and paddle. The paddle's motion is controlled by the user by pressing the up- and down-arrow keys on the keyboard.


[Page 694]

Figure 14.29. The UI for Pong.


We will develop class definitions for the ball, paddle, and applet. Following the example of our dot-drawing program earlier in the chapter, we will employ two independent threads, one for the GUI and one for the ball. Because the user will control the movements of the paddle, the applet will employ a listener object to listen for and respond to the user's key presses.

Figure 14.30 provides an overview of the object-oriented design of the Pong program. The PongApplet class is the main class. It uses instances of the Ball and Paddle classes. PongApplet is a subclass of JApplet and implements the KeyListener interface. This is another of the several event handlers provided in the java.awt library. This one handles KeyEvents, and the KeyListener interface consists of three abstract methods, keyPressed(), keyTyped(), and keyReleased(), all of which are associated with the act of pressing a key on the keyboard. All three of these methods are implemented in the PongApplet class. A key-typed event occurs when a key is pressed down. A key-release event occurs when a key that has been pressed down is released. A key-press event is a combination of both of these events.


[Page 695]

Figure 14.30. Design of the Pong program.
(This item is displayed on page 694 in the print version)


The Ball class is a Thread subclass. Its data and methods are designed mainly to keep track of its motion within the applet's drawing panel. The design strategy employed here leaves the drawing of the ball up to the applet. The Ball tHRead itself just handles the movement within the applet's drawing panel. Note that the Ball() constructor takes a reference to the PongApplet. As we will see, the Ball uses this reference to set the dimensions of the applet's drawing panel. Also, as the Ball moves, it will repeatedly call the applet's repaint() method to draw the ball.

The Paddle class is responsible for moving the paddle up and down along the drawing panel's right edge. Its public methods, moveUP() and moveDown(), will be called by the applet in response to the user pressing the up and down arrows on the keyboard. Because the applet needs to know where to draw the applet, the paddle class contains several public methods, getX(), getY(), and resetLocation(), whose tasks are to report the paddle's location or to adjust its location in case the applet is resized.

The PongApplet controls the overall activity of the program. Note in particular its ballHitsPaddle() method. This method has the task of determining when the ball and paddle come in contact as the ball continuously moves around in the applet's drawing panel. As in the THReadedDotty example earlier in the chapter, it is necessary for the Ball and the applet to be implemented as separated threads so that the applet can be responsive to the user's key presses.

14.7.2. Implementation of the Pong Program

We begin our discussion of the program's implementation with the Paddle class implementation (Fig. 14.31).

Figure 14.31. Definition of the Paddle class.
(This item is displayed on page 696 in the print version)

public class Paddle {     public static final int HEIGHT = 50;        // Paddle size     public static final int WIDTH = 10;     private static final int DELTA = HEIGHT/2;  // Move size     private static final int BORDER = 0;     private int gameAreaHeight;     private int locationX, locationY;     private PongApplet applet;     public Paddle (PongApplet a) {        applet = a;        gameAreaHeight = a.getHeight();        locationX = a.getWidth()-WIDTH;        locationY = gameAreaHeight/2;     } // Paddle()     public void resetLocation() {        gameAreaHeight = applet.getHeight();        locationX = applet.getWidth()-WIDTH;     }     public int getX() {          return locationX;     } // getX()     public int getY() {          return locationY;     } // getY()     public void moveUp () {        if (locationY > BORDER )            locationY -= DELTA;     } // moveUp()     public void moveDown() {        if (locationY + HEIGHT < gameAreaHeight - BORDER)            locationY += DELTA;     } // moveDown() } // Paddle class 

Class constants HEIGHT and WIDTH are used to define the size of the Paddle, which is represented on the applet as a simple rectangle. The applet will use Graphics fillRect() method to draw the paddle:

g.fillRect(pad.getX(),pad.getY(),Paddle.WIDTH,Paddle.HEIGHT);


Note how the applet uses the paddle's getX() and getY() methods to get the paddle's current location.

The class constants DELTA and BORDER are used to control the paddle's movement. DELTA represents the number of pixels that the paddle moves on each move up or down, and BORDER is used with gameAreaHeight to keep the paddle within the drawing area. The moveUp() and moveDown() methods are called by the applet each time the user presses an up- or down-arrow key. They change the paddle's location by DELTA pixels up or down.


[Page 696]

The Ball class (Fig. 14.32) uses the class constant SIZE to determine the size of the oval that represents the ball, drawn by the applet as follows:

g.fillOval(ball.getX(),ball.getY(),ball.SIZE,ball.SIZE);


Figure 14.32. Definition of the Ball class.
(This item is displayed on page 697 in the print version)

import javax.swing.*; import java.awt.Toolkit; public class Ball extends Thread {   public static final int SIZE = 10;          // Diameter of the ball   private PongApplet applet;                  // Reference to the applet   private int topWall, bottomWall, leftWall, rightWall;  // Boundaries   private int locationX, locationY;           // Current location of the ball   private int directionX = 1, directionY = 1; // x- and y-direction (1 or -1)   private Toolkit kit = Toolkit.getDefaultToolkit(); // For beep() method   public Ball(PongApplet app) {     applet = app;     locationX = leftWall + 1;                 // Set initial location     locationY = bottomWall/2;   } // Ball()   public int getX() {     return locationX;   } // getX()   public int getY() {     return locationY;   } // getY()   public void move() {     rightWall = applet.getWidth() - SIZE;     // Define bouncing region     leftWall = topWall = 0;                   // And location of walls     bottomWall = applet.getHeight() - SIZE;     locationX = locationX + directionX;       // Calculate a new location     locationY = locationY + directionY;     if (applet.ballHitsPaddle()){       directionX = -1;                        // move toward left wall       kit.beep();     }                                         // if ball hits paddle     if (locationX <= leftWall){       directionX = + 1;                       // move toward right wall       kit.beep();     }                                         // if ball hits left wall     if (locationY + SIZE >= bottomWall || locationY <= topWall){       directionY = -directionY;               // reverse direction       kit.beep();     }                                         // if ball hits top or bottom walls     if (locationX >= rightWall + SIZE) {       locationX = leftWall + 1;               // jump back to left wall     }                                         // if ball goes through right wall   } // move()   public void run() {     while (true) {       move();                                 // Move       applet.repaint();       try { sleep(15);       } catch (InterruptedException e) {}     } // while   } // run() } // Ball class 

As with the paddle, the applet uses the ball's getX() and getY() method to determine the ball's current location.

Unlike the paddle, however, the ball moves autonomously. Its run() method, which is inherited from its Thread superclass, repeatedly moves the ball, draws the ball, and then sleeps for a brief interval (to slow down the speed of the ball's apparent motion). The run() method itself is quite simple because it consists of a short loop. We will deal with the details of how the ball is painted on the applet when we discuss the applet itself.


[Page 698]

The most complex method in the Ball class is the move() method. This is the method that controls the ball's movement within the boundaries of the applet's drawing area. This method begins by moving the ball one pixel left, right, up, or down by adjusting the values of its locationX and locationY coordinates:

locationX = locationX + directionX; // Calculate location locationY = locationY + directionY; 


The directionX and directionY variables are set to either +1 or -1, depending on whether the ball is moving left or right, up or down. After the ball is moved, the method uses a sequence of if statements to check whether the ball is touching one of the walls or the paddle. If the ball is in contact with the top, left, or bottom wall or the paddle, its direction is changed by reversing the value of the directionX or directionY variable. The direction changes depend on whether the ball has touched a horizontal or vertical wall. When the ball touches the right wall, having missed the paddle, it passes through the right wall and re-emerges from the left wall going in the same direction.

Note how the applet method ballHitsPaddle() is used to determine whether the ball has hit the paddle. This is necessary because only the applet knows the locations of both the ball and the paddle.

14.7.3. The KeyListener Interface

The implementation of the PongApplet class is shown in Figure 14.33. The applet's main task is to manage the drawing of the ball and paddle and to handle the user's key presses. Handling keyboard events is a simple matter of implementing the KeyListener interface. This works in much the same way as the ActionListener interface, which is used to handle button clicks and other ActionEvents. Whenever a key is pressed, it generates KeyEvents, which are passed to the appropriate methods of the KeyListener interface.

Figure 14.33. Definition of the PongApplet class.
(This item is displayed on page 699 in the print version)

import javax.swing.*; import java.awt.*; import java.awt.event.*; public class PongApplet extends JApplet implements KeyListener {      private Ball ball;      private Paddle pad;      public void init() {          setBackground(Color.white);          addKeyListener(this);          pad = new Paddle(this);               // Create the paddle          ball = new Ball(this);                // Create the ball          ball.start();          requestFocus();                       // Required to receive key events      } // init()      public void paint (Graphics g ) {           g.setColor(getBackground());         // Erase the drawing area           g.fillRect(0,0,getWidth(),getHeight());           g.setColor(Color.blue);              // Paint the ball           g.fillOval(ball.getX(),ball.getY(),ball.SIZE,ball.SIZE);           pad.resetLocation();                 // Paint the paddle           g.setColor(Color.red);           g.fillRect(pad.getX(),pad.getY(),Paddle.WIDTH,Paddle.HEIGHT);      } // paint()      public boolean ballHitsPaddle() {          return ball.getX() + Ball.SIZE >= pad.getX()             && ball.getY() >= pad.getY()             && ball.getY() <= pad.getY() + Paddle.HEIGHT;      } // ballHitsPaddle()      public void keyPressed( KeyEvent e) {     // Check for arrow keys          int keyCode = e.getKeyCode();          if (keyCode == e.VK_UP)               // Up arrow              pad.moveUp();          else if (keyCode == e.VK_DOWN)        // Down arrow              pad.moveDown();      } // keyReleased()      public void keyTyped(KeyEvent e) {}       // Unused      public void keyReleased( KeyEvent e) {}   // Unused } // PongApplet class 

There is a bit of redundancy in the KeyListener interface in the sense that a single key press and release generates three KeyEvents: a key-typed event, when the key is pressed, a key-released event when the key is released, and a key-pressed event when the key is pressed and released. While it is important for some programs to be able to distinguish between key-typed and key-released events, for this program we will take action whenever either of the arrow keys is pressed (typed and released). Therefore, we implement the keyPressed() method as follows:

public void keyPressed( KeyEvent e) {  // Check arrow keys    int keyCode = e.getKeyCode();    if (keyCode == e.VK_UP)             // Up arrow       pad.moveUp();    else if (keyCode == e.VK_DOWN)      // Down arrow       pad.moveDown(); } // keyReleased() 



[Page 699]

Each key on the keyboard has a unique code that identifies it. The key's code is gotten from the KeyEvent object by means of the getKeyCode() method. Then it is compared with the codes for the up-arrow and down-arrow keys, which are implemented as class constants, VK_UP and VK_DOWN, in the KeyEvent class. If either of these keys is typed, the appropriate paddle method, moveUP() or moveDown(), is called.

Even though we are not using the keyPressed() and keyReleased() methods in this program, it is still necessary to provide implementations for them in the applet. In order to implement an interface, such as the KeyListener interface, you must implement all the abstract methods in the interface. That is why we provide trivial implementations of the keyPressed() and keyReleased() methods.


[Page 700]

14.7.4. Animating the Bouncing Ball

Computer animation is accomplished by repeatedly drawing, erasing, and redrawing an object at different locations on the drawing panel. The applet's paint() method is used for drawing the ball and the paddle at their current locations. The paint() method is never called directly. Rather, it is called automatically after the init() method, when the applet is started. It is then invoked indirectly by the program by calling the repaint() method, which is called in the run() method of the Ball class. The reason that paint() is called indirectly is because Java needs to pass it the applet's current Graphics object. Recall that in Java all drawing is done using a Graphics object.

In order to animate the bouncing ball, we first erase the current image of the ball, then we draw the ball in its new location. We also draw the paddle in its current location. These steps are carried out in the applet's paint() method. First the drawing area is cleared by painting its rectangle in the background color. Then the ball and paddle are painted at their current locations. Before painting the paddle, we first call its resetLocation() method. This causes the paddle to be relocated in case the user has resized the applet's drawing area. There is no need to do this for the ball because the ball's drawing area is updated within the Ball.move() method every time the ball is moved.

One problem with computer animations of this sort is that the repeated drawing and erasing of the drawing area can cause the screen to flicker. In some drawing environments a technique known as double buffering is used to reduce the flicker. In double buffering, an invisible, off-screen, buffer is used for the actual drawing operations and is then used to replace the visible image all at once when the drawing is done. Fortunately, Java's Swing components, including JApplet and JFrame, perform an automatic form of double buffering, so we need not worry about it. Some graphics environments, including Java's AWT environment, do not perform double buffering automatically, in which case the program itself must carry it out.

Double buffering


Like the other examples in this chapter, the game of Pong provides a simple illustration of how threads are used to coordinate concurrent actions in a computer program. As computer game fans will realize, most modern interactive computer games utilize a multithreaded design. The use of threads allows our interactive programs to achieve a responsiveness and sophistication that is not possible in single-threaded programs. One of the great advantages of Java is that it simplifies the use of threads, thereby making thread programming accessible to programmers. However, one of the lessons illustrated in this chapter is that multithreaded programs must be carefully designed in order to work effectively.

Self-Study Exercise

Exercise 14.12

Modify the PongApplet program so that it contains a second ball that starts at a different location from the first ball.




Java, Java, Java(c) Object-Orienting Problem Solving
Java, Java, Java, Object-Oriented Problem Solving (3rd Edition)
ISBN: 0131474340
EAN: 2147483647
Year: 2005
Pages: 275

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net