package nl.tudelft.bw4t.server.repast; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import org.apache.log4j.Logger; import nl.tudelft.bw4t.map.BlockColor; import nl.tudelft.bw4t.map.Door.Orientation; import nl.tudelft.bw4t.map.Point; import nl.tudelft.bw4t.map.Zone; import nl.tudelft.bw4t.server.environment.BW4TEnvironment; import nl.tudelft.bw4t.server.logging.BotLog; import nl.tudelft.bw4t.server.model.BW4TServerMap; import nl.tudelft.bw4t.server.model.BW4TServerMapListerner; import nl.tudelft.bw4t.server.model.blocks.Block; import nl.tudelft.bw4t.server.model.doors.Door; import nl.tudelft.bw4t.server.model.zone.Blockade; import nl.tudelft.bw4t.server.model.zone.BlocksRoom; import nl.tudelft.bw4t.server.model.zone.ChargingZone; import nl.tudelft.bw4t.server.model.zone.Corridor; import nl.tudelft.bw4t.server.model.zone.DropZone; import nl.tudelft.bw4t.server.model.zone.Room; import repast.simphony.context.Context; import repast.simphony.context.space.continuous.ContinuousSpaceFactory; import repast.simphony.context.space.continuous.ContinuousSpaceFactoryFinder; import repast.simphony.context.space.grid.GridFactory; import repast.simphony.context.space.grid.GridFactoryFinder; import repast.simphony.space.continuous.ContinuousSpace; import repast.simphony.space.continuous.SimpleCartesianAdder; import repast.simphony.space.continuous.StickyBorders; import repast.simphony.space.grid.Grid; import repast.simphony.space.grid.GridBuilderParameters; import repast.simphony.space.grid.SimpleGridAdder; import repast.simphony.space.grid.StrictBorders; /** * The MapLoader class loads the map from a given map location. */ public class MapLoader implements BW4TServerMapListerner { /** * Identifier used for the space projections, matched in context.xml */ public static final String PROJECTION_ID = "BW4T_Projection"; /** The GRID_PROJECTION_ID Constant. */ public static final String GRID_PROJECTION_ID = "BW4T_Grid_Projection"; /** * The log4j Logger which displays logs on console. */ private static final Logger LOGGER = Logger.getLogger(MapLoader.class); private boolean loaded = false; /** * Instantiates a new map loader. */ public MapLoader() { } @Override public void contextChange(BW4TServerMap map) { loaded = false; if (map.hasContext() && map.hasMap()) { loadMap(map); loaded = true; } } @Override public void mapChange(BW4TServerMap map) { if (map.hasContext() && map.hasMap()) { //Repast leegmaken en dan loadMap if (!loaded) { loadMap(map); loaded = true; } } } private static Random getRandom(BW4TServerMap smap) { Integer seed = (smap == null || smap.getMap() == null) ? null : smap.getMap().getSeed(); return (seed == null) ? new Random() : new Random(seed); } /** * Loads a file containing a map into the context. extends the sequence color list and the room color lists. * <ul> * <li>The sequence is extended with N random blocks (where N is the number of rooms in the map) * <li>These N random blocks are random placed in the rooms * <li>An additional 1.5*N random blocks are random placed in the rooms. * </ul> * * @param context * The context to load to. */ public static void loadMap(BW4TServerMap context) { if (context == null || !context.hasContext() || !context.hasMap()) { throw new IllegalArgumentException("Cannot load a map that does not have a repast Context and a BW4TMap."); } Map<String, nl.tudelft.bw4t.server.model.zone.Zone> zones = new HashMap<>(); Map<String, List<BlockColor>> roomBlocks = new HashMap<>(); createSpace(context); createGridSpace(context); List<BlockColor> sequence = new ArrayList<>(context.getMap().getSequence()); createZones(context, zones, roomBlocks, sequence); makeBlocks(context, roomBlocks, sequence); placeBlocks(context, zones, roomBlocks); context.getObjectsFromContext(DropZone.class).iterator().next().setSequence(sequence); BW4TEnvironment.getInstance().setMapFullyLoaded(); } /** * Creates the {@link ContinuousSpace} in which all objects will be placed. * * @param context * the context in which this space operates. * @param width * The width of the space * @param height * The height of the space * @return the created space */ private static ContinuousSpace<Object> createSpace(BW4TServerMap smap) { Context<Object> context = smap.getContext(); final Point area = smap.getMap().getArea(); int width = (int) area.getX() + 1; int height = (int) area.getY() + 1; ContinuousSpaceFactory spaceFactory = ContinuousSpaceFactoryFinder.createContinuousSpaceFactory(null); return spaceFactory.createContinuousSpace(PROJECTION_ID, context, new SimpleCartesianAdder<Object>(), new StickyBorders(), width, height); } /** * Creates the {@link Grid} in which all objects will be placed, in conjuction with the continuous space. The grid * space allows for querying for Von Neumann and Moore neighborhoods. * * @param context * the context in which this space operates. * @param mapWidth * The width of the space * @param mapHeight * The height of the space * @return the create grid for the given context and map dimensions */ private static Grid<Object> createGridSpace(BW4TServerMap smap) { Context<Object> context = smap.getContext(); final Point area = smap.getMap().getArea(); int width = (int) area.getX() + 1; int height = (int) area.getY() + 1; GridFactory gridFactory = GridFactoryFinder.createGridFactory(null); GridBuilderParameters<Object> params = new GridBuilderParameters<>(new StrictBorders(), new SimpleGridAdder<Object>(), true, width, height); return gridFactory.createGrid(GRID_PROJECTION_ID, context, params); } /** * Creates the zones. * * @param smap * the context * @param zones * the zones * @param roomblocks * the roomblocks * @param space * the space * @param sequence * the sequence * @param grid * the grid */ private static void createZones(BW4TServerMap smap, Map<String, nl.tudelft.bw4t.server.model.zone.Zone> zones, Map<String, List<BlockColor>> roomblocks, List<BlockColor> sequence) { createCorridors(smap, zones); createRooms(smap, zones, roomblocks, sequence); createBlockades(smap, zones); createChargingZones(smap, zones); connectAllZones(smap, zones); } /** * Creates the corridors. * * @param context * the context * @param zones * the zones * @param space * the space * @param grid * the grid */ private static void createCorridors(BW4TServerMap context, Map<String, nl.tudelft.bw4t.server.model.zone.Zone> zones) { for (Zone corridor : context.getMap().getZones(Zone.Type.CORRIDOR)) { Corridor corr = new Corridor(corridor, context); zones.put(corr.getName(), corr); } } /** * Creates the rooms. * * @param context * the context * @param zones * the zones * @param roomblocks * the roomblocks * @param sequence * the sequence */ private static void createRooms(BW4TServerMap context, Map<String, nl.tudelft.bw4t.server.model.zone.Zone> zones, Map<String, List<BlockColor>> roomblocks, List<BlockColor> sequence) { for (Zone roomzone : context.getMap().getZones(Zone.Type.ROOM)) { Room room; if (Zone.DROP_ZONE_NAME.equals(roomzone.getName())) { room = new DropZone(roomzone, sequence, context); } else { room = new BlocksRoom(roomzone, context); roomblocks.put(room.getName(), new ArrayList<>(roomzone.getBlocks())); } for (nl.tudelft.bw4t.map.Door door : roomzone.getDoors()) { createDoor(context, door, room); } zones.put(roomzone.getName(), room); } } /** * Creates a new {@link Door} in the context according to the {@link nl.tudelft.bw4t.map.Door}. * * @param smap * The context in which the room should be placed. * @param args * {@link nl.tudelft.bw4t.map.Door} object. * @param room * the room */ private static void createDoor(BW4TServerMap smap, nl.tudelft.bw4t.map.Door args, Room room) { Door door = new Door(smap); double x = args.getPosition().getX(); double y = args.getPosition().getY(); Orientation ori = args.getOrientation(); door.setPos(x, y, ori); door.connectTo(room); } /** * Creates the blockades. * * @param context * the context * @param zones * the zones */ private static void createBlockades(BW4TServerMap context, Map<String, nl.tudelft.bw4t.server.model.zone.Zone> zones) { for (Zone blockzone : context.getMap().getZones(Zone.Type.BLOCKADE)) { zones.put(blockzone.getName(), new Blockade(blockzone, context)); } } /** * Creates the charging zones. * * @param context * the context * @param zones * the zones * @param space * the space * @param grid * the grid */ private static void createChargingZones(BW4TServerMap context, Map<String, nl.tudelft.bw4t.server.model.zone.Zone> zones) { for (Zone chargezone : context.getMap().getZones(Zone.Type.CHARGINGZONE)) { ChargingZone czone = new ChargingZone(chargezone, context); zones.put(chargezone.getName(), czone); } } /** * Make blocks. * * @param roomblocks * the roomblocks * @param sequence * the sequence */ private static void makeBlocks(BW4TServerMap smap, Map<String, List<BlockColor>> roomblocks, List<BlockColor> sequence) { Random random = getRandom(smap); List<BlockColor> extraSequenceBlocks = makeRandomSequence(smap, smap.getMap().getRandomSequence()); sequence.addAll(extraSequenceBlocks); List<BlockColor> extraBlocks = new ArrayList<>(extraSequenceBlocks); extraBlocks.addAll(makeRandomSequence(smap, smap.getMap().getRandomBlocks())); addExtraBlocks(roomblocks, random, extraBlocks); } /** * make a random sequence of <size> colors. * * @param num * is required number of blocks in the sequence. * @return the list */ private static List<BlockColor> makeRandomSequence(BW4TServerMap smap, int num) { List<BlockColor> colors = BlockColor.getAvailableColors(); List<BlockColor> sequence = new ArrayList<>(num); Random random = getRandom(smap); for (int n = 0; n < num; n++) { sequence.add(colors.get(random.nextInt(colors.size()))); } return sequence; } /** * Adds the extra blocks. * * @param roomblocks * the roomblocks * @param random * the random * @param extraBlocks * the extra blocks */ private static void addExtraBlocks(Map<String, List<BlockColor>> roomblocks, Random random, List<BlockColor> extraBlocks) { List<String> rooms = new ArrayList<>(roomblocks.keySet()); for (BlockColor extraBlock : extraBlocks) { // find the blocks of a room where it can be added. List<BlockColor> blocks; do { String room = rooms.get(random.nextInt(rooms.size())); blocks = roomblocks.get(room); } while (blocks.size() >= 10); blocks.add(extraBlock); } } /** * Connect all the zones according to the map. * * @param smap * * @param zones * the map of all zones in repast. We can't yet access the BW4T context, therefore we need to pass this * explicitly.. */ private static void connectAllZones(BW4TServerMap smap, Map<String, nl.tudelft.bw4t.server.model.zone.Zone> zones) { for (Zone zone : smap.getMap().getZones()) { nl.tudelft.bw4t.server.model.zone.Zone z = zones.get(zone.getName()); for (Zone mapneigh : zone.getNeighbours()) { nl.tudelft.bw4t.server.model.zone.Zone neigh = zones.get(mapneigh.getName()); z.addNeighbour(neigh); } } } /** * Place blocks. * * @param smap * the context on which the actions should have effect * @param zones * the zones including the rooms in which the blocks should be created * @param roomblocks * the rooms which should contain blocks * @param space * the space * @param grid * the grid */ private static void placeBlocks(BW4TServerMap smap, Map<String, nl.tudelft.bw4t.server.model.zone.Zone> zones, Map<String, List<BlockColor>> roomblocks) { for (String room : roomblocks.keySet()) { createBlocksForRoom((Room) zones.get(room), smap, roomblocks.get(room)); } } /** * Add given colors to the room. * * @param room * the room * @param context * the context * @param space * the space * @param grid * the grid * @param args * is a list of colors to be added. */ private static void createBlocksForRoom(Room room, BW4TServerMap context, List<BlockColor> args) { LOGGER.log(BotLog.BOTLOG, String.format("room %s contains blocks: %s", room.getName(), toString(args))); Rectangle2D roomBox = room.getBoundingBox(); List<Rectangle2D> newblocks = new ArrayList<>(args.size()); Random random = getRandom(context); for (BlockColor color : args) { Rectangle2D newpos = findFreePlace(roomBox, newblocks, random); Block block = new Block(color, context); block.moveTo(newpos.getCenterX(), newpos.getCenterY()); newblocks.add(newpos); } } /** * find an unoccupied position for a new block in the given room, where the given list of blocks are already in that * room. Basically this algorithm picks random points till a free position is found. * * @param room * the room * @param blocks * the blocks * @return the rectangle2 d */ private static Rectangle2D findFreePlace(Rectangle2D room, List<Rectangle2D> blocks, Random random) { Rectangle2D block = null; // max number of retries int retryCounter = 100; boolean blockPlacedOK = false; while (!blockPlacedOK) { double x = room.getMinX() + room.getWidth() * random.nextDouble(); double y = room.getMinY() + room.getHeight() * random.nextDouble(); block = new Rectangle2D.Double(x, y, Block.SIZE, Block.SIZE); blockPlacedOK = room.contains(block); for (Rectangle2D bl : blocks) { blockPlacedOK = checkPlacement(blockPlacedOK, x, y, bl); } if (retryCounter-- == 0 && !blockPlacedOK) { throw new IllegalStateException("room is too small to fit more blocks"); } } return block; } /** * @param blockPlacedOK * @param x * the x-coordinate * @param y * the y-coordinate * @param bl * the block * @return check if the block is placed correctly */ private static boolean checkPlacement(boolean blockPlacedOK, double x, double y, Rectangle2D bl) { boolean noXoverlap = Math.abs(bl.getCenterX() - x) >= 2; boolean noYoverlap = Math.abs(bl.getCenterY() - y) >= 2; boolean noOverlap = noXoverlap || noYoverlap; blockPlacedOK = blockPlacedOK && noOverlap; return blockPlacedOK; } public static String toString(Collection<BlockColor> colors) { StringBuilder builder = new StringBuilder(); for (BlockColor c : colors) { builder.append(" "); builder.append(c.getLetter()); } return builder.toString(); } }