package clear; import rescuecore2.worldmodel.ChangeSet; import rescuecore2.worldmodel.EntityID; import rescuecore2.messages.Command; import rescuecore2.messages.control.KSCommands; import rescuecore2.log.Logger; import rescuecore2.misc.geometry.GeometryTools2D; import rescuecore2.misc.geometry.Line2D; import rescuecore2.misc.geometry.Point2D; import rescuecore2.standard.components.StandardSimulator; import rescuecore2.standard.entities.Area; import rescuecore2.standard.entities.Blockade; import rescuecore2.standard.entities.PoliceForce; import rescuecore2.standard.entities.StandardEntity; import rescuecore2.standard.messages.AKClear; import java.util.Map; import java.util.HashMap; import java.util.List; import java.util.ArrayList; import java.util.Set; import java.util.HashSet; /** The area model clear simulator. This simulator processes AKClear messages. */ public class ClearSimulator extends StandardSimulator { private static final String SIMULATOR_NAME = "Area model clear simulator"; private static final String REPAIR_RATE_KEY = "clear.repair.rate"; private static final String REPAIR_DISTANCE_KEY = "clear.repair.distance"; @Override public String getName() { return SIMULATOR_NAME; } @Override protected void processCommands(KSCommands c, ChangeSet changes) { long start = System.currentTimeMillis(); int time = c.getTime(); Logger.info("Timestep " + time); Map<Blockade, Integer> partiallyCleared = new HashMap<Blockade, Integer>(); Set<EntityID> cleared = new HashSet<EntityID>(); for (Command command : c.getCommands()) { if (command instanceof AKClear) { AKClear clear = (AKClear)command; if (!isValid(clear, cleared)) { continue; } Logger.debug("Processing " + clear); EntityID blockadeID = clear.getTarget(); Blockade blockade = (Blockade)model.getEntity(blockadeID); Area area = (Area)model.getEntity(blockade.getPosition()); int cost = blockade.getRepairCost(); int rate = config.getIntValue(REPAIR_RATE_KEY); Logger.debug("Blockade repair cost: " + cost); Logger.debug("Blockade repair rate: " + rate); if (rate >= cost) { // Remove the blockade entirely List<EntityID> ids = new ArrayList<EntityID>(area.getBlockades()); ids.remove(blockadeID); area.setBlockades(ids); model.removeEntity(blockadeID); changes.addChange(area, area.getBlockadesProperty()); changes.entityDeleted(blockadeID); partiallyCleared.remove(blockade); cleared.add(blockadeID); Logger.debug("Cleared " + blockade); } else { // Update the repair cost if (!partiallyCleared.containsKey(blockade)) { partiallyCleared.put(blockade, cost); } cost -= rate; blockade.setRepairCost(cost); changes.addChange(blockade, blockade.getRepairCostProperty()); } } } // Shrink partially cleared blockades for (Map.Entry<Blockade, Integer> next : partiallyCleared.entrySet()) { Blockade b = next.getKey(); double original = next.getValue(); double current = b.getRepairCost(); // d is the new size relative to the old size double d = current / original; Logger.debug("Partially cleared " + b); Logger.debug("Original repair cost: " + original); Logger.debug("New repair cost: " + current); Logger.debug("Proportion left: " + d); int[] apexes = b.getApexes(); double cx = b.getX(); double cy = b.getY(); // Move each apex towards the centre for (int i = 0; i < apexes.length; i += 2) { double x = apexes[i]; double y = apexes[i + 1]; double dx = x - cx; double dy = y - cy; // Shift both x and y so they are now d * dx from the centre double newX = cx + (dx * d); double newY = cy + (dy * d); apexes[i] = (int)newX; apexes[i + 1] = (int)newY; } b.setApexes(apexes); changes.addChange(b, b.getApexesProperty()); } long end = System.currentTimeMillis(); Logger.info("Timestep " + time + " took " + (end - start) + " ms"); } private boolean isValid(AKClear clear, Set<EntityID> cleared) { StandardEntity agent = model.getEntity(clear.getAgentID()); StandardEntity target = model.getEntity(clear.getTarget()); if (agent == null) { Logger.info("Rejecting clear command " + clear + ": agent does not exist"); return false; } if (cleared.contains(clear.getTarget())) { Logger.info("Ignoring clear command " + clear + ": target already cleared this timestep"); return false; } if (target == null) { Logger.info("Rejecting clear command " + clear + ": target does not exist"); return false; } if (!(agent instanceof PoliceForce)) { Logger.info("Rejecting clear command " + clear + ": agent is not a police officer"); return false; } if (!(target instanceof Blockade)) { Logger.info("Rejecting clear command " + clear + ": target is not a road"); return false; } PoliceForce police = (PoliceForce)agent; StandardEntity agentPosition = police.getPosition(model); if (agentPosition == null) { Logger.info("Rejecting clear command " + clear + ": could not locate agent"); return false; } if (!police.isHPDefined() || police.getHP() <= 0) { Logger.info("Rejecting clear command " + clear + ": agent is dead"); return false; } if (police.isBuriednessDefined() && police.getBuriedness() > 0) { Logger.info("Rejecting clear command " + clear + ": agent is buried"); return false; } Blockade targetBlockade = (Blockade)target; if (!targetBlockade.isPositionDefined()) { Logger.info("Rejecting clear command " + clear + ": blockade has no position"); return false; } if (!targetBlockade.isRepairCostDefined()) { Logger.info("Rejecting clear command " + clear + ": blockade has no repair cost"); return false; } // Check location // Find the closest point on the blockade to the agent int range = config.getIntValue(REPAIR_DISTANCE_KEY); Point2D agentLocation = new Point2D(police.getX(), police.getY()); double bestDistance = Double.MAX_VALUE; for (Line2D line : GeometryTools2D.pointsToLines(GeometryTools2D.vertexArrayToPoints(targetBlockade.getApexes()), true)) { Point2D closest = GeometryTools2D.getClosestPointOnSegment(line, agentLocation); double distance = GeometryTools2D.getDistance(agentLocation, closest); if (distance < range) { return true; } if (bestDistance > distance) { bestDistance = distance; } } Logger.info("Rejecting clear command " + clear + ": agent is not adjacent to target: distance is " + bestDistance); return false; } }