// AP(r) Computer Science Marine Biology Simulation:
// The Fish class is copyright(c) 2002 College Entrance
// Examination Board (www.collegeboard.com).
//
// This class is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation.
//
// This class is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
import java.awt.Color;
import java.util.ArrayList;
import java.util.Random;
/**
* AP® Computer Science Marine Biology Simulation:
* A Fish
object represents a fish in the Marine Biology
* Simulation. Each fish has a unique ID, which remains constant
* throughout its life. A fish also maintains information about its
* location and direction in the environment.
*
*
* Modification History: * - Modified to support a dynamic population in the environment: * fish can now breed and die. * *
* The Fish
class is
* copyright© 2002 College Entrance Examination Board
* (www.collegeboard.com).
*
* @author Alyce Brady
* @author APCS Development Committee
* @version 1 July 2002
* @see Environment
* @see Direction
* @see Location
**/
public class Fish implements Locatable
{
// Class Variable: Shared among ALL fish
private static int nextAvailableID = 1; // next avail unique identifier
// Instance Variables: Encapsulated data for EACH fish
private Environment theEnv; // environment in which the fish lives
private int myId; // unique ID for this fish
private Location myLoc; // fish's location
private Direction myDir; // fish's direction
private Color myColor; // fish's color
// THE FOLLOWING TWO INSTANCE VARIABLES ARE NEW IN CHAPTER 3 !!!
private double probOfBreeding; // defines likelihood in each timestep
private double probOfDying; // defines likelihood in each timestep
// I added this!
private int myAge;
// constructors and related helper methods
/** Constructs a fish at the specified location in a given environment.
* The Fish is assigned a random direction and random color.
* (Precondition: parameters are non-null; loc
is valid
* for env
.)
* @param env environment in which fish will live
* @param loc location of the new fish in env
**/
public Fish(Environment env, Location loc)
{
initialize(env, loc, env.randomDirection(), randomColor());
}
/** Constructs a fish at the specified location and direction in a
* given environment. The Fish is assigned a random color.
* (Precondition: parameters are non-null; loc
is valid
* for env
.)
* @param env environment in which fish will live
* @param loc location of the new fish in env
* @param dir direction the new fish is facing
**/
public Fish(Environment env, Location loc, Direction dir)
{
initialize(env, loc, dir, randomColor());
}
/** Constructs a fish of the specified color at the specified location
* and direction.
* (Precondition: parameters are non-null; loc
is valid
* for env
.)
* @param env environment in which fish will live
* @param loc location of the new fish in env
* @param dir direction the new fish is facing
* @param col color of the new fish
**/
public Fish(Environment env, Location loc, Direction dir, Color col)
{
initialize(env, loc, dir, col);
}
/** Initializes the state of this fish.
* (Precondition: parameters are non-null; loc
is valid
* for env
.)
* @param env environment in which this fish will live
* @param loc location of this fish in env
* @param dir direction this fish is facing
* @param col color of this fish
**/
private void initialize(Environment env, Location loc, Direction dir,
Color col)
{
theEnv = env;
myId = nextAvailableID;
nextAvailableID++;
myLoc = loc;
myDir = dir;
myColor = col;
theEnv.add(this);
myAge = 0;
// object is at location myLoc in environment
// THE FOLLOWING CODE IS NEW IN CHAPTER 3 !!!
// For now, every fish is equally likely to breed or die in any given
// timestep, although this could be individualized for each fish.
probOfBreeding = 1.0/7.0; // 1 in 7 chance in each timestep
probOfDying = 0.0/5.0; // 1 in 5 chance in each timestep
}
/** Generates a random color.
* @return the new random color
**/
protected Color randomColor()
{
// There are 256 possibilities for the red, green, and blue attributes
// of a color. Generate random values for each color attribute.
// Random randNumGen = RandNumGenerator.getInstance();
// return new Color(randNumGen.nextInt(256), // amount of red
// randNumGen.nextInt(256), // amount of green
// randNumGen.nextInt(256)); // amount of blue
// return new Color(Color.HSBtoRGB((float)myAge/(float)240,(float)1.0,(float)0.5));
return new Color(Color.HSBtoRGB((float)(2*Math.atan(myAge/100.0)/Math.PI),(float)1.0,(float)1.0));
}
// accessor methods
/** Returns this fish's ID.
* @return the unique ID for this fish
**/
public int id()
{
return myId;
}
/** Returns this fish's environment.
* @return the environment in which this fish lives
**/
public Environment environment()
{
return theEnv;
}
/** Returns this fish's color.
* @return the color of this fish
**/
public Color color()
{
return myColor;
}
/** Returns this fish's location.
* @return the location of this fish in the environment
**/
public Location location()
{
return myLoc;
}
/** Returns this fish's direction.
* @return the direction in which this fish is facing
**/
public Direction direction()
{
return myDir;
}
/**
* Returns this fish's age
* @return the age of this fish in timesteps
*/
public int age(){
return myAge;
}
/** Checks whether this fish is in an environment.
* @return true
if the fish is in the environment
* (and at the correct location); false
otherwise
**/
public boolean isInEnv()
{
return environment().objectAt(location()) == this;
}
/** Returns a string representing key information about this fish.
* @return a string indicating the fish's ID, location, and direction
**/
public String toString()
{
String ans = getClass().getName() + ":" + age() + "-" + id();
if (isInEnv())
return ans + location() + direction();
return ans + "(dead)";
}
// modifier method
// THE FOLLOWING METHOD IS MODIFIED FOR CHAPTER 3 !!!
// (was originally a check for aliveness and a simple call to move)
/** Acts for one step in the simulation.
**/
public void act()
{
// Make sure fish is alive and well in the environment -- fish
// that have been removed from the environment shouldn't act.
if ( ! isInEnv() )
return;
myColor = randomColor();
// Try to breed.
if ( ! breed() )
// Did not breed, so try to move.
move();
myAge++;
// Determine whether this fish will die in this timestep.
Random randNumGen = RandNumGenerator.getInstance();
if ( randNumGen.nextDouble() < probOfDying )
die();
}
// internal helper methods
// THE FOLLOWING METHOD IS NEW FOR CHAPTER 3 !!!
/** Attempts to breed into neighboring locations.
* @return true
if fish successfully breeds;
* false
otherwise
**/
protected boolean breed()
{
// Determine whether this fish will try to breed in this
// timestep. If not, return immediately.
Random randNumGen = RandNumGenerator.getInstance();
if ( randNumGen.nextDouble() >= probOfBreeding )
return false;
// Get list of neighboring empty locations.
ArrayList emptyNbrs = emptyNeighbors();
Debug.print(this + " attempting to breed. ");
Debug.println("Has neighboring locations: " + emptyNbrs);
// If there is nowhere to breed, then we're done.
if ( emptyNbrs.size() == 0 )
{
Debug.println(" Did not breed.");
return false;
}
double probOfBreedingToALocation = 1.0/emptyNbrs.size();
// Breed to some of the empty neighboring locations.
for ( int index = 0; index < emptyNbrs.size(); index++ )
{
Location loc = (Location) emptyNbrs.get(index);
if (randNumGen.nextDouble() < probOfBreedingToALocation)
generateChild(loc);
}
return true;
}
// THE FOLLOWING METHOD IS NEW FOR CHAPTER 3 !!!
/** Creates a new fish with the color of its parent.
* @param loc location of the new fish
**/
protected void generateChild(Location loc)
{
Class[] classes = {Environment.class, Location.class};
Object[] args = {environment(), loc};
Fish child;
// Create new fish, which adds itself to the environment.
try {
child = (Fish) getClass().getConstructor(classes).newInstance(args);
}catch (Exception e){child = null;}
Debug.println(" New Fish created: " + child);
}
/** Moves this fish in its environment.
**/
protected void move()
{
// Find a location to move to.
Debug.print(this + " attempting to move. ");
Location nextLoc = nextLocation();
// If the next location is different, move there.
if ( ! nextLoc.equals(location()) )
{
// Move to new location.
Location oldLoc = location();
Fish enemy = (Fish) theEnv.objectAt(nextLoc);
if (enemy != null)
if (age() > enemy.age()){
theEnv.remove(enemy);
changeLocation(nextLoc);
// Update direction in case fish had to turn to move.
Direction newDir = environment().getDirection(oldLoc, nextLoc);
changeDirection(newDir);
Debug.println(" Moves to " + location() + direction() + " and eats " + enemy);
}
else
Debug.println(" Does not move (" + enemy + " in the way).");
else {
changeLocation(nextLoc);
// Update direction in case fish had to turn to move.
Direction newDir = environment().getDirection(oldLoc, nextLoc);
changeDirection(newDir);
Debug.println(" Moves to " + location() + direction());
}
}
else
Debug.println(" Does not move.");
}
/** Finds this fish's next location.
* A fish may move to any empty adjacent locations except the one
* behind it (fish do not move backwards). If this fish cannot
* move, nextLocation
returns its current location.
* @return the next location for this fish
**/
protected Location nextLocation()
{
// Get list of neighboring empty locations.
// ArrayList emptyNbrs = emptyNeighbors();
ArrayList emptyNbrs = theEnv.neighborsOf(myLoc);
// Remove the location behind, since fish do not move backwards.
Direction oppositeDir = direction().reverse();
Location locationBehind = environment().getNeighbor(location(),
oppositeDir);
emptyNbrs.remove(locationBehind);
Debug.print("Possible new locations are: " + emptyNbrs);
// If there are no valid empty neighboring locations, then we're done.
if ( emptyNbrs.size() == 0 )
return location();
// Return a randomly chosen neighboring empty location.
Random randNumGen = RandNumGenerator.getInstance();
int randNum = randNumGen.nextInt(emptyNbrs.size());
return (Location) emptyNbrs.get(randNum);
}
/** Finds empty locations adjacent to this fish.
* @return an ArrayList containing neighboring empty locations
**/
protected ArrayList emptyNeighbors()
{
// Get all the neighbors of this fish, empty or not.
ArrayList nbrs = environment().neighborsOf(location());
// Figure out which neighbors are empty and add those to a new list.
ArrayList emptyNbrs = new ArrayList();
for ( int index = 0; index < nbrs.size(); index++ )
{
Location loc = (Location) nbrs.get(index);
if ( environment().isEmpty(loc) )
emptyNbrs.add(loc);
}
return emptyNbrs;
}
/** Modifies this fish's location and notifies the environment.
* @param newLoc new location value
**/
protected void changeLocation(Location newLoc)
{
// Change location and notify the environment.
Location oldLoc = location();
myLoc = newLoc;
environment().recordMove(this, oldLoc);
// object is again at location myLoc in environment
}
/** Modifies this fish's direction.
* @param newDir new direction value
**/
protected void changeDirection(Direction newDir)
{
// Change direction.
myDir = newDir;
}
// THE FOLLOWING METHOD IS NEW FOR CHAPTER 3 !!!
/** Removes this fish from the environment.
**/
protected void die()
{
Debug.println(this + " about to die.");
environment().remove(this);
}
}