/*
Copyright 2006 by Sean Luke and George Mason University
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
package sim.app.antsforage;
import sim.field.grid.*;
import sim.portrayal.*;
import sim.portrayal.simple.*;
import sim.util.*;
import sim.engine.*;
import java.awt.*;
public /*strictfp*/ class Ant extends OvalPortrayal2D implements Steppable
{
public static final int ADD_PHEROMONE = 0;
public static final int MAX_PHEROMONE = 1;
public static final int LOCAL_PHEROMONE = 2;
public static final int PHEROMONE_TYPE = LOCAL_PHEROMONE;
public static final boolean TOROIDAL_WORLD = false;
// type of Ant
public static final int ORIENTED_ANT = 0;
public static final int NSEW_ANT = 1;
public static final int EIGHT_NEIGHBOURS_ANT = 2;
public static final int ANT_TYPE = ORIENTED_ANT;
public static final boolean GREEDY_REPOSITIONING = true;
public static final boolean GREEDY_EXPLORATION = true;
public static final int N = 0;
public static final int NE = 1;
public static final int E = 2;
public static final int SE = 3;
public static final int S = 4;
public static final int SW = 5;
public static final int W = 6;
public static final int NW = 7;
public double pheromoneToLeaveBehind;
public double minPheromone;
public double maxPheromone;
public int timeToLive;
double subtractingRatio;
double pheromoneRatio;
int orientation;
public boolean getHasFoodItem() { return hasFoodItem; }
public void setHasFoodItem(boolean val) { hasFoodItem = val; }
public boolean hasFoodItem;
public static final double ANT_K = 0.001;
public static final double ANT_N = 10.0;
boolean justCreated;
public Ant( int orientation,
double pheromoneToLeaveBehind,
double minPheromone,
double maxPheromone,
int timeToLive )
{
this.orientation = orientation;
this.pheromoneToLeaveBehind = pheromoneToLeaveBehind;
this.minPheromone = minPheromone;
this.maxPheromone = maxPheromone;
this.timeToLive = timeToLive;
subtractingRatio = ( 1.0 / timeToLive ) * maxPheromone;
pheromoneRatio = 1.0 * maxPheromone;
hasFoodItem = false;
justCreated = true;
}
protected void addInformation( final SimState state, int x, int y, final int orientation )
{
final AntsForage af = (AntsForage)state;
final DecisionInfo di = af.decisionInfo;
final DecisionMaker decisionMaker = af.decisionMaker;
if( TOROIDAL_WORLD )
{
x = (x+AntsForage.GRID_WIDTH)%AntsForage.GRID_WIDTH;
y = (y+AntsForage.GRID_HEIGHT)%AntsForage.GRID_HEIGHT;
}
else
{
if( x < 0 || x >= AntsForage.GRID_WIDTH || y < 0 || y >= AntsForage.GRID_HEIGHT )
return;
}
if( ( af.buggrid.getObjectsAtLocation(x,y) == null ||
af.buggrid.getObjectsAtLocation(x,y).numObjs < AntsForage.MAX_ANTS_PER_LOCATION ) &&
af.obstacles.field[x][y] <= 0.5 )
{
// toroidal coordinates!
di.position.x = x;
di.position.y = y;
di.orientation = orientation;
// di.homePheromoneAmount = /*Strict*/Math.pow( ANT_K + ((AntsForage)state).toHomeGrid.field[di.position.x][di.position.y], ANT_N) ;
// di.foodPheromoneAmount = /*Strict*/Math.pow( ANT_K + ((AntsForage)state).toFoodGrid.field[di.position.x][di.position.y], ANT_N );;
di.homePheromoneAmount = 0.001 + af.toHomeGrid.field[di.position.x][di.position.y];
di.foodPheromoneAmount = 0.001 + af.toFoodGrid.field[di.position.x][di.position.y];
decisionMaker.addInfo( di );
}
}
public DecisionInfo decideAction( final SimState state, final int myx, final int myy, final int orientation )
{
final AntsForage af = (AntsForage)state;
final DecisionMaker decisionMaker = af.decisionMaker;
decisionMaker.reset();
// collect the sensory information for the new grid model
// this should be done separately in the model, but whatever....
switch( ANT_TYPE )
{
case ORIENTED_ANT:
switch( orientation )
{
case 0: addInformation( state, myx-1, myy+1, (orientation+7)%8 ); // forward-left
addInformation( state, myx, myy+1, orientation ); // forward
addInformation( state, myx+1, myy+1, (orientation+1)%8 ); // forward-right
break;
case 1: addInformation( state, myx, myy+1, (orientation+7)%8 ); // forward-left
addInformation( state, myx+1, myy+1, orientation ); // forward
addInformation( state, myx+1, myy, (orientation+1)%8 ); // forward-right
break;
case 2: addInformation( state, myx+1, myy+1, (orientation+7)%8 ); // forward-left
addInformation( state, myx+1, myy, orientation ); // forward
addInformation( state, myx+1, myy-1, (orientation+1)%8 ); // forward-right
break;
case 3: addInformation( state, myx+1, myy, (orientation+7)%8 ); // forward-left
addInformation( state, myx+1, myy-1, orientation ); // forward
addInformation( state, myx, myy-1, (orientation+1)%8 ); // forward-right
break;
case 4: addInformation( state, myx+1, myy-1, (orientation+7)%8 ); // forward-left
addInformation( state, myx, myy-1, orientation ); // forward
addInformation( state, myx-1, myy-1, (orientation+1)%8 ); // forward-right
break;
case 5: addInformation( state, myx, myy-1, (orientation+7)%8 ); // forward-left
addInformation( state, myx-1, myy-1, orientation ); // forward
addInformation( state, myx-1, myy, (orientation+1)%8 ); // forward-right
break;
case 6: addInformation( state, myx-1, myy-1, (orientation+7)%8 ); // forward-left
addInformation( state, myx-1, myy, orientation ); // forward
addInformation( state, myx-1, myy+1, (orientation+1)%8 ); // forward-right
break;
case 7: addInformation( state, myx-1, myy, (orientation+7)%8 ); // forward-left
addInformation( state, myx-1, myy+1, orientation ); // forward
addInformation( state, myx, myy+1, (orientation+1)%8 ); // forward-right
break;
}
break;
case NSEW_ANT:
addInformation( state, myx-1, myy, (orientation+7)%8 ); // N
addInformation( state, myx+1, myy, (orientation+7)%8 ); // S
addInformation( state, myx, myy-1, (orientation+7)%8 ); // E
addInformation( state, myx, myy+1, (orientation+7)%8 ); // W
break;
case EIGHT_NEIGHBOURS_ANT:
addInformation( state, myx-1, myy-1, (orientation+7)%8 ); // N
addInformation( state, myx-1, myy, (orientation+7)%8 ); // N
addInformation( state, myx-1, myy+1, (orientation+7)%8 ); // N
addInformation( state, myx+1, myy-1, (orientation+7)%8 ); // S
addInformation( state, myx+1, myy, (orientation+7)%8 ); // S
addInformation( state, myx+1, myy+1, (orientation+7)%8 ); // S
addInformation( state, myx-1, myy, (orientation+7)%8 ); // E
addInformation( state, myx+1, myy, (orientation+7)%8 ); // W
break;
}
if( hasFoodItem )
return decisionMaker.getHomeGreedyDecision( state );
else
{
if( GREEDY_EXPLORATION )
return decisionMaker.getFoodGreedyDecision( state );
else
return decisionMaker.getFoodDecision( state );
}
}
public void addPheromone(DoubleGrid2D grid, int x, int y, double pheromone)
{
switch( PHEROMONE_TYPE )
{
case ADD_PHEROMONE:
grid.field[x][y] += /*Strict*/Math.abs(pheromoneToLeaveBehind);
if (grid.field[x][y] > maxPheromone)
grid.field[x][y] = maxPheromone;
break;
case MAX_PHEROMONE:
grid.field[x][y] = /*Strict*/Math.max( grid.field[x][y], pheromone );
break;
case LOCAL_PHEROMONE:
double amount = /*Strict*/Math.max( grid.field[x][y], pheromone );
if( x > 0 && y > 0 )
amount = /*Strict*/Math.max( /*Strict*/Math.max( grid.field[x-1][y-1]-subtractingRatio, minPheromone ), amount );
if( x > 0)
amount = /*Strict*/Math.max( /*Strict*/Math.max( grid.field[x-1][y]-subtractingRatio, minPheromone ), amount );
if( x > 0 && y < grid.field[x].length-1 )
amount = /*Strict*/Math.max( /*Strict*/Math.max( grid.field[x-1][y+1]-subtractingRatio, minPheromone ), amount );
if( y > 0 )
amount = /*Strict*/Math.max( /*Strict*/Math.max( grid.field[x][y-1]-subtractingRatio, minPheromone ), amount );
if( y < grid.field.length-1 )
amount = /*Strict*/Math.max( /*Strict*/Math.max( grid.field[x][y+1]-subtractingRatio, minPheromone ), amount );
if( x < grid.field.length-1 && y > 0 )
amount = /*Strict*/Math.max( /*Strict*/Math.max( grid.field[x+1][y-1]-subtractingRatio, minPheromone ), amount );
if( x < grid.field.length-1 )
amount = /*Strict*/Math.max( /*Strict*/Math.max( grid.field[x+1][y]-subtractingRatio, minPheromone ), amount );
if( x < grid.field.length-1 && y < grid.field[x].length-1 )
amount = /*Strict*/Math.max( /*Strict*/Math.max( grid.field[x+1][y+1]-subtractingRatio, minPheromone ), amount );
grid.field[x][y] = amount;
pheromoneRatio = amount;
break;
}
}
public DecisionInfo decideGreedyAction( final SimState state, final int myx, final int myy, final int orientation )
{
final AntsForage af = (AntsForage)state;
final DecisionMaker decisionMaker = af.decisionMaker;
decisionMaker.reset();
// add all neighboring cells and move to one of them
addInformation( state, myx, myy+1, 0 );
addInformation( state, myx+1, myy+1, 1 );
addInformation( state, myx+1, myy, 2 );
addInformation( state, myx+1, myy-1, 3 );
addInformation( state, myx, myy-1, 4 );
addInformation( state, myx-1, myy-1, 5 );
addInformation( state, myx-1, myy, 6 );
addInformation( state, myx-1, myy+1, 7 );
if( hasFoodItem )
return decisionMaker.getHomeGreedyDecision( state );
else
return decisionMaker.getFoodGreedyDecision( state );
}
public void step( final SimState state )
{
final AntsForage af = (AntsForage)state;
final DecisionMaker decisionMaker = af.decisionMaker;
Int2D location = af.buggrid.getObjectLocation(this);
int myx = location.x;
int myy = location.y;
if( justCreated )
{
DecisionInfo temp = decideGreedyAction( state, myx, myy, orientation );
if( temp == null )
return;
orientation = temp.orientation;
justCreated = false;
}
// final int START=-1;
int bestx, besty, besto;
DecisionInfo movingDecision = null;
if( hasFoodItem )
movingDecision = decideGreedyAction( state, myx, myy, orientation );
else
{
decisionMaker.reset();
addInformation( state, myx, myy+1, 0 );
addInformation( state, myx+1, myy+1, 1 );
addInformation( state, myx+1, myy, 2 );
addInformation( state, myx+1, myy-1, 3 );
addInformation( state, myx, myy-1, 4 );
addInformation( state, myx-1, myy-1, 5 );
addInformation( state, myx-1, myy, 6 );
addInformation( state, myx-1, myy+1, 7 );
int max = 0;
int howMany = 1;
for( int i = 1 ; i < decisionMaker.numInfos ; i++ )
if( decisionMaker.info[max].foodPheromoneAmount ==
decisionMaker.info[i].foodPheromoneAmount )
{
howMany++;
}
else if( decisionMaker.info[max].foodPheromoneAmount <
decisionMaker.info[i].foodPheromoneAmount )
{
max = i;
howMany = 1;
}
if( howMany == 1 )
movingDecision = decideGreedyAction( state, myx, myy, orientation );
else
movingDecision = decideAction( state, myx, myy, orientation );
}
if( movingDecision == null )
{
movingDecision = decideGreedyAction( state, myx, myy, orientation );
if( movingDecision == null )
{
bestx = myx;
besty = myy;
besto = orientation;
}
else
{
bestx = movingDecision.position.x;
besty = movingDecision.position.y;
besto = movingDecision.orientation;
}
}
else
{
bestx = movingDecision.position.x;
besty = movingDecision.position.y;
besto = movingDecision.orientation;
}
if( ( bestx != myx || besty != myy ))
{
// add some pheromones
if( hasFoodItem )
{
addPheromone(af.toFoodGrid,myx,myy,(/*pheromoneToLeaveBehind*/pheromoneRatio));
}
else
{
addPheromone(af.toHomeGrid,myx,myy,(/*pheromoneToLeaveBehind*/pheromoneRatio));
}
if( bestx != myx && besty != myy )
pheromoneRatio -= subtractingRatio*1.4142;
else
pheromoneRatio -= subtractingRatio;
if( pheromoneRatio < 0 )
{
die( state );
return;
}
// adjust the position of the agent, and then deposit the "to food" and "to home" pheromones
af.buggrid.setObjectLocation(this,bestx,besty);
orientation = besto;
if( ( besty >= AntsForage.HOME_YMIN ) && ( besty <= AntsForage.HOME_YMAX ) &&
( bestx >= AntsForage.HOME_XMIN ) && ( bestx <= AntsForage.HOME_XMAX ) )
{
if( hasFoodItem )
{
af.foodCollected++;
hasFoodItem = false;
pheromoneRatio = 1.0 * maxPheromone;
if( GREEDY_REPOSITIONING )
{
// pick greediest orientation!
DecisionInfo temp = decideGreedyAction( state, myx, myy, orientation );
if( temp != null )
orientation = temp.orientation;
else
orientation = (orientation+4)%8;
}
else
orientation = (orientation+4)%8; // rotate 180
}
}
else if( ( besty >= AntsForage.FOOD_YMIN ) && ( besty <= AntsForage.FOOD_YMAX ) &&
( bestx >= AntsForage.FOOD_XMIN ) && ( bestx <= AntsForage.FOOD_XMAX ) )
{
if( !hasFoodItem )
{
hasFoodItem = true;
pheromoneRatio = 1.0 * maxPheromone;
if( GREEDY_REPOSITIONING )
{
// pick greediest orientation!
DecisionInfo temp = decideGreedyAction( state, myx, myy, orientation );
if( temp != null )
orientation = temp.orientation;
else
orientation = (orientation+4)%8;
}
else
orientation = (orientation+4)%8; // rotate 180
}
}
timeToLive--;
if( timeToLive <= 0 )
{
die( state );
return;
}
}
}
// a few tweaks by Sean
private Color noFoodColor = Color.black;
private Color foodColor = Color.red;
public final void draw(Object object, Graphics2D graphics, DrawInfo2D info)
{
if( hasFoodItem )
graphics.setColor( foodColor );
else
graphics.setColor( noFoodColor );
// this code was stolen from OvalPortrayal2D
int x = (int)(info.draw.x - info.draw.width / 2.0);
int y = (int)(info.draw.y - info.draw.height / 2.0);
int width = (int)(info.draw.width);
int height = (int)(info.draw.height);
graphics.fillOval(x,y,width, height);
}
public Stoppable toDiePointer = null;
public void die( final SimState state )
{
AntsForage antsforage = (AntsForage)state;
antsforage.numberOfAnts--;
antsforage.buggrid.remove( this );
if(toDiePointer!=null) toDiePointer.stop();
}
}