Courses/CS 461/Winter 2006/Jeff Bailey/Homework 2
From CSWiki
Contents |
[edit] Summary
I attempted an implementation of the Wolf Sheep Predation model using both MASON and Repast. I first implemented some of the tutorials supplied with each library and found Repast to be far more intuitive (not to mention significantly better documented). While I find the separation of visualization from model in MASON to be a neat idea, It was a bit too cumbersome. Repast, on the other hand, requires that agents be able to draw themselves (implements Drawable).
Most of my struggles were the result of trying to design the agents properly. Agents should be responsible for maintaining and updating their own state without regard for the state of the model. In this example, Grass should know how to grow, Sheep and Wolves how to move, eat, and reproduce.
This presented a problem with how to handle birth and death (due to eating and running out of energy). These two states modify the model and, thus, are difficult to contain within individual objects.
The solution is to use agent factories and a singleton (or, preferably, a thread local) list of active agents. Hooks could then be added into each factory that would allow it to notify the global model of changes. Agents, then, would be responsible for their own state and would notify the factory when a birth or death has occured. The factory would then take the appropriate action and notify the model of the changes.
I wish I had a bit more time to implement it this way but I am out of time and have to post what I have.
So here it is.
[edit] Source
Note: Comments have been removed and formatting adjusted for sake of brevity. The code should be pretty self explanitory.
[edit] PredationModel
A poorly implemented model. Too much agent based logic is implemented here. Ideally I would have implemented a step() function for each agent. I broke this up for sake of clarity.
package com.stannum.repast.predation;
import java.util.*;
import org.apache.commons.logging.*;
import uchicago.src.sim.engine.*;
import uchicago.src.sim.gui.*;
import uchicago.src.sim.space.*;
import uchicago.src.sim.analysis.OpenSequenceGraph;
import uchicago.src.sim.analysis.Sequence;
public class PredationModel extends SimpleModel {
private static final Log logger = LogFactory.getLog(PredationModel.class);
// static attributes
private static final String NAME = "Wolf-Sheep Predation";
private static final int SIZE = 50;
// attributes
private DisplaySurface surface;
private Multi2DTorus space;
private MultiObject2DDisplay display;
private OpenSequenceGraph plot;
// grass params
private int regrowth = 30;
// sheep params
private int sheepCount = 500;
private int sheepEnergy = 50;
private int sheepReproduce = 10;
// wolf params
private int wolfCount = 25;
private int wolfEnergy = 50;
private int wolfReproduce = 5;
public PredationModel() {
super();
name = NAME;
params = new String[] {
"regrowth",
"sheepCount",
"sheepEnergy",
"sheepReproduce",
"wolfCount",
"wolfEnergy",
"wolfReproduce",
};
}
public int getRegrowth() { return regrowth; }
public void setRegrowth(int regrowth) { this.regrowth = regrowth; }
public int getSheepCount() { return sheepCount; }
public void setSheepCount(int sheepCount) { this.sheepCount = sheepCount; }
public int getSheepEnergy() { return sheepEnergy; }
public void setSheepEnergy(int sheepEnergy) { this.sheepEnergy = sheepEnergy; }
public int getSheepReproduce() { return sheepReproduce; }
public void setSheepReproduce(int sheepReproduce) { this.sheepReproduce = sheepReproduce; }
public int getWolfCount() { return wolfCount; }
public void setWolfCount(int wolfCount) { this.wolfCount = wolfCount; }
public int getWolfEnergy() { return wolfEnergy; }
public void setWolfEnergy(int wolfEnergy) { this.wolfEnergy = wolfEnergy; }
public int getWolfReproduce() { return wolfReproduce; }
public void setWolfReproduce(int wolfReproduce) { this.wolfReproduce = wolfReproduce; }
public void setup() {
super.setup();
if (surface != null) {
surface.dispose();
}
if (plot != null) {
plot.dispose();
}
surface = new DisplaySurface(this, NAME);
super.registerDisplaySurface(NAME, surface);
plot = new OpenSequenceGraph(NAME, this);
plot.setXRange(0, 1000);
plot.setYRange(0, Math.max(sheepCount, wolfCount)+1);
plot.setAxisTitles("time", "count");
plot.addSequence("sheep", new Sequence() {
public double getSValue() {
int sheep = 0;
for (int i=0; i<agentList.size(); i++) {
if (agentList.get(i) instanceof Sheep) {
sheep++;
}
}
return sheep;
}
});
plot.addSequence("wolves", new Sequence() {
public double getSValue() {
int wolves = 0;
for (int i=0; i<agentList.size(); i++) {
if (agentList.get(i) instanceof Wolf) {
wolves++;
}
}
return wolves;
}
});
}
public void buildModel() {
space = new Multi2DTorus(SIZE, SIZE, false);
display = new MultiObject2DDisplay(space);
// init grass
for (int i=0; i<SIZE; i++) {
for (int j=0; j<SIZE; j++) {
Grass g = new Grass(regrowth, getNextIntFromTo(-(regrowth), regrowth), i, j);
space.putObjectAt(g.getX(), g.getY(), g);
agentList.add(g);
}
}
// init sheeps
for (int i=0; i<sheepCount; i++) {
Sheep s = new Sheep(sheepEnergy, getNextIntFromTo(0, SIZE), getNextIntFromTo(0, SIZE));
space.putObjectAt(s.getX(), s.getY(), s);
agentList.add(s);
}
//init wolves
for (int i=0; i<wolfCount; i++) {
Wolf w = new Wolf(wolfEnergy, getNextIntFromTo(0, SIZE), getNextIntFromTo(0, SIZE));
space.putObjectAt(w.getX(), w.getY(), w);
agentList.add(w);
}
// init plot
plot.display();
surface.addDisplayable(display, NAME);
surface.display();
}
public void step() {
Animal offspring = null;
Iterator iterator = agentList.iterator();
while (iterator.hasNext()) {
AbstractAgent agent = (AbstractAgent) iterator.next();
if (agent instanceof Grass) {
Grass grass = (Grass) agent;
grass.setEnergy(grass.getEnergy() + 1);
}
if (agent instanceof Animal) {
Animal animal = (Animal) agent;
if (animal.getEnergy() <= 0) {
space.removeObjectAt(animal.getX(), animal.getY(), animal);
iterator.remove();
}
else {
move(animal, space);
eat(animal, space);
offspring = reproduce(animal, space);
}
}
}
if (offspring != null) {
space.putObjectAt(offspring.getX(), offspring.getY(), offspring);
agentList.add(offspring);
}
surface.updateDisplay();
plot.step();
plot.updateGraph();
}
private void move(Animal animal, Multi2DTorus space) {
// not enough energy to move
if (animal.getEnergy() <= 0) {
return;
}
space.removeObjectAt(animal.getX(), animal.getY(), animal);
int x = animal.getX();
int y = animal.getY();
int direction = Math.abs((new Random()).nextInt() % 8);
switch (direction) {
case 0 : x++; break; // north
case 1 : x++; y++; break; // north-east
case 2 : y++; break; // east
case 3 : x--; y++; break; // south-east
case 4 : x--; break; // south
case 5 : x--; y++; break; // south-west
case 6 : y--; break; // west
case 7 : x++; y--; break; // north-west
default: break;
}
// add it to the new location
animal.setX(x);
animal.setY(y);
space.putObjectAt(animal.getX(), animal.getY(), animal);
animal.setEnergy(animal.getEnergy() - 1);
}
private void eat(Animal animal, Multi2DTorus space) {
List objects = space.getObjectsAt(animal.getX(), animal.getY());
Iterator iterator = objects.iterator();
while (iterator.hasNext()) {
AbstractAgent agent = (AbstractAgent) iterator.next();
if (animal instanceof Sheep && agent instanceof Grass) {
if (((Grass) agent).getEnergy() > 0) {
animal.setEnergy(animal.getEnergy() + (((Grass) agent).eaten())/2);
return;
}
}
else if (animal instanceof Wolf && agent instanceof Sheep) {
if (((Sheep) agent).getEnergy() > 0) {
animal.setEnergy(animal.getEnergy() + (((Sheep) agent).eaten()/2));
return;
}
}
}
}
protected Animal reproduce(Animal animal, Multi2DTorus space) {
List objects = space.getObjectsAt(animal.getX(), animal.getY());
Iterator iterator = objects.iterator();
while (iterator.hasNext()) {
AbstractAgent agent = (AbstractAgent) iterator.next();
if (animal instanceof Sheep && agent instanceof Sheep) {
double rnd = getNextDoubleFromTo(0, 100);
if (rnd <= (sheepReproduce/2)) {
return new Sheep(sheepEnergy, animal.getX(), animal.getY());
}
}
else if (animal instanceof Wolf && agent instanceof Wolf) {
double rnd = getNextDoubleFromTo(0, 100);
if (rnd <= (wolfReproduce/2)) {
return new Wolf(wolfEnergy, animal.getX(), animal.getY());
}
}
}
// no baby born
return null;
}
}
public static void main(String[] args) {
SimInit init = new SimInit();
init.loadModel(new PredationModel(), null, false);
}
[edit] AbstractAgent
Convenient parent class for all agents.
package com.stannum.repast.predation;
import org.apache.commons.logging.*;
import uchicago.src.sim.gui.*;
public abstract class AbstractAgent implements Drawable {
private static final Log logger = LogFactory.getLog(AbstractAgent.class);
protected int x;
protected int y;
protected int energy;
public AbstractAgent(int energy, int x, int y) {
this.energy = energy;
this.x = x;
this.y = y;
}
public int getX() { return x; }
public void setX(int x) { this.x = x; }
public int getY() { return y; }
public void setY(int y) { this.y = y; }
public int getEnergy() { return energy; }
public void setEnergy(int energy) { this.energy = energy; }
}
[edit] Grass
Represents the grass. Adds a regrowth attribute representing the time before dead grass grows back. Should grass be an agent? What options are there?
package com.stannum.repast.predation;
import java.awt.*;
import org.apache.commons.logging.*;
import uchicago.src.sim.gui.*;
public class Grass extends AbstractAgent {
private static final Log logger = LogFactory.getLog(Grass.class);
private static final Color BROWN = new Color(121, 73, 1);
private static final Color GREEN_1 = new Color(191, 255, 191);
private static final Color GREEN_2 = new Color(0, 204, 0);
private static final Color GREEN_3 = new Color(0, 143, 0);
protected int regrowth;
public Grass(int regrowth, int energy, int x, int y) {
super(energy, x, y);
this.regrowth = regrowth;
}
public void draw(SimGraphics graphics) {
if (energy <= 0) {
graphics.drawFastRect(BROWN);
}
else {
switch (energy) {
case 1 : graphics.drawFastRect(GREEN_1); break;
case 2 : graphics.drawFastRect(GREEN_2); break;
default: graphics.drawFastRect(GREEN_3); break;
}
}
}
public int eaten() {
int tmp = energy;
energy = -(regrowth);
return tmp;
}
}
[edit] Animal
Animal parent class. Convenient for determining agents that .move(), .eat() and .reproduce()
package com.stannum.repast.predation;
public abstract class Animal extends AbstractAgent {
public Animal(int energy, int x, int y) {
super(energy, x, y);
}
public int eaten() {
int tmp = energy;
energy = 0;
return tmp;
}
}
[edit] Sheep
A sheep. The only unique thing here is how a sheep is drawn. For simplicity sake, a sheep is little more than a white circle.
package com.stannum.repast.predation;
import java.awt.*;
import java.util.List;
import org.apache.commons.logging.*;
import uchicago.src.sim.gui.*;
import uchicago.src.sim.space.*;
public class Sheep extends Animal {
private static final Log logger = LogFactory.getLog(Sheep.class);
public Sheep(int energy, int x, int y) {
super(energy, x, y);
}
public void draw(SimGraphics graphics) {
graphics.drawFastCircle(Color.WHITE);
}
}
[edit] Wolf
Pretty much the same as a Sheep with a different implementation of draw()
package com.stannum.repast.predation;
import java.awt.*;
import org.apache.commons.logging.*;
import uchicago.src.sim.space.*;
import uchicago.src.sim.gui.*;
public class Wolf extends Animal {
private static final Log logger = LogFactory.getLog(Wolf.class);
public Wolf(int energy, int x, int y) {
super(energy, x, y);
}
public void draw(SimGraphics graphics) {
graphics.drawFastCircle(Color.RED);
}
}

