package nl.tudelft.bw4t.client.controller;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import eis.exceptions.NoEnvironmentException;
import eis.exceptions.PerceiveException;
import eis.iilang.Parameter;
import nl.tudelft.bw4t.client.controller.percept.processors.BumpedProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.ColorBlindProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.ColorProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.EPartnerProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.GripperCapacityProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.HoldingBlocksProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.LocationProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.NegationProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.OccupiedProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.PerceptProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.PositionProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.RobotBatteryProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.RobotOldTargetUnreachableProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.RobotProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.RobotSizeProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.SequenceIndexProcessor;
import nl.tudelft.bw4t.client.controller.percept.processors.SequenceProcessor;
import nl.tudelft.bw4t.map.BlockColor;
import nl.tudelft.bw4t.map.NewMap;
import nl.tudelft.bw4t.map.Zone;
import nl.tudelft.bw4t.map.renderer.AbstractMapController;
import nl.tudelft.bw4t.map.renderer.MapRendererInterface;
import nl.tudelft.bw4t.map.view.ViewBlock;
import nl.tudelft.bw4t.map.view.ViewEPartner;
import nl.tudelft.bw4t.map.view.ViewEntity;
/**
* The Client Map Controller. This processes incoming percepts and pushes them
* through {@link PerceptProcessor}s that do the actual updating. If GOAL is
* connected, the percepts are retrieved only by GOAL getPercept calls. If the
* controller is running standalone, it calls getPercepts itself. see also
* {@link #run()}.
*
* The incoming percepts, human GUI clicks and map rendering threads are all
* running independently. Therefore this class has to be thread safe and not
* return straight pointers to internal structures.
*
* This class is thread safe. But this does not mean that the objects that are
* returned are thread safe. Please consult thread safety of the relevant
* objects as well.
*/
public class ClientMapController extends AbstractMapController {
/**
* The log4j logger which writes logs.
*/
private static final Logger LOGGER = Logger
.getLogger(ClientMapController.class);
/** The exception string constant. */
private static final String COULDNOTPOLL = "Could not correctly poll the percepts from the environment.";
/**
* The Client Controller used by this Client Map Controller.
*/
private final ClientController clientController;
/** The occupied rooms. */
private final Set<Zone> occupiedRooms = new HashSet<Zone>();
/** The rendered bot. */
private ViewEntity myBot = new ViewEntity(0);
/** The color sequence index. */
private int colorSequenceIndex = 0;
/** The visible blocks. */
private final Set<ViewBlock> visibleBlocks = new HashSet<>();
/** The (at one point) visible e-partners. */
private final Set<ViewEPartner> knownEPartners = new HashSet<>();
/** The visible robots. */
private final Set<ViewEntity> visibleRobots = new HashSet<>();
/** The visible robots. */
private final Map<Long, ViewEntity> knownRobots = new HashMap<>();
/**
* All the blocks. This works in a bit of hacky way. We insert
* partially-instantiated blocks in this set, waiting for other percepts to
* give more information. So if we receive a color, we know the blocks color
* but not the location. And when we receive a location, we do not yet know
* the block color. In both cases, partially instantiated ViewBlocks end up
* in the allBlocks list.
*/
private final Map<Long, ViewBlock> allBlocks = new HashMap<>();
private final Map<String, PerceptProcessor> perceptProcessors;
/** The color sequence. */
private final List<BlockColor> colorSequence = new LinkedList<>();
/**
* Instantiates a new client map controller.
*
* @param map
* the map
* @param controller
* the controller
*/
public ClientMapController(NewMap map, ClientController controller) {
super(map);
if (controller == null) {
throw new NullPointerException("controller=null");
}
if (!myBot.isInitialized()) {
throw new IllegalStateException("myBot is not initialized properly");
}
clientController = controller;
perceptProcessors = new HashMap<>(16);
perceptProcessors.put("not", new NegationProcessor());
perceptProcessors.put("robot", new RobotProcessor());
perceptProcessors.put("occupied", new OccupiedProcessor());
perceptProcessors.put("holdingblocks", new HoldingBlocksProcessor());
perceptProcessors.put("position", new PositionProcessor());
perceptProcessors.put("color", new ColorProcessor());
perceptProcessors.put("epartner", new EPartnerProcessor());
perceptProcessors.put("sequence", new SequenceProcessor());
perceptProcessors.put("sequenceIndex", new SequenceIndexProcessor());
perceptProcessors.put("location", new LocationProcessor());
perceptProcessors.put("robotSize", new RobotSizeProcessor());
perceptProcessors.put("bumped", new BumpedProcessor());
perceptProcessors.put("battery", new RobotBatteryProcessor());
perceptProcessors.put("oldTargetUnreachable",
new RobotOldTargetUnreachableProcessor());
perceptProcessors
.put("gripperCapacity", new GripperCapacityProcessor());
perceptProcessors.put("colorblind", new ColorBlindProcessor());
}
@Override
public synchronized List<BlockColor> getSequence() {
return new ArrayList<BlockColor>(colorSequence);
}
/**
* Sets the sequence to given list.
*
* @param sequence
* new sequence to use.
*/
public synchronized void setSequence(List<BlockColor> sequence) {
colorSequence.clear();
colorSequence.addAll(sequence);
}
@Override
public synchronized int getSequenceIndex() {
return colorSequenceIndex;
}
public synchronized void setSequenceIndex(int sequenceIndex) {
this.colorSequenceIndex = sequenceIndex;
}
/**
* returns (copy of) the set of occupied rooms.
*
* @return
*/
public synchronized Set<Zone> getOccupiedRooms() {
return new HashSet<Zone>(occupiedRooms);
}
/*
* (non-Javadoc)
*
* @see
* nl.tudelft.bw4t.map.renderer.MapController#isOccupied(nl.tudelft.bw4t
* .map.Zone)
*/
@Override
public synchronized boolean isOccupied(Zone room) {
if (!room.isInitialized()) {
return false;
}
return occupiedRooms.contains(room);
}
/**
* Adds an occupied room to the list of occupied rooms.
*
* @param name
* the name
*/
public synchronized void addOccupiedRoom(Zone zone) {
if (!zone.isInitialized()) {
throw new IllegalStateException("zone is not initialized:" + zone);
}
occupiedRooms.add(zone);
}
/**
* Removes the occupied room from the list of occupied rooms.
*
* @param name
* the name
*/
public synchronized void removeOccupiedRoom(Zone zone) {
if (!zone.isInitialized()) {
throw new IllegalStateException("zone is not initialized:" + zone);
}
occupiedRooms.remove(zone);
}
@Override
public synchronized Set<ViewBlock> getVisibleBlocks() {
return new HashSet<ViewBlock>(visibleBlocks);
}
@Override
public synchronized void addVisibleBlock(ViewBlock b) {
if (!b.isInitialized()) {
throw new IllegalStateException("block is not initialized:" + b);
}
visibleBlocks.add(b);
}
public synchronized void clearVisible() {
visibleBlocks.clear();
}
@Override
public synchronized Set<ViewEntity> getVisibleEntities() {
return new HashSet<ViewEntity>(visibleRobots);
}
/**
* Get all the known e-partners.
*
* @return (copy of) the set of known e-partners
*/
public synchronized Set<ViewEPartner> getEPartners() {
return new HashSet<ViewEPartner>(knownEPartners);
}
public synchronized void clearVisiblePositions() {
visibleRobots.clear();
visibleRobots.add(myBot);
}
/**
* Returns an e-partner with this id that at one point has ever been
* visible.
*
* @param id
* The id.
* @return The e-partner found. null if not found.
*/
public synchronized ViewEPartner getKnownEPartner(long id) {
// fake epartner, so that we can use equals (which is guaranteed stable
// after init).
ViewEPartner equalp = new ViewEPartner(id, new Point2D.Double(), false);
for (ViewEPartner ep : knownEPartners) {
if (ep.equals(equalp)) {
return ep;
}
}
return null;
}
/**
* Makes a robot visible. Maybe this should be changed into an attribute
* attached to a Robot? We do not even check if given entity is a robot.
*
* @param bot
* robot to be added. Actually any {@link ViewEntity}
*/
public synchronized void addVisibleRobot(ViewEntity bot) {
if (!bot.isInitialized()) {
throw new IllegalStateException("entity is not initialized: " + bot);
}
visibleRobots.add(bot);
}
/**
* Utility function.
*
* @param id
* @return
*/
public synchronized ViewEntity getKnownRobot(long id) {
return knownRobots.get(id);
}
/**
* Get existing robot with given id, or return a new one with that id.
*
* @param id
* @return
*/
public synchronized ViewEntity getCreateRobot(long id) {
ViewEntity bot = getKnownRobot(id);
if (bot != null) {
return bot;
}
bot = new ViewEntity(id);
knownRobots.put(id, bot);
return bot;
}
/**
* get our own robot. Initially this returns a FAKE {@link ViewEntity},
* because we need to store incoming percepts about it until we receive the
* real robot ID.
*
* @return ViewEntity that represents our robot. This ViewEntity may have
* the incorrect ID and settings as long as we did not receive a
* robot percept from the server.
*/
public ViewEntity getTheBot() {
return myBot;
}
/**
* get block with given ID, or create it if it does not yet exist.
*
* @param id
* block id
* @return {@link ViewBlock} that has given id.
*/
public synchronized ViewBlock getBlock(Long id) {
ViewBlock b = allBlocks.get(id);
if (b == null) {
b = new ViewBlock(id);
allBlocks.put(id, b);
}
return b;
}
/**
* @param id
* of the block to be checked
* @return true iff the block is in in the environment
*/
public synchronized boolean containsBlock(Long id) {
return allBlocks.containsKey(id);
}
/**
* Add an e-partner.
*
* @param id
* @param holderId
* @return
*/
public synchronized void addEPartner(ViewEPartner epartner) {
if (!epartner.isInitialized()) {
throw new IllegalArgumentException("epartner not initialized:"
+ epartner);
}
knownEPartners.add(epartner);
}
/******************************************************************************/
/**************** utility functions (may be not thread safe) ******************/
/******************************************************************************/
@Override
public Set<ViewEPartner> getVisibleEPartners() {
// notice, not synchronized, this is just a utility function that is not
// thread safe because it calls isVisible()
Set<ViewEPartner> visibleEPartners = new HashSet<ViewEPartner>();
for (ViewEPartner ep : getEPartners()) {
if (ep.isVisible()) {
visibleEPartners.add(ep);
}
}
return visibleEPartners;
}
/**
* Returns an e-partner with this id that is currently visible. This is just
* a utility function and may be not thread safe.
*
* @param id
* The id.
* @return The e-partner found. null if no such e-partner.
*/
public ViewEPartner getViewEPartner(long id) {
for (ViewEPartner ep : getVisibleEPartners()) {
if (ep.getId() == id) {
return ep;
}
}
return null;
}
/**
* Handle all the percepts. Utility function. May be not thread safe as we
* can not guarantee thread safety of processors (they are poking into other
* structures).
*
* @param name
* the name of the percept
* @param perceptParameters
* the percept parameters
*/
@SuppressWarnings("deprecation")
public void handlePercept(String name, List<Parameter> perceptParameters) {
StringBuilder sb = new StringBuilder("Handling percept: ");
sb.append(name);
sb.append(" => [");
for (Parameter p : perceptParameters) {
sb.append(p.toProlog());
sb.append(", ");
}
sb.append("]");
LOGGER.debug(sb.toString());
PerceptProcessor processor = perceptProcessors.get(name);
if (processor != null) {
processor.process(perceptParameters, this);
}
}
/**
* utility function. Probably not thread safe.
*
* @see nl.tudelft.bw4t.map.renderer.AbstractMapController#updateRenderer
* (nl.tudelft.bw4t.map.renderer.MapRendererInterface)
*/
@Override
protected void updateRenderer(MapRendererInterface mri) {
mri.validate();
mri.repaint();
}
/*
* (non-Javadoc) Utility function. Probably not thread safe.
*
* @see nl.tudelft.bw4t.map.renderer.AbstractMapController#run()
*/
@Override
public void run() {
/**
* If GOAL is not connected we need to fetch the percepts ourselves.
* Otherwise, GOAL will fetch them and we just reuse them.
*/
if (clientController.isHuman()
&& !clientController.getEnvironment().isConnectedToGoal()) {
try {
clientController.getEnvironment().gatherPercepts(
getTheBot().getName());
} catch (PerceiveException e) {
LOGGER.error(COULDNOTPOLL, e);
} catch (NoEnvironmentException | NullPointerException e) {
LOGGER.fatal(COULDNOTPOLL
+ " No connection could be made to the environment", e);
setRunning(false);
}
}
super.run();
}
/**
* This is called when we hear our own bot ID from the server. This needs
* special treatment because we already may have received info about this
* bot, which was stored so far in a fake {@link #myBot}. Also there may
* already exist a bot with the real ID (if there was a robotSize percept ).
*
* FIXME we do not check here if this is called twice. It really should not
* happen but some unit tests do this wrong.
*
* @param longValue
*/
public void setTheBotId(long id) {
ViewEntity fakeBot = myBot;
myBot = getCreateRobot(id);
myBot.merge(fakeBot);
}
}