package iamrescue.agent.firebrigade; import iamrescue.execution.command.IPath; import iamrescue.routing.IRoutingModule; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import org.apache.log4j.Logger; import javolution.util.FastMap; import rescuecore2.standard.entities.Building; import rescuecore2.standard.entities.FireBrigade; import rescuecore2.worldmodel.EntityID; public class FireStrategy { private List<FireBrigade> fireBrigadeAgents; private IRoutingModule pathPlanner; private static final Logger LOGGER = Logger.getLogger(FireStrategy.class); protected EntityID me; protected IAMStrategyFireBrigade strategyBrigade; public FireStrategy(List<FireBrigade> fireBrigadeAgents, IRoutingModule pathPlanner, EntityID me, IAMStrategyFireBrigade strategyBrigade){ this.fireBrigadeAgents = fireBrigadeAgents; this.pathPlanner = pathPlanner; this.me = me; this.strategyBrigade = strategyBrigade; } public Building oneBuildingAtATime(FireBrigade me, // ArrayList<ArrayList<Building>> fireSite, ArrayList<ArrayList<Building>> centreBuildings, FirePredictor fireModel){ ArrayList<ArrayList<Building>> fireSite, ArrayList<ArrayList<Building>> centreBuildings, FastFirePredictor fireModel){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("oneBuildingAtATime, fireSite is: "+fireSite.toString());} if (LOGGER.isDebugEnabled()) { LOGGER.debug("oneBuildingAtATime, centreBuildings is: "+centreBuildings.toString());} Building b = null; if(fireSite.isEmpty() || fireSite.get(0).isEmpty()){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("oneBuildingAtATime, fireSite empty, so sending centre.");} b = this.oneBuildingAtATime(me, centreBuildings, fireModel); }else{ if (LOGGER.isDebugEnabled()) { LOGGER.debug("oneBuildingAtATime, fireSite not empty, so sending fireSite.");} b = this.oneBuildingAtATime(me, fireSite, fireModel); } return b; } //fireSite is the predicted buildings private Building oneBuildingAtATime(FireBrigade me, // ArrayList<ArrayList<Building>> fireSite, FirePredictor fireModel){ ArrayList<ArrayList<Building>> fireSite, FastFirePredictor fireModel){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("oneBuildingAtATime, fireSite is: "+fireSite.toString());} ArrayList<ArrayList<Building>> fireSites = this.orderFireSites(fireSite, me, fireModel); if (LOGGER.isDebugEnabled()) { LOGGER.debug("oneBuildingAtATime, orderedFireSites is: "+fireSites.toString());} for(ArrayList<Building> buildings : fireSites){ for(Building b: buildings){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("Checking FireStrategy to see if agent "+me.getID()+" can real building "+b.getID());} if(this.canIReachTarget(b, me.getID())){ return b; } } } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Agent "+me.getID()+" could not reach any buildings.");} return null; } private String lastAllocationHash = null; private FastMap<FireBrigade, Building> oneAllocatedBuildings = null; public Building oneBuildingEach(FireBrigade me, // ArrayList<ArrayList<Building>> targetBuildings, ArrayList<ArrayList<Building>> centreBuildings, FirePredictor fireModel){ ArrayList<ArrayList<Building>> targetBuildings, ArrayList<ArrayList<Building>> centreBuildings, FastFirePredictor fireModel){ Building b = null; if(targetBuildings.isEmpty() || targetBuildings.get(0).isEmpty()){ b = this.oneBuildingEach(me, centreBuildings, fireModel); }else{ b = this.oneBuildingEach(me, targetBuildings, fireModel); } return b; } private Building oneBuildingEach(FireBrigade me, // ArrayList<ArrayList<Building>> targetBuildings, FirePredictor fireModel){ ArrayList<ArrayList<Building>> targetBuildings, FastFirePredictor fireModel){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("oneBuildingEach with firesites: "+targetBuildings.toString());} if (targetBuildings.isEmpty()){ return null; } // if the firesites have changed, reallocate if (lastAllocationHash != null){ String thisAllocationHash = targetBuildings.toString(); if (!thisAllocationHash.equals(lastAllocationHash)){ oneAllocatedBuildings = null; } lastAllocationHash = thisAllocationHash; } // always reallocate, since agents might have moved! oneAllocatedBuildings = null; if(oneAllocatedBuildings == null){ ArrayList<ArrayList<Building>> fireSites = this.orderFireSites(targetBuildings, me, fireModel); oneAllocatedBuildings = new FastMap<FireBrigade, Building>(); ArrayList<FireBrigade> unallocatedFireBrigades = new ArrayList<FireBrigade>(); for (FireBrigade fb: fireBrigadeAgents){ unallocatedFireBrigades.add(fb); } FireBrigade nextfb = unallocatedFireBrigades.remove(0); boolean avoidinf = false; int tried = 0; // how many buildings we have tried to allocate this building to. ArrayList<Building> fireSite = fireSites.remove(0); // pull the first fire site out UNALLOCLOOP: while(!unallocatedFireBrigades.isEmpty()){ for (Building b: fireSite){ if (this.canIReachTarget(b, nextfb.getID())){ oneAllocatedBuildings.put(nextfb, b); avoidinf = false; // reset the infinite loop avoider if we match if (unallocatedFireBrigades.isEmpty()){ break UNALLOCLOOP; } else { nextfb = unallocatedFireBrigades.remove(0); tried = 0; } continue UNALLOCLOOP; } else { tried++; if (tried >= fireSite.size()){ // we can try the other fire sites. for (ArrayList<Building> altFireSite: fireSites){ for (Building altB: altFireSite){ if (this.canIReachTarget(altB, nextfb.getID())){ oneAllocatedBuildings.put(nextfb, b); if (unallocatedFireBrigades.isEmpty()){ break UNALLOCLOOP; } else { nextfb = unallocatedFireBrigades.remove(0); tried = 0; avoidinf = false; // reset the infinite loop avoider if we match continue UNALLOCLOOP; // go back to the other fire site } } } } // couldn't allocate :( nextfb = unallocatedFireBrigades.remove(0); avoidinf = false; tried = 0; continue UNALLOCLOOP; // go back to the other fire site } } } if (avoidinf){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("hit infinite lopp avoider");} if (unallocatedFireBrigades.isEmpty()){ break UNALLOCLOOP; } else { nextfb = unallocatedFireBrigades.remove(0); tried = 0; avoidinf = false; } } else { avoidinf = true; // only allow one loop through for each agent, to avoid infinite loop } } } if (LOGGER.isDebugEnabled()) { LOGGER.debug("oneBuildingEach allocations are:"+oneAllocatedBuildings.toString()+", where we are: "+me.toString());} Building b = oneAllocatedBuildings.get(me); return b; } public Building teamDivide(FireBrigade me, ArrayList<ArrayList<Building>> targetBuildings, ArrayList<ArrayList<Building>> centreBuildings, List<FireBrigade> fireBrigades, // FirePredictor fireModel){ FastFirePredictor fireModel){ Building b = null; // if we assigned both teams to the same target, check if the targets are the same, and then return the previous target building. if (this.singleTarget){ String thisTarget = targetBuildings.toString() + centreBuildings.toString(); if (thisTarget.equals(this.singleTargetPrevious)){ return previousSingleTarget; } singleTargetPrevious = thisTarget; } // if the target (perimeter) buildings are empty, assign the centre of the fire site to the agents. if(targetBuildings.isEmpty() || targetBuildings.get(0).isEmpty()){ b = this.teamDivide(me, centreBuildings, fireBrigades, fireModel); }else{ b = this.teamDivide(me, targetBuildings, fireBrigades, fireModel); } return b; } private List<FireBrigade> sortFireBrigades(List<FireBrigade> brigades){ FastMap<Integer, FireBrigade> brigadeMap = new FastMap<Integer, FireBrigade>(); List<Integer> ids = new ArrayList<Integer>(); for (FireBrigade fb : brigades){ brigadeMap.put(fb.getID().getValue(), fb); ids.add(fb.getID().getValue()); } List<FireBrigade> sorted = new ArrayList<FireBrigade>(); Collections.sort(ids); for(Integer id: ids){ sorted.add(brigadeMap.get(id)); } return sorted; } private Building lastTeamDivideBuilding = null; private boolean singleTarget = false; // if the teamDivide assigns both teams to the same fireSite, this flags to true and we will check to reassign next timestep. private String singleTargetPrevious = ""; private Building previousSingleTarget = null; /** *Method divides the fireBrigade parameter into two teams and tries to put out the two highest rated *fire sites that it can reach * @param myFireBrigadeID * @param targetBuildings * */ private Building teamDivide(FireBrigade me, ArrayList<ArrayList<Building>> targetBuildings, List<FireBrigade> fireBrigades, // FirePredictor fireModel){ FastFirePredictor fireModel){ // numeric sort the fire brigades so the team assignments are always the same fireBrigades = sortFireBrigades(fireBrigades); // assign teams, odd and even ArrayList<EntityID> evenTeam = new ArrayList<EntityID>(); ArrayList<EntityID> oddTeam = new ArrayList<EntityID>(); for(int i = 0; i<fireBrigades.size(); i++){ this.strategyBrigade.stopIfInterrupted(); if(i % 2 == 0){ evenTeam.add(fireBrigades.get(i).getID()); }else{ oddTeam.add(fireBrigades.get(i).getID()); } } // rank order the fire sites according to importance ArrayList<ArrayList<Building>> fireSites = this.orderFireSites(targetBuildings, me, fireModel); // split firesites to even and odd. ArrayList<ArrayList<Building>> evenFireSites = new ArrayList<ArrayList<Building>>(); ArrayList<ArrayList<Building>> oddFireSites = new ArrayList<ArrayList<Building>>(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("teamDivide, orderedFireSites is: "+fireSites.toString());} int fireSiteCount = -1; for(ArrayList<Building> buildings : fireSites){ this.strategyBrigade.stopIfInterrupted(); fireSiteCount++; if (fireSiteCount % 2 == 0){ evenFireSites.add(buildings); } else { oddFireSites.add(buildings); } } // send both teams to the same firesite if there is only one. singleTarget = false; if (oddFireSites.size() == 0){ oddFireSites = evenFireSites; // if there's only one fire site, both go to it. singleTarget = true; } else if (evenFireSites.size() == 0){ evenFireSites = oddFireSites; singleTarget = true; } // figure out which team we are on, and use that firesite now ArrayList<ArrayList<Building>> fireSitesNow = null; ArrayList<EntityID> thisteam = null; if(evenTeam.contains(me.getID())){ fireSitesNow = evenFireSites; thisteam = evenTeam; } else { fireSitesNow = oddFireSites; thisteam = oddTeam; } // get a disposable list of fire brigades ArrayList<EntityID> unallocatedFireBrigades = new ArrayList<EntityID>(); for (EntityID fb: thisteam){ this.strategyBrigade.stopIfInterrupted(); unallocatedFireBrigades.add(fb); } boolean avoidinf = false; int tried = 0; // how many buildings we have tried to allocate this agent to. boolean startAllocating = false; FastMap<EntityID, Building> allocatedBuildings = new FastMap<EntityID, Building>(); if (fireSitesNow.size() == 0){ return null; } if (lastTeamDivideBuilding == null){ startAllocating = true; } if (LOGGER.isDebugEnabled()) { LOGGER.debug("starting allocations of agents: "+unallocatedFireBrigades);} // this loops over firesites and buildings, assigning fire brigades to buildings that they can reach. ArrayList<Building> fireSite = fireSitesNow.remove(0); // pull the first fire site out EntityID nextfb = unallocatedFireBrigades.remove(0); UNALLOCLOOP: while(true){ this.strategyBrigade.stopIfInterrupted(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("start of UNALLOCLOOP with agent "+nextfb);} if (LOGGER.isDebugEnabled()) { LOGGER.debug("unallocatedAgents is: "+unallocatedFireBrigades);} // this firesite is the ideal chosen one based on the rank for (Building b: fireSite){ this.strategyBrigade.stopIfInterrupted(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("start of firesite loop with building: "+b.getID());} if (startAllocating == false && b.getID() == lastTeamDivideBuilding.getID()){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("hit previous last allocation, so starting to allocate now.");} startAllocating = true; continue UNALLOCLOOP; } if (this.canIReachTarget(b, nextfb)){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("Agent "+nextfb+" can reach Building "+b.getID()+", allocating");} allocatedBuildings.put(nextfb, b); lastTeamDivideBuilding = b; avoidinf = false; // reset the infinite loop avoider if we match if (unallocatedFireBrigades.isEmpty()){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("unallocatedFireBrigades is empty, exiting");} break UNALLOCLOOP; } else { nextfb = unallocatedFireBrigades.remove(0); if (LOGGER.isDebugEnabled()) { LOGGER.debug("getting next agent: "+nextfb);} tried = 0; continue UNALLOCLOOP; } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Agent "+nextfb+" could not reach Building "+b.getID());} tried++; if (tried >= fireSite.size()){ // when we have tried to assign to all buildings in the main fire site, // we can try the other fire sites. for (ArrayList<Building> altFireSite: fireSitesNow){ this.strategyBrigade.stopIfInterrupted(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Trying to allocated Agent "+nextfb+" to alt Fire Site: "+altFireSite.toString());} for (Building altB: altFireSite){ this.strategyBrigade.stopIfInterrupted(); if (this.canIReachTarget(altB, nextfb)){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("Agent "+nextfb+" can reach alt Building "+altB.getID()+", allocating");} allocatedBuildings.put(nextfb, altB); lastTeamDivideBuilding = altB; if (unallocatedFireBrigades.isEmpty()){ break UNALLOCLOOP; } else { nextfb = unallocatedFireBrigades.remove(0); tried = 0; avoidinf = false; // reset the infinite loop avoider if we match continue UNALLOCLOOP; // go back to the other fire site } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Agent "+nextfb+" could not reach alt Building "+altB.getID());} } } } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Agent "+nextfb+" will not be allocated.");} // couldn't allocate :( nextfb = unallocatedFireBrigades.remove(0); avoidinf = false; tried = 0; continue UNALLOCLOOP; // go back to the other fire site } } } // this stops the loop from iterating infinitely, if it runs over the whole thing more than once, we give up assigning and move onto the next agent if (avoidinf){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("hit infinite loop avoider");} if (unallocatedFireBrigades.isEmpty()){ break UNALLOCLOOP; } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("so skipping this agent: "+nextfb);} nextfb = unallocatedFireBrigades.remove(0); tried = 0; avoidinf = false; } } else { avoidinf = true; // only allow one loop through for each agent, to avoid infinite loop } } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Allocation is: "+allocatedBuildings.toString());} this.strategyBrigade.stopIfInterrupted(); // return our assigned building back to the agent as the target previousSingleTarget = allocatedBuildings.get(me.getID()); return allocatedBuildings.get(me.getID()); } private FireStrategy fireSiteStrategy = null; private FireStrategy perimeterStrategy = null; private ArrayList<FireBrigade> fireSiteTeam = null; private ArrayList<FireBrigade> perimeterTeam = null; /** * Uses the oneBuildingEach method on two sets of target buildings, the on-fire buildings and the surrounding buildings. * @param me * @param targetBuildings * @param fireSiteBuildings * @param fireBrigades */ public Building halfAndHalf(FireBrigade me, ArrayList<ArrayList<Building>> targetBuildings, ArrayList<ArrayList<Building>> fireSiteBuildings, List<FireBrigade> fireBrigades, // FirePredictor fireModel){ FastFirePredictor fireModel){ if (targetBuildings.isEmpty() || targetBuildings.get(0).isEmpty()){ targetBuildings = fireSiteBuildings; } FastMap<Integer, HashSet<FireBrigade>> possibleSites = new FastMap<Integer, HashSet<FireBrigade>>(); int counter = 0; for(List<Building> fireSite: targetBuildings){ for(Building b :fireSite){ for(FireBrigade fireBrigade : fireBrigades){ if(this.canIReachTarget(b, fireBrigade.getID())){ if(possibleSites.containsKey(counter)){ HashSet<FireBrigade> fireBs = possibleSites.get(counter); fireBs.add(fireBrigade); possibleSites.put(counter, fireBs); }else{ HashSet<FireBrigade> fireBs = new HashSet<FireBrigade>(); fireBs.add(fireBrigade); possibleSites.put(counter, fireBs); } } } } counter++; } // select which firesite strikes a balance between being important and // being reachable by many agents. final float agentCoefficient = 0.5f; FastMap<Integer, Float> firesiteWeightedRankings = new FastMap<Integer, Float>(); // fireSite to Ranking Integer topFireSite = null; // best fire site float topFireSiteRank = Float.MIN_VALUE; for(Integer fireSite : possibleSites.keySet()){ HashSet<FireBrigade> agents = possibleSites.get(fireSite); int numAgents = agents.size(); float rankWeight = fireSiteBuildings.size() - fireSite; float weightedRanking = (float)numAgents*agentCoefficient + rankWeight; if (weightedRanking > topFireSiteRank){ topFireSite = fireSite; } firesiteWeightedRankings.put(fireSite, weightedRanking); } if (topFireSite == null){ // if there are no fires to pick from, return null return null; } // get a list of fire brigades at the top fire site. ArrayList<FireBrigade> topSiteFireBrigades = new ArrayList<FireBrigade>(); for(FireBrigade fireBrigade: possibleSites.get(topFireSite)){ topSiteFireBrigades.add(fireBrigade); } if (fireSiteStrategy == null || perimeterStrategy == null ){ // allocate teams fireSiteTeam = new ArrayList<FireBrigade>(); perimeterTeam = new ArrayList<FireBrigade>(); for(int i = 0; i<topSiteFireBrigades.size(); i++){ if(i % 2 == 0){ fireSiteTeam.add(topSiteFireBrigades.get(i)); }else{ perimeterTeam.add(topSiteFireBrigades.get(i)); } } fireSiteStrategy = new FireStrategy(fireSiteTeam, this.pathPlanner, this.me, this.strategyBrigade); perimeterStrategy = new FireStrategy(perimeterTeam, this.pathPlanner, this.me, this.strategyBrigade); } if (fireSiteTeam.contains(me)){ return fireSiteStrategy.oneBuildingEach(me, fireSiteBuildings, fireModel); } else { return perimeterStrategy.oneBuildingEach(me, targetBuildings, fireModel); } } /** * Checks if a FB agent can reach a target building * @param b * @param fireBrigadeAgents * @return */ private boolean canIReachTarget(Building b, EntityID me){ // if we're checking the agent that called this, then do a real test if (me.getValue() == this.me.getValue()){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("Seeing if we can get to building "+b.getID()+" from our location.");} IPath path = pathPlanner.findShortestPath(me, b.getID()); if (path.isValid()){ return true; } // path isn't valid, so we have to try to go to any of the destinations neighbours IPath newPath = pathPlanner.findShortestPath(me, b.getNeighbours()); if (newPath.isValid()){ return true; } return false; } // otherwise just say YES so that the other agents get assigned return true; } /** * Returns a list of FB agents that can reach the specified building given * a set of FB agents * @param b * @param fireBrigadeAgents * @return */ private List<FireBrigade> getFBAgentsThatCanReachBuilding(Building b, List<FireBrigade> fireBrigadeAgents){ List<FireBrigade> validFireBrigadeAgents = new ArrayList<FireBrigade>(); for(FireBrigade fireBrigade : fireBrigadeAgents){ EntityID locationID = fireBrigade.getPosition(); IPath path = pathPlanner.findShortestPath(locationID, b.getID()); if (path.isValid()){ validFireBrigadeAgents.add(fireBrigade); } } return validFireBrigadeAgents; } /** * This method returns an ArrayList of sorted fire sites, and the buildings * within the fire sites are also ordered by their importance * * @param targetBuildings * @param fBAgent * @return */ private ArrayList<ArrayList<Building>> orderFireSites(ArrayList<ArrayList<Building>> targetBuildings, FireBrigade fBAgent, // FirePredictor fireModel){ FastFirePredictor fireModel){ if (LOGGER.isDebugEnabled()) { LOGGER.debug("ordering the fire sites: "+targetBuildings.toString());} FastMap<Double, List<Building>> fireSitesAndImportance = new FastMap <Double, List<Building>>(); //get the total the importances of all predicted buildings on fire for(List<Building> fireSite : targetBuildings){ Double totalImp = fireModel.getTotalImportance(fireSite); fireSitesAndImportance.put(totalImp, fireSite); } //get all the importances of the fire sites so we can loop through them ArrayList<Double> sortedFireSiteImportances = new ArrayList<Double>( fireSitesAndImportance.keySet()); Collections.sort(sortedFireSiteImportances); ArrayList<ArrayList<Building>> orderedFireSites = new ArrayList<ArrayList<Building>>(); //loop through the fire sites in order of their importance for(Double fireSiteImportance: sortedFireSiteImportances){ ArrayList<Building> fSite = (ArrayList<Building>) fireSitesAndImportance.get(fireSiteImportance); //sort the buildings on their importance ArrayList<Building> orderedBuildings = (ArrayList<Building>) fireModel.getOrderOfImportantBuildingsToExtinguish(fSite); orderedFireSites.add(orderedBuildings); } //return the array sorted by the most important fires and then by the //most important fires if (LOGGER.isDebugEnabled()) { LOGGER.debug("ordered: "+orderedFireSites);} return orderedFireSites; } public boolean needToReassign() { if (this.singleTarget){ // first condition to force a reassignment is if both teams are assigned to a single fireSite. return true; } return false; } }