package sim.app.geo.haiti; import java.util.ArrayList; import sim.app.geo.haiti.HaitiFood.Node; import sim.engine.SimState; import sim.engine.Steppable; import sim.engine.Stoppable; import sim.field.grid.IntGrid2D; import sim.util.Bag; /** * AGENT * <p/> * This class holds the attributes and behavior of the individuals who live in * the environment. They are motivated by their energy levels, seeking to * maximize their energy over the course of the simulation. * <p/> * Agents can choose to move toward a food distribution center (based on their * knowledge of available centers) or to remain at home. Agents try to choose * the cheapest source of energy, preferring a closer center to a farther one. * If the agents project that getting the food will cost as much energy as the * food itself can provide, they will not move. * <p/> * Agents expend energy to move, in quantities that vary based on the quality of * the agent's path and the activity of the crowd. E.g., a paved road "costs" * less than an unpaved road, and participating in a riot costs even more * energy. If an agent's energy level dips below 0, it dies. */ public class Agent implements Steppable { private static final long serialVersionUID = 1L; // ATTRIBUTES double energyLevel; boolean hasFood = false; // whether the agent has received food int activity = ACTION_STAY_HOME; // updated so that the observer can tell what the agent is currently doing // info about centers and whether they have food int centerInfo = 0; // an efficient way to store information about all of the centers the agent believes // are distributing food. The information is stored as bits, so that if an Agent knows about both // Center 1 and Center 3 it sets its Center Info to the number defined by (Center 1 && Center 3), // or (0001 && 0100) to get (0101) or a centerInfo of 5. This takes advantage of the convenience of // bitwise operations and speeds up the simulation. int[] powers = { 1, 2, 4, 8, 16, 32, 64, 128 }; // info about where the agent is, where it's going, and where its home is Location position = null; Location home = null; Location goal = null; // the amount of time since the agent has confirmed that its course of // action is the best available to it int timeSinceReevaluate = 0; int reevaluateInterval = 1; ArrayList<Location> path = null; // the agent's current path to its current goal // ENERGY PARAMTERS // the parameters that indicate how much energy is used every tick the agent // does one of these activities. If we want them to die faster, INCREASE THESE COSTS public static double ENERGY_TO_STAY = .1; public static double ENERGY_TO_WALK_PAVED = .5; public static double ENERGY_TO_WALK_UNPAVED = 1.; public static double ENERGY_TO_RIOT = 5.; // ACTIVITY OPTIONS public static int ACTION_STAY_HOME = 0; public static int ACTION_GO_TO_CENTER = 1; public static int ACTION_GO_HOME = 2; // OTHER public Stoppable stopper = null; // used to unschedule the agent /** * @param home - the home location * @param position - the initial position of the agent * @param destruction - the level of destruction on the agent's home tile, * which impacts its initial energy levels */ public Agent(Location home, Location position, int destruction) { this.home = home; this.position = position; energyLevel = energyInitialization(destruction); } public Agent(Location home, Location position, int destruction, double enToStay, double enWalkPaved, double enWalkUnpav, double enRiot, int interval) { this.home = home; this.position = position; energyLevel = energyInitialization(destruction); ENERGY_TO_STAY = enToStay; ENERGY_TO_WALK_PAVED = enWalkPaved; ENERGY_TO_WALK_UNPAVED = enWalkUnpav; ENERGY_TO_RIOT = enRiot; reevaluateInterval = interval; } /** * Calculates the energy level with which the agent should be initialized, * given its destruction. IF WE WANT THEM TO DIE FASTER, SET THESE INITIAL * ENERGIES LOWER * <p/> * @param destruction - the destruction level at the agent's home location * <p/> * @return the amount of energy with which the agent begins */ double energyInitialization(int destruction) { // either no data / unclassified or no damage. if (destruction == 0 || destruction == 1) { return 2000; } else if (destruction == 2) // visible damage { return 1600; } else if (destruction == 3) // moderate damage { return 1200; } else if (destruction == 4) // significant damage { return 1000; } return 0; } /** * Confirm that the Agent is pursuing the best course of action. Checks * among the possible goals the agent might have and picks the best one, * given the agent's current food-holding status, location, and energy level * <p/> * ***MUCH MORE WORK CAN BE DONE HERE in terms of agent perception, * estimation, etc. *** */ void checkGoal(HaitiFood state) { timeSinceReevaluate = 0; // we have checked just now! // // Check to see if we're on a course that shouldn't be interrupted // // we're home and fed. Don't go back out if (hasFood && position.equals(home)) { activity = ACTION_STAY_HOME; goal = null; path = null; return; } // we're going home, so keep on keepin' on: we wouldn't be going home unless we'd // gotten food or run out of energy else if (goal != null && goal.equals(home)) { activity = ACTION_GO_HOME; return; } // the Agent has procured food, and needs to go home else if (hasFood && goal != null) { // it has food and it's not gotten home yet activity = ACTION_GO_HOME; goal = home; path = null; return; } // we already have a plan and no new information else if (goal != null && centerInfo > 0) { return; } if (centerInfo == 0) { activity = ACTION_STAY_HOME; goal = null; return; } // // otherwise check for centers and go to the closest one // Location oldGoal = goal; goal = null; for (int i = 0; i < state.centersList.size(); i++) { if ((centerInfo & powers[i]) == 0) { continue; // don't know about that center yet! } Center c = state.centersList.get(i); // if not going anywhere else, try it if (goal == null) { goal = c.loc; } // otherwise if it's closer than the current goal, go to it! else if (position.distanceTo(c.loc) < position.distanceTo(goal)) { goal = c.loc; } } // // if the NEAREST POSSIBLE GOAL is not too far, go to it. Otherwise set goal // to null and GO HOME // if (goal != null && // goal.distanceTo(position) * ENERGY_TO_WALK_PAVED < energyLevel ){ (goal.distanceTo(position) + goal.distanceTo(home)) * ENERGY_TO_WALK_PAVED < energyLevel) { if (goal != oldGoal) { path = null; // force a recalculation of path } activity = ACTION_GO_TO_CENTER; } else { goal = null; activity = ACTION_STAY_HOME; // don't go out } } /** * Unschedules the agent. Called once the agent has died. */ void die(HaitiFood state) { // stop the agent from ticking stopper.stop(); // remove this agent from the population state.population.remove(this); state.peopleList.remove(this); state.deaths_total++; state.deaths_this_tick++; } @Override public void step(SimState state) { HaitiFood hf = (HaitiFood) state; // check that the agent hasn't died of low energy if (energyLevel <= 0) { die(hf); return; } // confirm that current goal is worthwhile/the easiest target if (timeSinceReevaluate > reevaluateInterval) { checkGoal(hf); } else { timeSinceReevaluate++; } // // MOVE depending on the goal and path information // // if the agent has no goal, it does not move. if (goal == null) { energyLevel -= ENERGY_TO_STAY; // update its energy level return; // not doing anything } // otherwise, the agent moves toward its goal. Depending on how it // moves, it expends more or less energy else { if (position.equals(goal)) { // we have reached our goal! No need to proceed further checkGoal(hf); // force a reconsideration of what we should be doing return; // take a breather } // make sure we have a path to the goal! if (path == null) { AStar astar = new AStar(); path = astar.astarPath(hf, (Node) hf.closestNodes.get(position.x, position.y), (Node) hf.closestNodes.get(goal.x, goal.y)); if (path != null) { path.add(goal); } } // determine the best location to immediately move *toward* Location subgoal; // It's possible that the agent isn't close to a node that can take it to the center. // In that case, the A* will return null. If this is so the agent should move toward // the goal until such a node is found. if (path == null) { subgoal = goal; } // Otherwise we have a path and should continue to move along it else { // have we reached the end of an edge? If so, move to the next edge if (path.get(0).equals(position)) { path.remove(0); } // our current subgoal is the end of the current edge if (path.size() > 0) { subgoal = path.get(0); } else { subgoal = goal; } } // calculate the next tile to move *to* Location nextTile = getNextTile(hf, subgoal); // get a list of the people currently in the new place. If the density of that space // does not prohibit it, move there Bag density = hf.population.getObjectsAtLocation(nextTile.x, nextTile.y); if (density == null || density.size() < hf.maximumDensity) { energyLevel -= tileCost(position, hf); // transition between spaces hf.population.setObjectLocation(this, nextTile.x, nextTile.y); // update my position position.x = nextTile.x; position.y = nextTile.y; } // if we can't move to the new tile and we're in a riot, keep rioting else if (hf.population.getObjectsAtLocation(position.x, position.y).size() > hf.riotDensity) { energyLevel -= ENERGY_TO_RIOT; } // otherwise just stay put else { energyLevel -= ENERGY_TO_STAY; } } if (hf.population.getObjectsAtLocation(position.x, position.y).size() > hf.riotDensity) { hf.rioting++; } } /** * Measures the cost to the agent of moving through the given tile * <p/> * @param loc - the tile * @param hf * <p/> * @return the cost of moving through the given tile */ double tileCost(Location loc, HaitiFood hf) { Bag people = hf.population.getObjectsAtLocation(loc.x, loc.y); if (people != null && people.size() > hf.riotDensity) { return ENERGY_TO_RIOT; } int roadType = ((IntGrid2D)hf.roads.getGrid()).get(loc.x, loc.y); if (roadType < hf.noRoadValue) { return ENERGY_TO_WALK_UNPAVED; } else { return ENERGY_TO_WALK_PAVED; } } /** * Given the subgoal toward which the agent is moving, determine the next * tile in the von Neumann neighborhood to which the agent should move, * weighting roads tiles as preferable to non-roads * <p/> * @param world * @param subgoal * <p/> * @return */ Location getNextTile(HaitiFood world, Location subgoal) { // move in which direction? int moveX = 0, moveY = 0; int dx = subgoal.x - position.x; int dy = subgoal.y - position.y; if (dx < 0) { moveX = -1; } else if (dx > 0) { moveX = 1; } if (dy < 0) { moveY = -1; } else if (dy > 0) { moveY = 1; } // can either move in Y direction or X direction: see which is better Location xmove = (Location) world.locations.get(position.x + moveX, position.y); Location ymove = (Location) world.locations.get(position.x, position.y + moveY); boolean xmoveToRoad = ((IntGrid2D) world.roads.getGrid()).get(xmove.x, xmove.y) > -1; boolean ymoveToRoad = ((IntGrid2D) world.roads.getGrid()).get(ymove.x, ymove.y) > -1; if (moveX == 0 && moveY == 0) { // we are ON the subgoal, so don't move at all! // both are the same result, so just return the xmove (which is identical) return xmove; } else if (moveX == 0) // this means that moving in the x direction is not a valid move: it's +0 { return ymove; } else if (moveY == 0) // this means that moving in the y direction is not a valid move: it's +0 { return xmove; } else if (xmoveToRoad == ymoveToRoad) { //equally good moves: pick randomly between them if (world.random.nextBoolean()) { return xmove; } else { return ymove; } } else if (xmoveToRoad && moveX != 0) // x is a road: pick it { return xmove; } else if (ymoveToRoad && moveY != 0)// y is a road: pick it { return ymove; } else if (moveX != 0) // move in the better direction { return xmove; } else if (moveY != 0) // yes { return ymove; } else { return ymove; // no justification } } }