CSCI 235 Project 6: The interaction of objects

The goal of this project is to practice designing and 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.

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

You’ll see three files.

Compile Player.java (which will cause Position.java to be compiled automatically). Because Player.java depends on classes 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 (default 1). For example,

$ 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; that ball immediately reappears at a random location on the edge of the grid. (Note that a ball does not “bounce” off the edge of the grid; it’s new location is not related to where it went off the edge.)

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”—i.e., 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.)

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 (moving from, say, (5,12) to (4, 11)) until it hits the left and bottom edges, eventually getting stuck in the lower left corner. No wonder it doesn’t catch many balls.

4. How the Player decides where to move

Your main task in this project is writing a smart act() method that will 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 provided, keeps track of its current position. It is important to note that this is not its actual position—it is only where the Player object thinks it is by its reckoning, not where the Player really 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. Do pay attention to the output in the terminal.

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—perhaps up to 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.)

You may find it convenient to make some other classes in addition to Player.

7. A few details

You can think of the player and the balls moving at the same time. So to catch a ball, the player needs to move to the same place that the ball also moves to.1

The program is set up so that the player has limited time to compute each move. If your player’s act() method runs for too long, the program will print a message and abort.

As noted earlier, if the player attempts an illegal move (such as trying to move too far in a single step), the move is ignored. A message is printed to the terminal whenever this happens. If you’d like for the program to stop, add the option -throw when you run the program.

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.

Note that to catch a ball, the player has to be in exactly the same location as the ball; you can’t catch it by moving into a square that it passes over. (So I suppose that means that you should think of the balls as bouncing.)

8. Turn in

Hand in your source file(s) as project6. Be sure to include any other classes you wrote along with Player.java.

DUE: Monday, March 28, Friday, April 1, at 5:00 PM.

1In reality, the player moves first; so you can also catch a ball by moving to where you find it.