package biosim;
import java.util.*;
import java.awt.Dimension;
public class Organism {
/**
* Just what it looks like: true if the Organism is live, false
* if the Organism is dead. All Organisms are alive when
* born (constructed).
*/
protected boolean alive = true;
/**
* A general-purpose random number generator for the Organisms to use.
*/
protected static Random rand = new Random();
/**
* The maximum number of units this organism can move
* in either x or y in a single time step.
* By default, the maximum velocity is 5.0.
*/
public double maxVelocity = 5.0;
/**
* A handle to the Ecosystem that contains this organism.
*/
Ecosystem ecosystem;
/**
* What is this organism's radius? By default, the radius of
* an Organism at birth is 3.0;
*/
protected double radius;
/**
* By default, the radius of an Organism at birth is 3.0;
*/
protected double birthRadius = 3.0;
/**
* What is this organism's position in the 2D space?
*/
protected XYPair position = new XYPair();
/**
* What are the x and y components of
* the Organism's velocity?
*/
protected XYPair velocity = new XYPair();
/**
* By what percentage does this organism's size increase per time step?
* Default value is 0.05; that is, 5%.
*/
protected double growthRate = 0.05;
/**
* This organism reproduces once every __ time steps? (Default is 20).
* Organisms that do not reproduce at regular intervals, and instead
* control their reproduction in another way -- perhaps by sexual reproduction,
* may set this value to -1 in order to turn off any automatic
* reproduction provided by the base class.
*/
protected int birthInterval = 20;
/**
* How many time steps has this organism been alive?
*/
protected int age = 0;
/**
* At what age does this Organism die naturally? Default is 45.
*/
protected int lifespan = 45;
/////////////////////////////////////////////////////////////
/**
* Default constructor. Not useful except in combination with other constructors,
* most notably because it initializes the Organism's ecosystem to null.
*/
public Organism() {
alive = true;
radius = birthRadius;
position = new XYPair(0.0, 0.0);
velocity = new XYPair();
velocity.x = ((2.0 * rand.nextDouble()) - 1.0) * maxVelocity;
velocity.y = ((2.0 * rand.nextDouble()) - 1.0) * maxVelocity;
ecosystem = null;
}
/**
* Construct an Organism at (px, py), in Ecosystem e, with random velocity in x and y
*/
public Organism(double px, double py, Ecosystem e) {
alive = true;
position = new XYPair(px, py);
radius = birthRadius;
ecosystem = e;
velocity = new XYPair();
velocity.x = ((2.0 * rand.nextDouble()) - 1.0) * maxVelocity;
velocity.y = ((2.0 * rand.nextDouble()) - 1.0) * maxVelocity;
}
/**
* Construct an Organism at (px, py), in Ecosystem e, with random velocity in x and y
*/
public Organism(int px, int py, Ecosystem e) {
this((double) px, (double) py, e);
}
/**
* Construct an Organism at (px, py), in Ecosystem e, with velocities vx and vy
*/
public Organism(double px, double py, double vx, double vy, Ecosystem e) {
alive = true;
position = new XYPair(px, py);
velocity = new XYPair(vx, vy);
radius = birthRadius;
ecosystem = e;
}
/////////////////////////////////////////////////////////////
/**
* Return the position of the Organism.
*/
public XYPair getPosition() {
return new XYPair(position);
}
/**
* Sets the position of the Organism.
*/
synchronized public void setPosition(double x, double y) {
position.x = x;
position.y = y;
}
/**
* Return the velocity of the Organism.
*/
public XYPair getVelocity() {
return new XYPair(velocity);
}
/**
* Sets the velocity of the Organism.
*/
synchronized public void setVelocity(double x, double y) {
velocity.x = x;
velocity.y = y;
trimVelocity();
}
/**
* Sets the velocity of this Organism.
*/
synchronized public void setVelocity(XYPair v) {
setVelocity(v.x, v.y);
}
/**
* Returns the age of the Organism.
*/
public int getAge() {
return age;
}
/**
* Return the radius of the Organism.
*/
public double getRadius() {
return radius;
}
/**
* Return the "mass" of the Organism;
* actually, it is the square of the Organism's
* radius that is returned.
*/
public double getMass() {
return radius * radius;
}
/**
* The Ecosystem calls this message to send
* the Organism an EcoEvent, typically the result
* of a collision with another Organism. This method
* defines the way in which this Organism interacts
* with other Organisms. In order, for instance, to
* implement a predator Organism, this method would need
* to be overriden so that the predator can attack its
* prey. In order for an Organism to survive an attack
* by a predator, it would need to be provided with
* some sort of specialized behavior in response to,
* or perhaps anticipation of, an attack.
*
* The default method simply emulates an elastic collision,
* where each Organism rebounds off of the other. Subclasses
* needing other behavior should override this method, but if
* the "bounce" is still required, they may wish to call
* super.react(e) in order to uncover this functionality.
*/
public void react(EcoEvent e) {
switch (e.id) {
case EcoEvent.COLLISION:
// Use the other organism's velocity and mass
// to determine our new velocity, after "bouncing"
// off of the other organism.
XYPair new_v = new XYPair();
XYPair other_v = (XYPair) e.attachment;
double other_m = (e.from.getMass());
// Formula for elastic collisions:
// v' = (2MV + v(m - M)) / (M + m)
new_v.x = (2 * other_m * other_v.x +
(velocity.x * (getMass() - other_m))) /
(other_m + getMass());
new_v.y = (2 * other_m * other_v.y +
(velocity.y * (getMass() - other_m))) /
(other_m + getMass());
setVelocity(new_v);
break;
case EcoEvent.ATTACK:
// The default behavior, if attacked, is
// to die instantly and tell the Ecosystem
// to notify its attacker.
die(e.from);
break;
default:
System.err.println("Unrecognized EcoEvent ID: " +
e.id);
System.err.println("\tSent to Organism of class " +
e.to.getClass().getName());
break;
}
}
/**
* This method is executed once per tick of the
* Ecosystem's clock. In this method, the Organism
* carries out its normal business. It ages by one tick,
* grows by its growth rate, reproduces if it is time, and
* dies if it has grown too old. This method defines an Organism's
* natural life pattern. The advance() method, along with the react()
* method, will typically be overridden to provide
* new behavior for a subclass of Organism, this is the method
* that you would typically override.
*/
public void advance() {
age++;
if (age >= lifespan) {
die();
return;
}
grow();
if (birthInterval != -1) {
// This organism reproduces at a regular rate.
if ((age % birthInterval) == 0) {
reproduce();
}
}
move();
}
/**
* This method is called by setVelocity in order to insure
* that the velocities do not exceed the Organism's maximum
* velocity.
*/
void trimVelocity() {
if (velocity.x > maxVelocity) {
velocity.x = maxVelocity;
} else if (velocity.x < -maxVelocity) {
velocity.x = -maxVelocity;
}
if (velocity.y > maxVelocity) {
velocity.y = maxVelocity;
} else if (velocity.y < -maxVelocity) {
velocity.y = -maxVelocity;
}
}
/**
* This method determines the organism's pattern of movement.
* It is in this method that the Organism queries the ecosystem
* to determine whether it has strayed out of bounds, and adjusts
* its heading accordingly. Then, it moves in x by its velocity.x,
* and in y by its velocity.y. This method should not typically be
* called from the outside. Instead, it is called as part of an
* organisms advance() method.
*/
synchronized protected void move() {
Dimension d = ecosystem.size();
// If out of bounds, adjust velocity to head back in
if (position.x - radius <= 0.0) {
velocity.x = Math.abs(velocity.x);
} else if (position.x + radius >= (double) d.width){
velocity.x = -(Math.abs(velocity.x));
}
if (position.y - radius <= 0.0) {
velocity.y = Math.abs(velocity.y);
} else if (position.y + radius >= (double) d.height){
velocity.y = -(Math.abs(velocity.y));
}
position.x += velocity.x;
position.y += velocity.y;
}
/**
* Causes the Organism to grow by its growth rate.
*/
synchronized protected void grow() {
radius += (growthRate * radius);
}
/**
* Unlike the non-parameterized grow() method, which uses a preset
* percentage to increment the radius, this method simply
* adds the amount parameter to the radius.
*/
synchronized protected void grow(double amount) {
radius += amount;
}
/**
* When this method is called, the Organism sends a
* DEATH EcoEvent to the Ecosystem, informing it that the
* Organism must be removed from the list of living
* Organisms. Either an Organism, or a list of Organisms
* may be passed in to be notified of the death.
*/
synchronized protected void die(Object attachment) {
alive = false;
// Construct a new DEATH event to send to the Ecosystem, to occur as soon as possible.
EcoEvent death = new EcoEvent(EcoEvent.DEATH, EcoEvent.NOW,
null, this, attachment);
ecosystem.schedule(death);
}
synchronized protected void die() {
die(null);
}
/**
* When an Organism is ready to give birth, it calls this
* method, which constructs one or more new Organisms,
* stores them in a Vector, and constructs an EcoEvent
* with an id of BIRTH, placing the offspring Vector in the
* attachment field. This way, the Organism class
* keeps the responsibility of creating new Organisms
* and populating their fields.
*
* In the default version, the offspring Vector is created
* with only one new Organism, located next to but not
* touching this one, moving in the opposite direction.
*
* Classes derived from Organism must override this method
* in order to create new organisms of the specific class
* to which they belong.
*/
protected void reproduce() {
Vector offspring = new Vector();
double px = (velocity.x >= 0)
? position.x - radius - birthRadius
: position.x + radius + birthRadius;
double py = (velocity.y >= 0)
? position.y - radius - birthRadius
: position.y + radius + birthRadius;
// Create one new child.
Organism child = new Organism(px, py,
-velocity.x, -velocity.y, ecosystem);
// Add the child to the offspring vector.
offspring.addElement(child);
// Create a BIRTH event to happen as soon as possible
EcoEvent birth = new EcoEvent(EcoEvent.BIRTH, EcoEvent.NOW, null,
this, offspring);
// Tell the Ecosystem to schedule the birth event
ecosystem.schedule(birth);
}
/**
* This method should be implemented to return true if the other
* Organism belongs to a class of Organisms that this one might
* prey upon. By default, this method returns false. It must
* be overridden to implement a predator class. The determination
* can be made using a combination of the getClass() method and
* the instanceof operator. Note that the method is declared
* as public. This allows flexibility for future implementations:
* Another Organism might call this method, passing itself as a
* parameter, in order to determine if it is in danger; if necessary,
* control could be shifted to the Ecosystem, or an observing class,
* which might call this method in order to determine whether or not to
* send some kind of a warning message to an Organism.
*/
public boolean preysOn(Organism other) {
return false;
}
/**
* Returns the distance from the Organism's center to the
* point represented by the XYPair.
*/
public double distanceTo(XYPair p) {
double a = Math.abs(p.x - position.x);
double b = Math.abs(p.y - position.y);
return (Math.sqrt(a*a + b*b)); // Thanks, Pythagoras!
}
/**
* Returns the distance from this Organism's center to the other's.
*/
public double distanceTo(Organism other) {
return distanceTo(other.getPosition());
}
/**
* Returns true if the other Organism is touching this one.
*/
public boolean isTouching(Organism other) {
return (distanceTo(other) <= (radius + other.radius));
}
/**
* Returns true if the Organism is alive, false if dead.
*/
public boolean isAlive() {
return alive;
}
}