Skip to content

Instantly share code, notes, and snippets.

@arctangent
Created December 30, 2011 19:11
Show Gist options
  • Save arctangent/1541077 to your computer and use it in GitHub Desktop.
Save arctangent/1541077 to your computer and use it in GitHub Desktop.
Ant Simulation
// Ant Foraging Simulator
// Jacob Conrad Martin
// http://jacobconradmartin.com
import processing.video.*;
MovieMaker mm;
// *** CONFIG ***
int worldWidth = 400;
int worldHeight = 400;
int foodPiles = 100;
int foodMaxDistance = min(worldWidth, worldHeight) / 2;
int foodMinDistance = 25; // Food will be at least this far away
int foodPerPile = 5;
int antReleaseRate = 10; // How fast the ants are released from the nest
int antCount = 0;
int maxAnts = 500;
int antLife = 1000; // How many turns each ant survives
int timeGearing = 500; // How many ants to move (chosen at random) before a screen refresh
// NOTE: Setting this higher than the total number of ants effectively means that complicated
// stuff like evaporation is calculated less often, therefore faster but less accurate simulation
int pMax = 100000; // Max pheremone allowed in a location
float pEvapRate = 0.1; // Evaporation rate
float pDiffRate = 0.1; // Diffusion rate
float neighbourK = 0.1;
int neighbourN = 10;
color cNest = color (0, 0, 0);
color cAnt = color (175, 0, 0);
color cAntWithFood = color(255, 0, 0);
color cImpassable = color(255, 255, 255);
color cFood = color(0, 125, 0);
int clock;
Location[][] world = new Location[worldWidth+2][worldHeight+2];
Nest nest = new Nest();
ArrayList ants;
// Directions
Direction dirN = new Direction(0,1);
Direction dirNE = new Direction(1,1);
Direction dirE = new Direction(1,0);
Direction dirSE = new Direction(1,-1);
Direction dirS = new Direction(0,-1);
Direction dirSW = new Direction(-1,-1);
Direction dirW = new Direction(-1,0);
Direction dirNW = new Direction(-1,1);
Direction[] allDirections = {dirN, dirNE, dirE, dirSE, dirS, dirSW, dirW, dirNW};
void setup() {
clock = 0;
antCount = 0;
background(0);
size(worldWidth+2, worldHeight+2, P2D); // NB: 1px border will be drawn around edge
frameRate(30);
int moviename = (int)random(1,100000);
mm = new MovieMaker(this, width, height, "drawing"+str(moviename)+".mov", 24, MovieMaker.MOTION_JPEG_A, MovieMaker.LOSSLESS);
//mm = new MovieMaker(this, width, height, "drawing"+str(moviename)+".mov", 30, MovieMaker.ANIMATION, MovieMaker.LOW);
// init storage space for ants
ants = new ArrayList();
// Init world
for (int wx=0; wx<worldWidth+2; wx++) {
for (int wy=0; wy<worldHeight+2; wy++) {
world[wx][wy] = new Location();
if ( (wx==0) || (wx==worldWidth+1) || (wy==0) || (wy==worldHeight+1) ) {
world[wx][wy].isImpassable = true;
}
}
}
// Init food
for (int f=0; f<foodPiles; f++) {
int fx = 0;
int fy = 0;
boolean valid = false;
while (!valid) {
fx = (int)random(worldWidth);
fy = (int)random(worldHeight);
float dist = sqrt(pow(fx-nest.x,2)+pow(fy-nest.y,2));
if ( (dist<foodMaxDistance) && (dist>foodMinDistance) ) {
world[fx][fy].foodCount=foodPerPile;
valid = true;
}
}
}
}
void draw() {
clock++;
background(0); // THIS IS VERY SLOW...
nest.releaseAnts();
//drawEdges();
diffusePheremone();
evaporatePheremone();
drawPheremone();
drawAnts();
drawFood();
drawNest();
for (int i = 0; i<ants.size()*timeGearing; i++) {
int a = (int)random(ants.size());
Ant ant = (Ant)ants.get(a);
ant.move();
}
// Draw pretty numbers on the screen
/*
PFont font;
font = loadFont("Helvetica-12.vlw");
textFont(font);
text("T: "+clock, 20, 20);
text("F: "+nest.foodCollected, 80, 20);
int fitness = (int)(1000 * nest.foodCollected/clock);
text("FIT: " + fitness, 140, 20);
*/
mm.addFrame(); // Add window's pixels to movie
}
// *** OBJECTS ***
class Direction {
int x, y;
Direction(int dx, int dy) {
x = dx;
y = dy;
}
boolean equals(Direction d) {
return ( (x==d.x) && (y==d.y) );
}
}
class Location {
int foodCount = 0;
float pHomeCount = 0;
float pFoodCount = 0;
boolean isImpassable = false;
}
class Nest {
int x, y;
int foodCollected = 0;
Nest() {
x = (int)(worldWidth/2) + 1;
y = (int)(worldHeight/2) + 1;
}
void releaseAnts() {
for (int a=0; a<antReleaseRate; a++) {
if (antCount < maxAnts) {
ants.add(new Ant());
antCount++;
}
}
}
}
class Ant {
int x, y;
int life = antLife;
Direction orientation = new Direction(0,0);
boolean hasFood = false;
String guid = "";
Ant() {
x = nest.x;
y = nest.y;
hasFood = false;
orientation.x = (int)random(-2,2);
orientation.y = (int)random(-2,2);
guid = str(random(999999999));
}
boolean atNest() {
return ( (x==nest.x) && (y==nest.y) );
}
boolean atFood() {
return (world[x][y].foodCount>0);
}
void move() {
// Pick up food and drop pheremones where necessary
if (hasFood) {
if (atNest()) {
hasFood=false;
world[x][y].pHomeCount=pMax;
spin180();
findFood();
nest.foodCollected++;
// Did we collect 95% of the food?
if (nest.foodCollected > 0.95*foodPiles*foodPerPile) {
mm.addFrame();
exit();
}
} else {
world[x][y].pFoodCount += (pMax*life/antLife);
world[x][y].pFoodCount = min(world[x][y].pFoodCount, pMax);
findNest();
}
} else {
if (atFood()) {
world[x][y].foodCount--;
hasFood=true;
spin180();
world[x][y].pFoodCount=pMax;
findNest();
} else if (atNest()) {
world[x][y].pHomeCount=pMax;
findFood();
} else {
world[x][y].pHomeCount += (pMax*life/antLife);
world[x][y].pHomeCount = min(world[x][y].pHomeCount, pMax);
findFood();
}
}
// Check if the ant is dead yet
life--;
if (life==0) {
for (int a=0; a<ants.size(); a++) { // NOTE: Very slow way to remove this ant from the ants ArrayList...
Ant ant = (Ant)ants.get(a);
if (ant.guid==guid) {
if ((ant.hasFood) && (!atNest())) {
world[ant.x][ant.y].foodCount++;
}
ants.remove(a);
antCount--;
break;
}
}
}
}
void findNest() {
findThing("nest");
}
void findFood() {
findThing("food");
}
Direction[] forwardDirections() {
if (orientation.equals(dirN)) {
Direction[] dirs = {dirNW, dirN, dirNE};
return dirs;
} else if (orientation.equals(dirNE)) {
Direction[] dirs = {dirN, dirNE, dirE};
return dirs;
} else if (orientation.equals(dirE)) {
Direction[] dirs = {dirNE, dirE, dirSE};
return dirs;
} else if (orientation.equals(dirSE)) {
Direction[] dirs = {dirE, dirSE, dirS};
return dirs;
} else if (orientation.equals(dirS)) {
Direction[] dirs = {dirSE, dirS, dirSW};
return dirs;
} else if (orientation.equals(dirSW)) {
Direction[] dirs = {dirS, dirSW, dirW};
return dirs;
} else if (orientation.equals(dirW)) {
Direction[] dirs = {dirSW, dirW, dirNW};
return dirs;
} else if (orientation.equals(dirNW)) {
Direction[] dirs = {dirW, dirNW, dirN};
return dirs;
} else {
return allDirections;
}
}
void spin180() {
orientation.x = -1 * orientation.x;
orientation.y = -1 * orientation.y;
}
void findThing(String thing) {
Direction[] directions = forwardDirections();
int directionsCount = directions.length;
// Weightings
float[] w = new float[directionsCount];
for (int i=0; i<directionsCount; i++) {
if (world[x+directions[i].x][y+directions[i].y].isImpassable==false) {
if (thing == "nest") {
w[i] = pow(neighbourK + world[x+directions[i].x][y+directions[i].y].pHomeCount/pMax, neighbourN);
} else if (thing == "food") {
w[i] = pow(neighbourK + world[x+directions[i].x][y+directions[i].y].pFoodCount/pMax, neighbourN);
}
}
}
// Cumulative weightings
float[] wcum = new float[directionsCount];
wcum[0] = w[0];
for (int i=1; i<directionsCount; i++) {
wcum[i] = wcum[i-1] + w[i];
}
// Calculate the direction and move there
float r = (float)random(0, wcum[directionsCount-1]);
for (int j=0; j<directionsCount; j++) {
if ( r < wcum[j]) {
int axNew = x+directions[j].x;
int ayNew = y+directions[j].y;
if (world[axNew][ayNew].isImpassable==false) {
x = axNew;
y = ayNew;
orientation.x = directions[j].x;
orientation.y = directions[j].y;
}
break;
}
// If we reach this point then the ant had no legal move
spin180();
}
}
}
// *** HELPER FUNCTIONS ***
void drawEdges() {
stroke(cImpassable);
line(0, 0, 0, worldHeight+1);
line(0, 0, worldWidth+1, 0);
line(worldWidth+1, 0, worldWidth+1, worldHeight+1);
line(0, worldHeight+1, worldHeight+1, worldWidth+1);
}
void drawAnts() {
for (int a = 0; a < ants.size(); a++) {
Ant ant = (Ant)ants.get(a);
if (ant.hasFood) {
fill(cAntWithFood);
stroke(cAntWithFood);
rect(ant.x, ant.y, 1, 1);
fill(255);
stroke(255);
} else {
fill(cAnt);
stroke(cAnt);
rect(ant.x, ant.y, 1, 1);
fill(255);
stroke(255);
}
}
}
void drawFood() {
for (int y = 0; y < worldHeight+2; y++) {
for (int x = 0; x < worldWidth+2; x++) {
if (world[x][y].foodCount > 0) {
fill(cFood);
stroke(cFood);
if (world[x][y].foodCount > 0.75 * foodPerPile) {
rect(x, y, 3, 3);
} else if (world[x][y].foodCount > 0.5 * foodPerPile) {
rect(x, y, 2, 2);
} else {
rect(x, y, 1, 1);
}
fill(255);
stroke(255);
}
}
}
}
void drawNest() {
//set(nest.x, nest.y, cNest);
fill(cNest);
stroke(cNest);
ellipse(nest.x, nest.y, 5, 5);
fill(255);
stroke(255);
}
void diffusePheremone() {
int[][] pFoodTransfer = new int[worldWidth+2][worldHeight+2];
int[][] pHomeTransfer = new int[worldWidth+2][worldHeight+2];
// Calculate the transfer matrix
for (int y = 2; y < worldHeight; y++) {
for (int x = 2; x < worldWidth; x++) {
for (int k = -1; k < 2; k++) {
for (int l = -1; l < 2; l++) {
pFoodTransfer[x+k][y+l] += (int)((world[x][y].pFoodCount - world[x+k][y+l].pFoodCount) * pDiffRate);
pHomeTransfer[x+k][y+l] += (int)((world[x][y].pHomeCount - world[x+k][y+l].pHomeCount) * pDiffRate);
}
}
}
}
// Apply the transfer matrix
for (int y = 1; y < worldHeight+1; y++) {
for (int x = 1; x < worldWidth+1; x++) {
world[x][y].pFoodCount += pFoodTransfer[x][y];
world[x][y].pHomeCount += pHomeTransfer[x][y];
}
}
}
void evaporatePheremone() {
for (int y = 0; y < worldHeight+2; y++) {
for (int x = 0; x < worldWidth+2; x++) {
world[x][y].pHomeCount = (int)(world[x][y].pHomeCount * (1-pEvapRate));
world[x][y].pFoodCount = (int)(world[x][y].pFoodCount * (1-pEvapRate));
}
}
}
void drawPheremone() {
for (int y = 0; y < worldHeight+2; y++) {
for (int x = 0; x < worldWidth+2; x++) {
if (world[x][y].pFoodCount + world[x][y].pHomeCount > 0) {
int f = (int)(255*world[x][y].pFoodCount/pMax);
int h = (int)(255*world[x][y].pHomeCount/pMax);
set(x, y, color(0, f, h));
}
}
}
}
// *** INTERACTIVITY ***
void keyPressed() {
//mm.addFrame(); // Add window's pixels to movie
mm.finish();
exit();
}
void mouseClicked() {
mm.finish();
nest.foodCollected = 0;
setup();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment