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.
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.
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.
Player
classOpen 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.
Player
decides where to moveYour 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.
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.
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.)
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.
Hand in your source files as project6.
DUE: Wednesday, Nov 4, at 5:00 PM.