Project 6: The interaction of objects

The goal of this project is to practice writing classes. The class you wrote in the previous project responded to some simple methods and did not have to call methods on any other object. The class you will write in this project will need to think for itself more and it will need to interact with other objects.

1. Introduction

The premise of this project is also a game. A player exists in a grid—each position in the grid is identified by an x and y coordinate. At any time during the execution of the game, there are a number of balls rolling across the grid. The player must catch as many balls as possible by moving to intercept them.

Unlike the previous project, the user will not be controlling the player. In fact, the user will not be doing anything (except watching). Instead, you are to write a class which models the player. Your class will have to decide how to move to catch as many balls as possible.

2. Set up

Clone the provided repository to make a new directory to work in.n

hg clone /cslab/class/csci235/projects/project6
cd project6

You'll see three files. Player.java contains the class that models/controls the player (which you will have to write; currently it's a dummy class); you will not need to modify Position.java, but you'll need to know how it works; ballgame.jar contains everything else.

Compile Player.java (Position.java will get compiled automatically when you do this). Since Player.java depends on stuff in ballgame.jar, you'll need to tell the compiler to look there:

javac -cp .:ballgame.jar Player.java

Then try running it (it will run before you write any code—just not very well). On the command line, you can specify a small or big grid (default small) and the number of balls (defalt 1). For examples,

java -cp .:ballgame.jar BallGame 

will start the game going on a small grid with 1 ball, and

java -cp .:ballgame.jar BallGame -big 5

will start the game going on a big grid with 5 balls.

You will see two windows pop up. The big window will show the grid. The player is blue, placed in a random location. The balls (red) are in random locations along the edge. The other window keeps track of the number of balls caught.

Click on "Start" and you'll see the game go. The balls will roll across the grid. The balls roll at a constant velocity; they each have a direction, which is also constant. When a ball reaches the edge of the grid, it disappears. Immediately a new ball appears at another random location on the grid. (Balls do not "bounce" off the edge of the grid; they roll off and are replaced with new balls.)

At present, the blue player will slowly plod towards the origin of the grid (the lower left hand corner; think of the grid as being part of the "first quadrant" of the plane). If you're lucky, it might run into a ball and "catch" it, but probably not.

3. The Player class

Open Player.java in emacs. There's not much there yet—your task will be to fill in the rest. But two things must be there in some form or another: There must be a constructor with the same signature of the one you see there, and there must be a method called act().

Once every second while the game is running, the player's act() method will be called. This is the player's opportunity to move (in an attempt to catch a ball). The player must respond with a "position"—ie, pair of coordinates—to which it wishes to move. (Since the player must respond with two ints, an x and y coordinate, we need to encapsulate those two values into one object. That's the purpose of the Position class. Probably the only need you will have for the Position is to package up the coordinates that the act() method returns.)

The constructor receives a whole bunch of information—the number of balls, the width and height of the grid, the x and y coordinates where the player is originally placed, and a GridInfo object, which will be explained more below.

The way Player is currently set up, it records its coordinates in instance variables (myX and myY). Then every time it is asked to act, it simply decrements each coordinate (so, if it was in position (5, 12), it would move to position (4, 11)), until it hits the origin, after which it stays put. No wonder it doesn't catch any balls.

4. How the Player decides where to move

Your main task in this project is writing a smart act() method, one that makes the player chase after balls (and catch them). This will require knowing where balls are, determining how they are moving, predicting where they will move next, and picking a next position which will bring the player closer to a ball.

The Player object can find out about what is happening in the grid around it using the GridInfo object that is passed to it in the constructor. The only thing you need to know about the GridInfo type is that it has a method getBall() with declaration

int getBall(int, int)

This method expects a pair of coordinates and it returns an integer indicating what can be found at the specified position.

Each ball has a unique integer identifying it. If the system has 5 balls, then the balls are numbered 0 through 4. If the position you supply to getBall() has a ball in it, then it will return the ball's number. If that position does not contain a ball, the method returns -1 to indicate that the grid position is vacant.

Using this, the act() method can scan the grid and figure out which ball is where. It can use that information to make its predictions and decisions.

5. Warnings

There is one important restriction to keep in mind, however. In a given turn, the player is not allowed to move more than two spaces in any direction. This means that the position it returns from the act() method may have an x-coordinate up to two positions away, but no more, and a y-coordinate position away, but no more.

For example, suppose the player is in position (12, 30). Then the legal positions to which it can move are

(10,32)(11,32)(12,32) (13,32)(14,32)
(10,31)(11,31)(12,31) (13,31)(14,31)
(10,30)(11,30)(12,30) (13,30)(14,30)
(10,29)(11,29)(12,29) (13,29)(14,29)
(10,28)(11,28)(12,28) (13,28)(14,28)

If the act() method returns an illegal position, then the system will not move the player at all on that turn; it will stay it the same spot. A message about the problem will be printed in the terminal window. If you want more information when this happens, add -throw to the command line, and it will throw a NullPointerException in addition to printing the complaint.

Notice that the Player class, as it is given to you, keeps track of its current position. It is important to note that this is not its official position—it's only where the Player object thinks it is by its reckoning, not where the Player actually is.

Consider this scenario. The player is in position (12, 30). When the act() method is called, it decides it wants to move to (15, 31). It updates its instance variables so myX = 15 and myY = 31 and it returns (15, 31) as the new position. The system, however, detects that (15, 31) is out of range, and so it does not allow the player to move. On the next turn the player is still in position (12, 30), but it thinks it is in position (15, 31).

The player will have a lot of trouble moving if it doesn't have an accurate record of its position, and probably all subsequent new positions will be illegal. If your player suddenly stops moving, it might be because you've made this error.

Note also that the balls move faster than the player. The player has no chance of chasing after a ball moving away from it; it instead needs to try to intercept balls coming more or less towards it.

6. How to proceed

First, determine a general strategy. Forget about programming for the moment: you need to solve the problem before you start coding anything. Watch the player as the balls move around it. Which ball should it go after, and how should it move to catch it? This will involve some math—at least some high school algebra using slopes and things like that; a fancy strategy might require some trigonometry. (Don't forget about the Math class to help with this.)

Next, think about what information the Player object will need to implement the strategy. These will need to be stored in instance variables. As one hint, almost everything passed into the constructor will need to be stored in an instance variable so it can be used in the act() method. What else needs to be stored? What needs to be remembered from one call to act() to the next?

Finally, think about algorithmic specifics and how to implement. How will the player scan the grid, looking for balls? How will it decide its next move?

(Not a good strategy: Find the closest ball and move towards it. Remember that the balls are moving—you need to move not towards where the ball is, but where it will be.)

7. A few details

Whenever a ball is caught or runs off the edge, it is replaced by a new ball at a random starting point. The new ball has the same unique id as the ball that was caught or rolled off the edge—that way there are always n balls, numbered 0 to n-1.

The balls are given a random direction. From their starting point they are pointed approximately toward the center of the grid, plus or minus at most 45 degrees. They all have the same velocity. They are not affected by friction or gravitation. (If you're a physics major, I suppose the balls not being affected by friction means you should think of them as sliding, not rolling.)

If two balls happen to collide, one of them disappears and is replaced with a new ball, just as if it had rolled off the edge.

8. Turn in

Hand in your source files as project6.

DUE: Wednesday, March 25, at 5:00 PM.


Thomas VanDrunen, Cary Gray
Last modified: Thu Mar 5 07:09:34 CST 2015