Activity 14.7.1. Pong Game.
Use Replit to have a go at Pong. If you want a challenge, add a second ball to the game. You’ll have to copy the source code and save it on your own system.
PongFrame
class is the main class. It uses ly to keep track of its motion within the program’s drawing panel. The design strategy employed here leaves the drawing of the ball up to the frame. The Ball
thread itself just handles the movement within the program’s drawing panel. Note that the Ball()
constructor takes a reference to the PongFrame
. As we will see, the Ball
uses this reference to set the dimensions of the frame’s drawing panel. Also, as the Ball
moves, it will repeatedly call the frame’s repaint()
method to draw the ball.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 frame in response to the user pressing the up and down arrows on the keyboard. Because the frame needs to know where to draw the paddle, 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 frame is resized.PongFrame
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 frame’s drawing panel. As in the ThreadedDotty
example earlier in the chapter, it is necessary for the Ball
and the the frame to be implemented as separated threads so that the frame can be responsive to the user’s key presses.Paddle
class implementation (Listing 14.7.3).Paddle
class.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 = 20;
private int gameAreaHeight;
private int locationX, locationY;
private PongFrame frame;
public Paddle(PongFrame f) {
frame = f;
gameAreaHeight = f.getHeight() - BORDER;
locationX = f.getWidth() - WIDTH;
locationY = gameAreaHeight / 2;
} // Paddle()
public void resetLocation() {
gameAreaHeight = frame.getHeight() - BORDER;
locationX = frame.getWidth() - WIDTH;
}
public int getX() { return locationX; }
public int getY() { return locationY; }
public void moveUp() {
if (locationY > BORDER)
locationY -= DELTA;
} // moveUp()
public void moveDown() {
if (locationY + HEIGHT < gameAreaHeight + BORDER)
locationY += DELTA;
} // moveDown()
} // Paddle
HEIGHT
and WIDTH
are used to define the size of the Paddle
, which is represented on the frame as a simple rectangle. The frame will use the Graphics.fillRect()
method to draw the paddle:g.fillRect(pad.getX(),pad.getY(),Paddle.WIDTH,Paddle.HEIGHT);
getX()
and getY()
methods to get the paddle’s current location.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 frame each time the user presses an up- or down-arrow key. They simply change the paddle’s location by DELTA
pixels up or down.Ball
class (Fig. Listing 14.7.4) uses the class constant SIZE
to determine the size of the oval that represents the ball, drawn by the frame as follows:g.fillOval(ball.getX(),ball.getY(),ball.SIZE,ball.SIZE);
getX()
and getY()
method to determine the ball’s current location.Ball
class.import javax.swing.*;
import java.awt.Toolkit;
class Ball extends Thread {
public static final int SIZE = 10; // Diameter of the ball
private PongFrame frame; // Reference to the frame
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(PongFrame f) {
frame = f;
locationX = 50; // Set initial location
locationY = 50;
} // Ball()
public int getX() { return locationX; } // getX()
public int getY() { return locationY; } // getY()
public void move() {
rightWall = frame.getWidth() - SIZE; // Define bouncing region
leftWall = 0;
topWall = 20; // And location of walls
bottomWall = frame.getHeight() - SIZE;
locationX = locationX + directionX; // Calculate a new location
locationY = locationY + directionY;
if (frame.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
frame.repaint();
try {
sleep(15);
} catch (InterruptedException e) {
}
} // while
} // run()
} // Ball
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 frame when we discuss the frame itself.Ball
class is the move()
method. This is the method that controls the ball’s movement within the boundaries of the frame’s drawing area. This method begins by moving the ball by 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;
directionX
and directionY
variables are set to either 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 walls 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.ballHitsPaddle()
is used to determine whether the ball has hit the paddle. This is necessary because only the frame knows the locations of both the ball and the paddle.KeyListener
Interface
PongFrame
class is shown in Listing 14.7.5. The frame’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 ActionEvent
s. Whenever a key is pressed, it generates KeyEvent
s, which are passed to the appropriate methods of the KeyListener
interface.PongFrame
class.import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class PongFrame extends JFrame implements KeyListener {
private Ball ball;
private Paddle pad;
public PongFrame() {
setTitle("Pong");
setBackground(Color.white);
addKeyListener(this);
pad = new Paddle(this); // Create the paddle
ball = new Ball(this); // Create the ball
ball.start();
} // PongFrame()
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
public static void main(String[] args) {
PongFrame f = new PongFrame();
f.setSize(400, 400);
f.setVisible(true);
}
} // PongFrame
KeyListener
interface in the sense that a single key press and release generates three KeyEvent
s: 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 a key-typed and key-released event, for this program, we will take action whenever one 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()
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 those keys were typed, the appropriate paddle method, moveUP()
or moveDown()
, is called.keyPressed()
and keyReleased()
methods in this program, it is still necessary to provide implementations for these methods in the frame. 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 both the keyPressed()
and keyReleased()
methods.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 constructor method PongFrame()
, when the program 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 frame’s current Graphics
object. Recall that in Java all drawing is done using a Graphics
object.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. Note that before painting the paddle, we first call its resetLocation()
method. This causes the paddle to be relocated in case the user has resized the frame’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.JApplet
and JFrame
, perform an automatic form of double buffering, so we needn’t 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.