package jwo.jpss.ants; // Part of the ant simulation package.
import java.awt.*; // For drawing the ant.
import jwo.jpss.spatial.*; // For spatial footprint.
import java.util.*; // For dynamic collections.
// *******************************************************
/** Class for representing and drawing Ants.
* @author Jo Wood
* @version 1.2, 23rd August, 2001.
*/
// *******************************************************
public class Ant extends Animal implements Dynamic,Drawable
{
// --------------- Object and class variables ---------------
protected int straightSteps, // Num. steps taken in a single direction.
numSteps; // Num. steps walked by ant.
protected float homingInstinct; // Liklihood of ant returning to nest.
protected boolean goingHome;
protected int maxFood; // Maximum amount of food ant can eat.
protected Color colour; // Ant colour.
protected float xDir,yDir; // Current direction taken by ant.
// Component drawing the graphics.
protected GraphicsListener gListener;
private Nest nest; // Nest in which this ant was born.
private int foodSize; // Size of food being carried.
// Declare immutable ant 'laws'
private static final float MUTATION = 0.05f; // Proportion of mutation.
private static final int MAX_STRAIGHT_STEPS = 20; // Maximum straight steps.
private static final float MAX_HOMING_INSTINCT = 0.1f; // Initial maximum homing prob.
private static final int MAX_FOOD = 10000; // Initial maximum food capacity.
private static final int WIDTH = 6; // Width of ant.
private static final int HEIGHT = 3; // Height of ant.
private static final int FEED_RATE = 100; // Food per cycle that can be eaten.
private static final int DONATION_LEVEL = 1000; // Food level at which ant gives to nest.
private static final int METABOLIC_RATE = 1; // Rate at which ant consumes food.
// --------------------- Constructors ---------------------
/** Creates an ant with a given food level and location.
* @param foodLevel Initial food level of the ant.
* @param x Initial x location of the ant.
* @param y Initial y location of the ant.
* @param colour Colour of ant.
* @param nest Nest in which this ant was born.
*/
public Ant(int foodLevel, float x, float y, Color colour, Nest nest)
{
super(foodLevel, new Footprint(x,y,WIDTH,HEIGHT));
this.nest = nest;
this.colour = colour;
straightSteps = (int)(Math.random()*MAX_STRAIGHT_STEPS)+1;
numSteps = (int)(Math.random()*10);
homingInstinct= (float)Math.random()*MAX_HOMING_INSTINCT;
goingHome = false;
maxFood = (int)(Math.random()*MAX_FOOD);
foodSize = 0;
}
/** Creates an ant with similar characteristics of the given
* parent. Applies a small genetic mutation to inherited characteristics.
* @param foodLevel Initial food level of the ant.
* @param parent Ant supplying inheritable characteristics.
* @param x Initial x location of the ant.
* @param y Initial y location of the ant.
* @param nest Nest in which this ant was born.
*/
public Ant(int foodLevel, Ant parent, float x, float y, Nest nest)
{
super(foodLevel, new Footprint(x,y,WIDTH,HEIGHT));
this.nest = nest;
float mutation;
numSteps = (int)(Math.random()*10);
foodSize = 0;
// Inherit straight steps with mutation.
mutation = (float)(MAX_STRAIGHT_STEPS*MUTATION*(Math.random() - 0.5f));
straightSteps = Math.round(parent.getStraightSteps()+mutation);
if (straightSteps straightSteps = 1;
// Inherit homing instinct with mutation.
mutation = (float)(MAX_HOMING_INSTINCT*MUTATION*(Math.random()- 0.5f));
homingInstinct = parent.getHomingInstinct()+mutation;
// Inherit maximum food capacity.
mutation = (float)(MAX_FOOD*MUTATION*(Math.random() - 0.5f));
maxFood = Math.round(parent.getMaxFood()+mutation);
// Inherit parent colour with mutation.
colour = parent.getColour();
if (Math.random() {
if (Math.random() < 0.5)
colour = colour.brighter();
else
colour = colour.darker();
}
}
// ------------------- Accessor Methods -------------------
/** Reports how many steps the ant will take before considering
* changing direction.
* @return Number of straight steps taken.
*/
public int getStraightSteps()
{
return straightSteps;
}
/** Reports the homing instinct of the ant. Instinct varies between
* 0 and 1 and represents the probability that the ant will move
* in a nestward direction whenever it changes direction.
* @return Homing instinct between 0 and 1.
*/
public float getHomingInstinct()
{
return homingInstinct;
}
/** Reports much food can be carried by the ant.
* @return Number of food units the ant can carry.
*/
public int getMaxFood()
{
return maxFood;
}
/** Reports the colour of this ant.
* @return Colour of this ant.
*/
public Color getColour()
{
return colour;
}
// ------------------ Overridden methods ------------------
/** Lets the ant eat some food and updates the size of any
* carried food.
* @param foodUnits Number of food units to eat.
*/
public void eat(int foodUnits)
{
super.eat(foodUnits);
foodSize = Math.round(getFoodLevel()/1000f);
}
/** Let the ant metabolise some food and updates the size of any
* carried food.
* @param foodUnits Number of food units to metabolise.
*/
public void metabolise(int foodUnits)
{
super.metabolise(foodUnits);
foodSize = Math.round(getFoodLevel()/1000f);
}
// ------------------ Implemented methods -----------------
/** Draws the ant using the given graphics context.
* @param g Graphics context to draw to.
*/
public void paint(Graphics g)
{
Footprint fp = getBounds();
int x = Math.round(fp.getXOrigin());
int y = Math.round(fp.getYOrigin());
int width = Math.round(fp.getMERWidth());
int height = Math.round(fp.getMERHeight());
g.setColor(colour);
g.fillOval(x,y,width,height);
if (getFoodLevel() > 1000)
{
g.setColor(new Color(0,155,100));
g.fillRect(x+width,y, foodSize+1, foodSize+1);
g.setColor(colour);
g.fillRect(x+width,y, foodSize+1, foodSize+1);
}
}
/** Adds a graphics listener to this ant. Allows graphics to be
* drawn by a GraphicsListener.
* @param gListener Component doing the drawing.
*/
public void addGraphicsListener(GraphicsListener gListener)
{
this.gListener = gListener;
}
/** Let the ant go about its business for one time unit.
*/
public void evolve()
{
metabolise(METABOLIC_RATE);
if (isAlive())
{
// Find out if the ant is sitting on anything.
Enumeration enum = gListener.objectsAt(this).elements();
while (enum.hasMoreElements())
{
SpatialObject spObject = (SpatialObject) enum.nextElement();
// If ant is sitting on some food, eat it.
if (spObject instanceof FoodSource)
{
// Attempt to eat food at feeding rate.
FoodSource foodSource = (FoodSource)spObject;
int foodRemoved=0;
if (getFoodLevel()+FEED_RATE foodRemoved = foodSource.removeFood(FEED_RATE);
if (foodRemoved > 0)
{
eat(foodRemoved);
return;
}
}
// If ant finds itself at its own nest, the nest is alive and
// the ant has enough food units, give half its food to the nest.
if (spObject == nest)
{
int foodLevel = getFoodLevel();
goingHome = false;
if ((foodLevel > DONATION_LEVEL) && (nest.isAlive()))
{
nest.addFood(this,foodLevel/2);
metabolise(foodLevel/2);
return;
}
}
}
// Change direction if any has walked sufficient steps.
if (numSteps%straightSteps == 0)
changeAntDirection();
move(xDir,yDir);
// Only move if ant is within bounds.
if (gListener.canDraw(this))
numSteps++;
else
{
// Change direction if cannot move.
move(-xDir,-yDir);
changeAntDirection();
}
}
}
// ---------------------- Private Methods ----------------------
/** Sets a new direction for ant to walk in.
*/
private void changeAntDirection()
{
// No need to change direction if ant is already going home.
if (goingHome)
return;
// Speed of ant depends on how much it is carrying.
float speed = 10f/(5f+getFoodLevel()/1000f);
// If ant decides to go home for first time, calculate direction.
if ((Math.random() < homingInstinct) ||
(maxFood - getFoodLevel() < FEED_RATE))
{
goingHome = true;
Footprint nestFP = nest.getBounds(),
antFP = getBounds();
float dx =(nestFP.getXOrigin() + nestFP.getMERWidth()/2) -
(antFP.getXOrigin() + antFP.getMERWidth()/2);
float dy =(nestFP.getYOrigin() + nestFP.getMERHeight()/2) -
(antFP.getYOrigin() + antFP.getMERHeight()/2);
float scaling = (float)Math.sqrt(dx*dx + dy*dy)/speed;
if (scaling != 0)
{
xDir = dx/scaling;
yDir = dy/scaling;
}
}
else // Otherwise calculate new random direction.
{
xDir = (float)(Math.random()*2-1)*speed;
yDir = (float)(Math.random()*2-1)*speed;
}
}
}