package rescuecore2.standard.kernel; import java.util.Collection; import java.util.Set; import java.util.HashSet; import java.util.Map; import java.util.HashMap; import java.util.Iterator; import java.util.Hashtable; import java.util.Dictionary; import kernel.Perception; import kernel.AgentProxy; import rescuecore2.worldmodel.Entity; import rescuecore2.worldmodel.EntityID; import rescuecore2.worldmodel.WorldModel; import rescuecore2.worldmodel.ChangeSet; import rescuecore2.worldmodel.properties.IntProperty; import rescuecore2.config.Config; import rescuecore2.misc.Pair; import rescuecore2.standard.entities.StandardWorldModel; import rescuecore2.standard.entities.StandardEntity; import rescuecore2.standard.entities.Road; import rescuecore2.standard.entities.Area; import rescuecore2.standard.entities.Blockade; import rescuecore2.standard.entities.Building; import rescuecore2.standard.entities.Human; import rescuecore2.standard.entities.StandardEntityURN; import rescuecore2.GUIComponent; import java.awt.GridLayout; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JLabel; import javax.swing.JSlider; import javax.swing.JCheckBox; import javax.swing.SwingConstants; import javax.swing.BorderFactory; import javax.swing.event.ChangeListener; import javax.swing.event.ChangeEvent; import handy.swing.SliderComponent; /** Legacy implementation of perception with a GUI. */ public class StandardPerception implements Perception, GUIComponent { private static final boolean DEFAULT_USE_FAR_FIRES = true; private static final int DEFAULT_HP_PRECISION = 1000; private static final int DEFAULT_DAMAGE_PRECISION = 100; private static final String VIEW_DISTANCE_KEY = "perception.standard.view-distance"; private static final String FAR_FIRE_DISTANCE_KEY = "perception.standard.far-fire-distance"; private static final String USE_FAR_FIRES_KEY = "perception.standard.use-far-fires"; private static final String HP_PRECISION_KEY = "perception.standard.hp-precision"; private static final String DAMAGE_PRECISION_KEY = "perception.standard.damage-precision"; private static final int PRECISION_STEP_SIZE = 1000; private static final int PRECISION_MAX = 10000; private static final int VIEW_DISTANCE_MAX = 1000000; private static final int VIEW_DISTANCE_MAJOR_TICK = 100000; private static final int VIEW_DISTANCE_MINOR_TICK = 10000; private static final int PRECISION_MAJOR_TICK = 1000; private static final int PRECISION_MINOR_TICK = 100; private int viewDistance; private int farFireDistance; private boolean useFarFires; private int hpPrecision; private int damagePrecision; private StandardWorldModel world; private int time; private Set<Building> unburntBuildings; private Map<Building, Integer> ignitionTimes; private Config config; // Lock object for updating via the GUI. private final Object lock = new Object(); /** Create a StandardPerception object. */ public StandardPerception() { } @Override public void initialise(Config newConfig, WorldModel<? extends Entity> model) { world = StandardWorldModel.createStandardWorldModel(model); this.config = newConfig; viewDistance = config.getIntValue(VIEW_DISTANCE_KEY); farFireDistance = config.getIntValue(FAR_FIRE_DISTANCE_KEY); useFarFires = config.getBooleanValue(USE_FAR_FIRES_KEY, DEFAULT_USE_FAR_FIRES); hpPrecision = config.getIntValue(HP_PRECISION_KEY, DEFAULT_HP_PRECISION); damagePrecision = config.getIntValue(DAMAGE_PRECISION_KEY, DEFAULT_DAMAGE_PRECISION); ignitionTimes = new HashMap<Building, Integer>(); unburntBuildings = new HashSet<Building>(); time = 0; for (StandardEntity next : world) { if (next instanceof Building) { Building b = (Building)next; if (!b.isFierynessDefined() || b.getFieryness() == 0) { unburntBuildings.add(b); } else { ignitionTimes.put(b, time); } } } } @Override public String toString() { return "Standard perception"; } @Override public JComponent getGUIComponent() { return new TunePanel(); } @Override public String getGUIComponentName() { return "Perception parameters"; } @Override public void setTime(int timestep) { // Look for buildings that caught fire last timestep for (Iterator<Building> it = unburntBuildings.iterator(); it.hasNext();) { Building next = it.next(); if (next.isFierynessDefined()) { switch (next.getFierynessEnum()) { case HEATING: case BURNING: case INFERNO: ignitionTimes.put(next, time); it.remove(); break; default: // Ignore } } } time = timestep; // Look for scripting elements in the config file checkForScript(); } @Override public ChangeSet getVisibleEntities(AgentProxy agent) { synchronized (lock) { StandardEntity agentEntity = (StandardEntity)agent.getControlledEntity(); ChangeSet result = new ChangeSet(); // Look for roads/nodes/buildings/humans within range Pair<Integer, Integer> location = agentEntity.getLocation(world); if (location != null) { int x = location.first().intValue(); int y = location.second().intValue(); Collection<StandardEntity> nearby = world.getObjectsInRange(x, y, viewDistance); // Copy entities and set property values for (StandardEntity next : nearby) { StandardEntityURN urn = next.getStandardURN(); switch (urn) { case ROAD: addRoadProperties((Road)next, result); break; case BUILDING: case REFUGE: case FIRE_STATION: case AMBULANCE_CENTRE: case POLICE_OFFICE: addBuildingProperties((Building)next, result); break; case CIVILIAN: case FIRE_BRIGADE: case AMBULANCE_TEAM: case POLICE_FORCE: // Always send all properties of the agent-controlled object if (next == agentEntity) { addSelfProperties((Human)next, result); } else { addHumanProperties((Human)next, result); } break; case BLOCKADE: addBlockadeProperties((Blockade)next, result); break; default: // Ignore other types break; } } // Now look for far fires if (useFarFires) { for (Map.Entry<Building, Integer> next : ignitionTimes.entrySet()) { Building b = next.getKey(); int ignitionTime = next.getValue(); int timeDelta = time - ignitionTime; int visibleRange = timeDelta * farFireDistance; int range = world.getDistance(agentEntity, b); if (range <= visibleRange) { addFarBuildingProperties(b, result); } } } } return result; } } private void addRoadProperties(Road road, ChangeSet result) { addAreaProperties(road, result); // Only update blockades result.addChange(road, road.getBlockadesProperty()); // Also update each blockade if (road.isBlockadesDefined()) { for (EntityID id : road.getBlockades()) { Blockade blockade = (Blockade)world.getEntity(id); addBlockadeProperties(blockade, result); } } } private void addBuildingProperties(Building building, ChangeSet result) { addAreaProperties(building, result); // Update TEMPERATURE, FIERYNESS and BROKENNESS result.addChange(building, building.getTemperatureProperty()); result.addChange(building, building.getFierynessProperty()); result.addChange(building, building.getBrokennessProperty()); } private void addAreaProperties(Area area, ChangeSet result) { } private void addFarBuildingProperties(Building building, ChangeSet result) { // Update FIERYNESS only result.addChange(building, building.getFierynessProperty()); } private void addHumanProperties(Human human, ChangeSet result) { // Update POSITION, POSITION_EXTRA, DIRECTION, STAMINA, HP, DAMAGE, BURIEDNESS result.addChange(human, human.getPositionProperty()); //result.addChange(human, human.getPositionExtraProperty()); result.addChange(human, human.getDirectionProperty()); result.addChange(human, human.getStaminaProperty()); result.addChange(human, human.getBuriednessProperty()); // Round HP and damage IntProperty hp = (IntProperty)human.getHPProperty().copy(); roundProperty(hp, hpPrecision); result.addChange(human, hp); IntProperty damage = (IntProperty)human.getDamageProperty().copy(); roundProperty(damage, damagePrecision); result.addChange(human, damage); } private void addSelfProperties(Human human, ChangeSet result) { // Update human properties and POSITION_HISTORY addHumanProperties(human, result); result.addChange(human, human.getPositionHistoryProperty()); // Un-round hp and damage result.addChange(human, human.getHPProperty()); result.addChange(human, human.getDamageProperty()); } private void addBlockadeProperties(Blockade blockade, ChangeSet result) { result.addChange(blockade, blockade.getXProperty()); result.addChange(blockade, blockade.getYProperty()); result.addChange(blockade, blockade.getPositionProperty()); result.addChange(blockade, blockade.getApexesProperty()); result.addChange(blockade, blockade.getRepairCostProperty()); } private void roundProperty(IntProperty p, int precision) { if (precision != 1 && p.isDefined()) { p.setValue(round(p.getValue(), precision)); } } private int round(int value, int precision) { int remainder = value % precision; value -= remainder; if (remainder >= precision / 2) { value += precision; } return value; } private void updateViewDistance(int value) { synchronized (lock) { viewDistance = value; } } private void updateHPPrecision(int value) { synchronized (lock) { if (value == 0) { value = 1; } hpPrecision = value; } } private void updateDamagePrecision(int value) { synchronized (lock) { if (value == 0) { value = 1; } damagePrecision = value; } } private void updateUseFarFires(boolean value) { synchronized (lock) { useFarFires = value; } } private void checkForScript() { viewDistance = config.getIntValue(VIEW_DISTANCE_KEY + ".script." + time, viewDistance); farFireDistance = config.getIntValue(FAR_FIRE_DISTANCE_KEY + ".script." + time, farFireDistance); useFarFires = config.getBooleanValue(USE_FAR_FIRES_KEY + ".script." + time, useFarFires); hpPrecision = config.getIntValue(HP_PRECISION_KEY + ".script." + time, hpPrecision); damagePrecision = config.getIntValue(DAMAGE_PRECISION_KEY + ".script." + time, damagePrecision); } private class TunePanel extends JPanel { private JSlider viewDistanceSlider; private JSlider hpPrecisionSlider; private JSlider damagePrecisionSlider; private JCheckBox farFiresBox; public TunePanel() { // CHECKSTYLE:OFF:MagicNumber super(new GridLayout(1, 4)); // CHECKSTYLE:ON:MagicNumber viewDistanceSlider = new JSlider(SwingConstants.VERTICAL, 0, VIEW_DISTANCE_MAX, viewDistance); hpPrecisionSlider = new JSlider(SwingConstants.VERTICAL, 0, PRECISION_MAX, hpPrecision); damagePrecisionSlider = new JSlider(SwingConstants.VERTICAL, 0, PRECISION_MAX, damagePrecision); farFiresBox = new JCheckBox("Use far fires?", useFarFires); SliderComponent s = new SliderComponent(viewDistanceSlider); s.setBorder(BorderFactory.createTitledBorder("View distance")); add(s); s = new SliderComponent(hpPrecisionSlider); s.setBorder(BorderFactory.createTitledBorder("HP precision")); add(s); s = new SliderComponent(damagePrecisionSlider); s.setBorder(BorderFactory.createTitledBorder("Damage precision")); add(s); add(farFiresBox); viewDistanceSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { updateViewDistance(viewDistanceSlider.getValue()); } }); hpPrecisionSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { updateHPPrecision(hpPrecisionSlider.getValue()); } }); damagePrecisionSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { updateDamagePrecision(damagePrecisionSlider.getValue()); } }); farFiresBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { updateUseFarFires(farFiresBox.isSelected()); } }); // Add tick marks to sliders viewDistanceSlider.setPaintLabels(true); viewDistanceSlider.setPaintTicks(true); viewDistanceSlider.setMajorTickSpacing(VIEW_DISTANCE_MAJOR_TICK); viewDistanceSlider.setMinorTickSpacing(VIEW_DISTANCE_MINOR_TICK); hpPrecisionSlider.setPaintLabels(true); hpPrecisionSlider.setPaintTicks(true); hpPrecisionSlider.setMajorTickSpacing(PRECISION_MAJOR_TICK); hpPrecisionSlider.setMinorTickSpacing(PRECISION_MINOR_TICK); damagePrecisionSlider.setPaintLabels(true); damagePrecisionSlider.setPaintTicks(true); damagePrecisionSlider.setMajorTickSpacing(PRECISION_MAJOR_TICK); damagePrecisionSlider.setMinorTickSpacing(PRECISION_MINOR_TICK); Dictionary<Integer, JComponent> labels = new Hashtable<Integer, JComponent>(); labels.put(1, new JLabel("Accurate")); for (int i = PRECISION_STEP_SIZE; i <= PRECISION_MAX; i += PRECISION_STEP_SIZE) { labels.put(i, new JLabel(String.valueOf(i))); } hpPrecisionSlider.setLabelTable(labels); damagePrecisionSlider.setLabelTable(labels); } } }