Csci 210 Lab3: Pong

(Laura Toma adapted from Eric Chown)

Overview

A runnable Pong.jar. Here is one that I implemented PongL.jar.

In this lab we are going to go back to the start of the video game era and implement a game called Pong. In Pong there are paddles and balls and your job is not to let the ball get past your paddle. For this lab we will concentrate on a simple single paddle, single ball game. If you write nice general code you should easily be able to scale it up to more advanced versions of the game (like Breakout--- which may well be our next lab). The file Pong.jar contains a running program (it is minimal and should not be considered the standard to shoot for) that should give you a good idea of what we are shooting for in this lab.

Some useful links:

Your program should be fairly similar in structure to the Magnet example and will consist of three classes. We will discuss them in turn. The first class is the Pong class itself. It is basically the game controller.

The Pong Class

The Pong class will handle all of the graphics for the program. Therefore it should extend the JFrame class just as your class for the last lab did. It will also need to listen for mouse movements as the player controls the paddle so it will need to implement the MouseMotionListener interface and all of the corresponding methods. Finally, it will need to tell the ball when to move through the use of a Timer. The Timer will set of timer event so your class will need to be able to listen for it. It does that by implementing the ActionListener interface.

The code for the Pong class is relatively simple. The constructor just makes a new window (like you did in the last lab), adds the appropriate listeners and makes a ball, a paddle and starts the Timer.

Drawing

We're going to handle graphics a little differently in this program than in the last. As we discussed in the Magnet example in class, many java programs do all of their drawing in a
public void paint(Graphics g)
method. This is already defined for the JFrame class, but for this program you will want to over-ride it and provide your own version of the method. In Java the paint method is called every time Java thinks that the screen needs refreshing (for example when you minimize the window). For this program your paint method will be very short, it will simply blank the window, then draw a rectangle to outline the game, then draw the paddle and finally the ball. It should get the information for the paddle and the ball from the appropriate instances. E.g.
g.fillOval(ball.getX(), ball.getY(), ball.getSize(), ball.getSize());
would draw the ball assuming you have written the ball class correctly. You'll have similar code for the paddle.

One counter-intuitive thing about using paint in Java is how to call it. You don't force the program to paint by directly calling paint (in fact it is a bad idea). You do it by calling the method repaint(). Repaint is actually a Java directive which tells the Java Virtual machine to please call paint as soon as it is convenient. You do not write your own repaint method!

Drawing in this program is fundamentally different than in earlier programs. Since the paint method is doing the drawing, it needs to get its information from the other objects (e.g. the Ball and the Paddle). So it will get that information by calling their "setters."

Moving the Ball

To get a Timer to work you first make a global Timer variable like so:
private Timer timer;
You actually need to be slightly careful about the Timer declaration as there are three different Timer classes in Java. To be on the safe side you might declare it this way:
private javax.swing.Timer timer;

Setting up a timer involves creating a Timer object, registering one or more action listeners on it, and starting the timer using the start method. For example, the following code creates and starts a timer that fires an action event once per MOVE milliseconds (as specified by the first argument to the Timer constructor). The second argument to the Timer constructor specifies a listener to receive the timer's action events.

//timer set to go off every MOVE milliseconds;
//the timer events are sent to actionListenerObject---this object must be
//an instance of a class that implements ActionListenerInterface
timer = new Timer(MOVE, actionListenerObject);

timer.setInitialDelay(1000);  //  Waits a 1000 milliseconds before starting
timer.setCoalesce(true);
timer.start();
The timer in the example will go off every 25 milliseconds and create an event that your program listens for. You'll need an
public void actionPerformed(ActionEvent newEvent)
method. Remember that your program is implementing the ActionListener interface, so this is the method you need to fulfill that contract. The action that you are listening for in this case is the Timer going off. The action that you want to occur when the Timer goes off is that the ball should move. So your method will tell the ball object to move.

One thing you will need to figure out here is how to trigger the timer to repaint (in addition to moving the ball).

Moving the Paddle

The paddle moves as the mouse moves. So your mouseMoved(MouseEvent e) will tell the paddle to move (following the mouse). And then will call repaint(). Please note that this method should simply tell the paddle to move. The logic for figuring out just how to move (or whether or not to move) should be in the Paddle class.

The Paddle Class

The Paddle class is going to be functionally similar to a Rectangle with some extra restrictions. It would be nice to build a general paddle that is useful in many games, not just Pong. In Pong, the Paddle will only move in the X direction and it will stop at the boundaries of the Pong window. With both of those things in mind you will probably want to pass your Paddle constructor the boundaries that it can move in both the X and Y directions (remember this should work as a general Paddle, not just for your particular game, for another it might only move vertically). As an alternative you could simply specify which dimension the paddle can move in, and then the boundaries in that direction. You should also pass the Paddle its dimensions (width and height). Your Paddle should have "getters" for its X and Y values as well as its width and height. It should also have a contains method (more on that later - your Ball class will need it). Finally it should have a move(x, y) method to move it as the mouse moves. Remember, however, that it should not move outside the legal game boundaries.

You might find it useful in implementing the Paddle class to use the Java Rectangle class, or the Rectangle2D.Double class. These classes would be useful for implementing contains, as well as for tracking where the paddle is (using the setLocation(x, y)) method.

The contains method (or alternatively an intersect method) is probably the key part of the program and almost certainly the most difficult. The idea is that other objects should never pierce your paddle, instead they should bounce off it. Among other things this means you need to be able to tell if two objects intersect each other. If you really want to do a nice job with it, it is also useful to figure out where they would hit. There are many strategies for doing this. One thing to think about is that hitting the side of the paddle might be quite different from hitting the top of the paddle. What this suggests is that instead of thinking of your paddle as a single rectangle, you might think of several smaller rectangles (perhaps including Rectangles within the class instead of simply extending the Rectangle class).

The Ball Class

The Ball class will be similar to the Paddle class. It too should be as general as possible. It will have getters for X, Y and Size. The major difference is that the Ball moves more or less on its own. In addition to tracking the ball's location you'll also need to keep track of its speed in both the X and Y directions. Each time the move() method is called the ball should move that many pixels in the appropriate direction. The tricky part is that the ball should recognize when it hits either a wall, the ceiling, or the paddle. If it hits a wall its X speed should be reversed. If it hits the ceiling its Y speed should be reversed and if it hits the paddle both should be reversed. Since it needs to figure all of these things out your ball constructor should take in the field dimensions as well as the instance of the Paddle class created in your Pong class. Then when it wants to check if it hit the Paddle it can do so by calling the Paddle's contains (or intersects) method. Alternatively (and perhaps even better) you could have the Ball call a boundary method in the world to check if it is running into something.

Ideally the speed of the ball should be initially set somewhat randomly (e.g. give each dimension a value between 2 and 5). For starters I would just fix them at some constant (I used 3 in each dimension).

One thing to be careful about---when the Ball runs into a paddle (or a wall) it should not literally go into the paddle (its movement should end on the paddle boundary). I have seen (and accidentally programmed myself) Balls get stuck inside paddles. In fact it is a very common error. You must have logic to prevent this, every program I've ever tested that doesn't has this bug.

The ball speed changes that I outline above are merely guidelines. It is reasonable to make the behaviors more interesting. For example the actual part of the paddle you hit might affect the directional change. Or a moving paddle might add velocity, you might change the velocity the longer the program runs, etc. It isn't necessary to do any of this, but I always encourage you to have fun with your programs.

Other Constants

Feel free to experiment with different dimensions, paddle sizes, colors, etc. Just try and keep them so they are playable. The following values are probably a reasonable place to start.
//position and dimensions of the court
private static final int COURT_LEFT = 100;
private static final int COURT_TOP = 50;
private static final int COURT_HEIGHT = 300;
private static final int COURT_WIDTH = 250;

// location and dimensions of the paddle
private static final int PADDLE_WIDTH = 50;
private static final int PADDLE_HEIGHT = 20;
private static final int PADDLE_TOP = COURT_TOP + COURT_HEIGHT - PADDLE_HEIGHT -1;

private static final int BALLSIZE = 30;
private static final int MOVE = 25;

Extensions

There are loads of possible extensions to this program. You could add levels such that the game gets harder as the player survives (e.g. the paddle gets narrower, the ball moves faster, etc.). You could provide the opportunity to have multiple balls. Or multiple paddles for a two player game (look into the KeyListener class for this). By the way, if you do a two player game you still should have a single player mode – I need to test it!

Warning: your game should be playable. It is bad practice to have it set up so that I have virtually no chance to play your game. I need to test your program. Please make that process as painless as possible – don't start your game at super fast speeds! There are few things more frustrating then hitting the “new Pong” command and then seeing the ball race down before I can move the mouse over to the window to stop it. It might be helpful to think about a "start" button and a "new game" button. Those help in playability.


Last modified: Thu Sep 20 21:46:42 EDT 2007