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);
}
}
}