The goal of this lab is to practice using the Adapter pattern.
Evil Professor NenurdNav has prepared his own version of
a predator-prey simulation for Lab 31 of his course CSCI 542.
It is similar to ours conceptually, but all the code is different.
It includes a class Snake
, which has some interesting behavior:
You like this idea and want to plug this Snake
class into
our version of the simulation.
However, it uses all different interfaces.
You must use the adapter pattern in order to make the Snake
class work in our simulation.
Specifically, you will need to finish a few classes for which
I have given you some skeleton code, all found in
the predprey
package.
Copy the current version of the simulation code into your directory.
cp -r ~tvandrun/Public/cs245/lab13/* .
When you make an Eclipse project for this, make sure that
you select the folder containing
the folders predprey
,
simulation
,
and nenurdnav
.
If you have things set up right, then the second screen of
the new-project wizard should look something like this:
If it doesn't and you don't know why, then ask for help.
The first step is to get to know the new code.
Inspect the the files in the nenurdnav
package.
(You will not need to modify any code in this package,
although you might want to add debugging code there at some point.)
Organism
interface works. The act()
and getIcon()
methods are renamed.
Snake
works.
In particular, follow the basic outline of
the go()
method.
Snake
's go()
method
will lead you to understanding how the
Island
interface works.
Figure out what its three methods should do
(especially the putOrganism()
method).
Keep in mind that we do not have an implementation
for this interface. You'll have to write one; more on that later.
Snake
's go()
method
also makes use of an object of type PredatorDeterminer
,
which is equivalent to our PreyArbitor
.
Check out the PredatorDeterminer
interface.
Read all these instructions carefully and do these steps in order. They are fairly complicated. Along the way, we will run into certain specific problems. For each of these, first make sure you have completed the task up to the point when the problem comes up; then make sure that you understand what the problem is; then, read on to find the solution to the problem.
SnakeAdapter
, the basicsLook the class SnakeAdapter
.
It adapts a Snake
to the Agent
interface.
It has an internal Snake
that is being wrapped/adapted.
The class also has a bunch of stuff that we'll need later.
For right now, look at the end of the file where
the methods required by the Agent
interface are.
Complete these methods.
All these methods can be one-liners. You may choose an arbitrary, reasonable value for the snake's weight and color. For other operations, delegate them to the internal snake.
The Agent
interface also
requires an enterGrid()
method, and
we would like to complete that method next.
However, we have a problem:
TheSnake
object needs a reference to anIsland
object, not aSimulationGrid
object. Moreover, while an agent in our simulation receives a reference to the grid through the methodenterGrid()
(invoked sometime after the class is instantiated to make the object), theSnake
class needs a reference to thatIsland
passed to it through the constructor.
GridAdapter.putOrganism(int, int, char, int, dist,
Organism)
We need to adapt the grid so that the snake can use it as an
Island
.
(Make sure you understand all that's going on:
With SnakeAdapter
, we were taking something from
the other simulation and adapting it to fit into ours;
now we are going to take something from our simulation
and adapt it so that it can fit into the class from the other simulation.)
Look at the GridAdapter
class.
It implements Island
and has an internal grid.
The Island
interface has two putOrganism()
methods, both of which play the role that setAgentAt()
plays in our grid.
The second putOrganism()
method has an interesting
feature: you don't need to give it the absolute new position of the
organism; instead you can give a reference point, a direction,
and a distance, and it will put the organism in the position
which is the given distance away from the given position in the given
direction.
Implement this method (the putOrgansim()
method that returns an int[]
).
Read the documentation for the method in the interface carefully
to be sure you understand what it should do.
Do not put the organism in the grid directly---after all, you can't
put an Organism
in the grid, because it is
a grid of Agent
s, not Organism
s.
We'll worry about that problem later.
Instead, putOrganism(int, int, char, int, Organism)
should call the other method, putOrganism(int, int, Organism)
Once that is done, we should consider how
to extract organisms, using getOrganism()
.
However, this leads us to a problem:
TheSimulationGrid
object contains onlyAgent
s, but this method needs to return anOrganism
. Put another way, theSnake
needs to look atOrganism
s, but everything else in the simulation is anAgent
.
AgentAdapter
and GridAdapter.getOrgansim()
What we need to do is adapt Agent
s so
that
Snake
can interact with them.
This is actually a pretty easy task,
so I've already done it for you.
Look at AgentAdapter
.
It is used to wrap an Agent
(such as Rabbit
)
so that it masquerades as an Organism
.
Your job is to use this class.
First of all, you will need to do a similar calculation of the exact position
based on the relative position, distance, and direction, as in
putOrganism()
.
Then, consider what will be at that position:
either it will be a real Agent
, in which case
it needs to be wrapped in an AgentAdapter
and returned;
or, it will be a SnakeAdapter
, in which case
it needs to be unwrapped and just the internal snake
returned.
Finish the GridAdapter.getOrganism()
method..
With that experience behind us, we can start thinking about
the other putOrganism()
method.
In principle, this method is just like getOrganism()
,
but the other way around:
the input is Organism
which means it is
either a Snake
or an AgentAdapter
;
all Snake
s need to wrapped as
SnakeAdapter
s before being put into the grid,
and all AgentAdapter
s need to be unwrapped.
However, this brings us to a new problem:
The most common use ofputOrganism()
for a snake is when it moves, which means it will be putting itself into another position of the grid. However, we don't want to keep making newSnakeAdapter
s every time the snake moves. Not only would that create more objects than necessary, it will also make it difficult to give each snake a reference to the grid.
SnakeAdapter
and
GridAdapter.putOrganism(int, int, Organism)
The solution is that we keep a HashMap
mapping Snake
s to SnakeAdapter
s.
When we want to make a new adapter for a snake, we first
check to see if that snake already has an adapter, and use that
one instead.
(The only time that a snake wouldn't have an adapter is
if it were just hatched.)
Instead of making an adapter for a snake by instantiating the
class (calling the SnakeAdapter(Snake)
constructor),
the GridAdapter
class should use the static
makeAdapter()
method,
which first checks the hash map for a previously made adapter
for that snake.
Notice that the SnakeAdapter(Snake)
constructor is private and that it prints an error to
the screen if an adapter already exists for that snake.
Also notice that there is another constructor,
SnakeAdapter()
without a Snake
parameter.
This is to be called when the simulation starts and we make
the initial population of the island.
Notice that this constructor does not initialize
internal
.
More on that later.
Your task in this this part is to write the method
GridAdapter.putOrganism(int, int, Organism)
.
Note that the organism o
is one
of three things: null, a Snake
, or
an AgentAdapter
.
PDAdapter
We still need to write SnakeAdapter.enterGrid()
,
but the Snake
needs not only a grid,
but also a PredatorDeterminer
.
So, one more adapter:
PDAdapter
which wraps
the PreyArbitor
so that
it acts like an object with type PredatorDeterminer
.
One complication: PreyArbitor
is a class
with only static methods,
but PredatorDeterminer
is to be an object.
Look at the class PDAdapter
.
Notice that it is a singleton class.
Finish it by writing the preysOn()
method.
Notice that the two parameters a
and b
are each either null, a Snake
(which needs to be wrapped),
or a AgentAdapter
(which needs to be unwrapped).
(Our PreyArbitor
can handle SnakeAdapter
s.
See the file predprey.dat
.)
SnakeAdapter.enterGrid()
Remember the problem that Snake
s need
to get their Island
when they are instantiated,
but in our simulation, agents don't get their grid reference until
later, when enterGrid()
is called.
Here's how we'll solve this:
There are two ways in which a Snake
is instantiated.
Either it is part of the initial population, or it's an offspring.
In Snake.go()
, the code for making an offspring
gives the child snake a reference to the same island
as the parent snake,
so we never need to call enterGrid()
.
If the snake is part of the initial population, then what will happen
is the simulation will make a new SnakeAdapter
using the public constructor.
This doesn't actually make a new Snake
, that is,
it doesn't initialize internal
.
We'll defer that to when enterGrid()
is called.
Your final task is to write that SnakeAdapter.enterGrid()
method.
It should
Snake
giving it an adapter
for the grid and the adapter for the PreyArbitor
.
internal
with that newly instantiated
Snake
snake
hash map
appropriately.
And that's it! Now try it out.