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; } }