Java tutorial
// Original work Copyright (c) 2015, 2017, Igor Dimitrijevic // Modified work Copyright (c) 2017-2018 OpenBW Team ////////////////////////////////////////////////////////////////////////// // // This file is part of the BWEM Library. // BWEM is free software, licensed under the MIT/X11 License. // A copy of the license is provided with the library in the LICENSE file. // Copyright (c) 2015, 2017, Igor Dimitrijevic // ////////////////////////////////////////////////////////////////////////// package bwem.map; import bwem.*; import bwem.area.Area; import bwem.area.AreaImpl; import bwem.area.typedef.AreaId; import bwem.tile.MiniTile; import bwem.tile.MiniTileImpl; import bwem.tile.Tile; import bwem.tile.TileImpl; import bwem.typedef.Altitude; import bwem.typedef.CPPath; import bwem.typedef.Pred; import bwem.unit.Mineral; import bwem.unit.Neutral; import bwem.unit.NeutralData; import bwem.unit.StaticBuilding; import bwem.util.BwemExt; import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.tuple.MutablePair; import org.openbw.bwapi4j.*; import org.openbw.bwapi4j.MapDrawer; import org.openbw.bwapi4j.type.Color; import org.openbw.bwapi4j.unit.MineralPatch; import org.openbw.bwapi4j.unit.PlayerUnit; import org.openbw.bwapi4j.unit.Unit; import org.openbw.bwapi4j.unit.VespeneGeyser; import java.util.*; import static bwem.area.typedef.AreaId.UNINITIALIZED; public abstract class MapImpl implements Map { private final MapPrinter mapPrinter; protected TerrainData terrainData = null; protected NeutralData neutralData = null; protected Altitude highestAltitude; private final MutableBoolean automaticPathUpdate = new MutableBoolean(false); private final Graph graph; protected final List<MutablePair<MutablePair<AreaId, AreaId>, WalkPosition>> rawFrontier = new ArrayList<>(); private final BWMap bwMap; private final MapDrawer mapDrawer; protected final List<MineralPatch> mineralPatches; protected final Collection<Player> players; protected final List<VespeneGeyser> vespeneGeysers; protected final Collection<Unit> units; private final NeighboringAreaChooser neighboringAreaChooser; public MapImpl(BWMap bwMap, MapDrawer mapDrawer, Collection<Player> players, List<MineralPatch> mineralPatches, List<VespeneGeyser> vespeneGeysers, Collection<Unit> units) { this.mapPrinter = new MapPrinter(); this.mapDrawer = mapDrawer; this.bwMap = bwMap; this.players = players; this.mineralPatches = mineralPatches; this.vespeneGeysers = vespeneGeysers; this.units = units; this.graph = new Graph(this); this.neighboringAreaChooser = new NeighboringAreaChooser(); } // MapImpl::~MapImpl() // { // automaticPathUpdate = false; // now there is no need to update the paths // } protected BWMap getBWMap() { return this.bwMap; } @Override public TerrainData getData() { return this.terrainData; } @Override public MapPrinter getMapPrinter() { return mapPrinter; } @Override public boolean isInitialized() { return (this.terrainData != null); } public Graph getGraph() { return graph; } @Override public List<MutablePair<MutablePair<AreaId, AreaId>, WalkPosition>> getRawFrontier() { return rawFrontier; } @Override public boolean automaticPathUpdate() { return automaticPathUpdate.booleanValue(); } @Override public void enableAutomaticPathAnalysis() { automaticPathUpdate.setTrue(); } @Override public void assignStartingLocationsToSuitableBases() { boolean atLeastOneFailed = false; for (final TilePosition startingLocation : getData().getMapData().getStartingLocations()) { boolean isAssigned = false; for (final Base base : getBases()) { if (BwemExt.queenWiseDist(base.getLocation(), startingLocation) <= BwemExt.MAX_TILES_BETWEEN_STARTING_LOCATION_AND_ITS_ASSIGNED_BASE) { ((BaseImpl) base).assignStartingLocation(startingLocation); isAssigned = true; } } if (!atLeastOneFailed && !isAssigned) { atLeastOneFailed = true; } } if (atLeastOneFailed) { throw new IllegalStateException("At least one starting location was not assigned to a base."); } } @Override public List<TilePosition> getUnassignedStartingLocations() { final List<TilePosition> remainingStartingLocations = new ArrayList<>( getData().getMapData().getStartingLocations()); for (final Base base : getBases()) { if (remainingStartingLocations.isEmpty()) { break; } else if (base.isStartingLocation() && base.getLocation().equals(remainingStartingLocations.get(0))) { remainingStartingLocations.remove(0); } } return remainingStartingLocations; } @Override public Altitude getHighestAltitude() { return highestAltitude; } @Override public List<Base> getBases() { return getGraph().getBases(); } @Override public List<ChokePoint> getChokePoints() { return getGraph().getChokePoints(); } @Override public NeutralData getNeutralData() { return this.neutralData; } @Override public void onUnitDestroyed(Unit u) { if (u instanceof MineralPatch) { onMineralDestroyed(u); } else { try { onStaticBuildingDestroyed(u); } catch (Exception ex) { //TODO: Handle this exception appropriately. /** * An exception WILL be thrown if the unit is not in * the "Map.StaticBuildings" list. * Just ignore the exception. */ } } } @Override public void onMineralDestroyed(Unit u) { for (int i = 0; i < getNeutralData().getMinerals().size(); ++i) { Mineral mineral = getNeutralData().getMinerals().get(i); if (mineral.getUnit().equals(u)) { onMineralDestroyed(mineral); mineral.simulateCPPObjectDestructor(); /* IMPORTANT! These actions are performed in the "~Neutral" dtor in BWEM 1.4.1 C++. */ getNeutralData().getMinerals().remove(i); return; } } // bwem_assert(iMineral != minerals.end()); throw new IllegalArgumentException("unit is not a Mineral"); } /** * This method could be placed in {@link #onMineralDestroyed(org.openbw.bwapi4j.unit.Unit)}. * This remains as a separate method for portability consistency. */ private void onMineralDestroyed(Mineral pMineral) { for (Area area : getGraph().getAreas()) { ((AreaImpl) area).onMineralDestroyed(pMineral); } } @Override public void onStaticBuildingDestroyed(Unit u) { for (int i = 0; i < getNeutralData().getStaticBuildings().size(); ++i) { StaticBuilding building = getNeutralData().getStaticBuildings().get(i); if (building.getUnit().equals(u)) { building.simulateCPPObjectDestructor(); /* IMPORTANT! These actions are performed in the "~Neutral" dtor in BWEM 1.4.1 C++. */ getNeutralData().getStaticBuildings().remove(i); return; } } // bwem_assert(iStaticBuilding != StaticBuildings.end()); throw new IllegalArgumentException("unit is not a StaticBuilding"); } public void onBlockingNeutralDestroyed(Neutral pBlocking) { // bwem_assert(pBlocking && pBlocking->blocking()); if (!(pBlocking != null && pBlocking.isBlocking())) { throw new IllegalArgumentException(); } for (Area pArea : pBlocking.getBlockedAreas()) for (ChokePoint cp : pArea.getChokePoints()) { ((ChokePointImpl) cp).onBlockingNeutralDestroyed(pBlocking); } if (getData().getTile(pBlocking.getTopLeft()).getNeutral() != null) { // there remains some blocking Neutrals at the same location return; } // Unblock the miniTiles of pBlocking: AreaId newId = pBlocking.getBlockedAreas().iterator().next().getId(); WalkPosition pBlockingW = pBlocking.getSize().toWalkPosition(); for (int dy = 0; dy < pBlockingW.getY(); ++dy) for (int dx = 0; dx < pBlockingW.getX(); ++dx) { MiniTile miniTile = ((TerrainDataInitializer) getData()) .getMiniTile_(pBlocking.getTopLeft().toWalkPosition().add(new WalkPosition(dx, dy))); if (miniTile.isWalkable()) { ((MiniTileImpl) miniTile).replaceBlockedAreaId(newId); } } // Unblock the Tiles of pBlocking: for (int dy = 0; dy < pBlocking.getSize().getY(); ++dy) for (int dx = 0; dx < pBlocking.getSize().getX(); ++dx) { ((TileImpl) ((TerrainDataInitializer) getData()) .getTile_(pBlocking.getTopLeft().add(new TilePosition(dx, dy)))).resetAreaId(); setAreaIdInTile(pBlocking.getTopLeft().add(new TilePosition(dx, dy))); } if (automaticPathUpdate()) { getGraph().computeChokePointDistanceMatrix(); } } @Override public List<Area> getAreas() { return getGraph().getAreas(); } // Returns an Area given its id. Range = 1..size() @Override public Area getArea(AreaId id) { return graph.getArea(id); } @Override public Area getArea(WalkPosition w) { return graph.getArea(w); } @Override public Area getArea(TilePosition t) { return graph.getArea(t); } @Override public Area getNearestArea(WalkPosition w) { return graph.getNearestArea(w); } @Override public Area getNearestArea(TilePosition t) { return graph.getNearestArea(t); } //graph.cpp:30:Area * mainArea(MapImpl * pMap, TilePosition topLeft, TilePosition size) //Note: The original C++ code appears to return the last discovered area instead of the area with the highest frequency. //TODO: Determine if we desire the last discovered area or the area with the highest frequency. @Override public Area getMainArea(final TilePosition topLeft, final TilePosition size) { // //---------------------------------------------------------------------- // // Area with the highest frequency. // //---------------------------------------------------------------------- // final java.util.Map<Area, Integer> areaFrequency = new HashMap<>(); // for (int dy = 0; dy < size.getY(); ++dy) // for (int dx = 0; dx < size.getX(); ++dx) { // final Area area = getArea(topLeft.add(new TilePosition(dx, dy))); // if (area != null) { // Integer val = areaFrequency.get(area); // if (val == null) { // val = 0; // } // areaFrequency.put(area, val + 1); // } // } // // if (!areaFrequency.isEmpty()) { // java.util.Map.Entry<Area, Integer> highestFreqEntry = null; // for (final java.util.Map.Entry<Area, Integer> currentEntry : areaFrequency.entrySet()) { // if (highestFreqEntry == null || (currentEntry.getValue() > highestFreqEntry.getValue())) { // highestFreqEntry = currentEntry; // } // } // return highestFreqEntry.getKey(); // } else { // return null; // } // //---------------------------------------------------------------------- //---------------------------------------------------------------------- // Last area. //---------------------------------------------------------------------- final List<Area> areas = new ArrayList<>(); for (int dy = 0; dy < size.getY(); ++dy) for (int dx = 0; dx < size.getX(); ++dx) { final Area area = getArea(topLeft.add(new TilePosition(dx, dy))); if (area != null && !areas.contains(area)) { areas.add(area); } } return areas.isEmpty() ? null : areas.get(areas.size() - 1); //---------------------------------------------------------------------- } @Override public CPPath getPath(Position a, Position b, MutableInt pLength) { return graph.getPath(a, b, pLength); } @Override public CPPath getPath(Position a, Position b) { return getPath(a, b, null); } public TilePosition breadthFirstSearch(TilePosition start, Pred findCond, Pred visitCond, boolean connect8) { if (findCond.isTrue(getData().getTile(start), start, this)) { return start; } final Set<TilePosition> visited = new TreeSet<>((a, b) -> { int result = Integer.compare(a.getX(), b.getX()); if (result != 0) { return result; } return Integer.compare(a.getY(), b.getY()); }); Queue<TilePosition> toVisit = new ArrayDeque<>(); toVisit.add(start); visited.add(start); TilePosition[] dir8 = { new TilePosition(-1, -1), new TilePosition(0, -1), new TilePosition(1, -1), new TilePosition(-1, 0), new TilePosition(1, 0), new TilePosition(-1, 1), new TilePosition(0, 1), new TilePosition(1, 1) }; TilePosition[] dir4 = { new TilePosition(0, -1), new TilePosition(-1, 0), new TilePosition(+1, 0), new TilePosition(0, +1) }; TilePosition[] directions = connect8 ? dir8 : dir4; while (!toVisit.isEmpty()) { TilePosition current = toVisit.remove(); for (TilePosition delta : directions) { TilePosition next = current.add(delta); if (getData().getMapData().isValid(next)) { Tile nextTile = getData().getTile(next, CheckMode.NO_CHECK); if (findCond.isTrue(nextTile, next, this)) { return next; } if (visitCond.isTrue(nextTile, next, this) && !visited.contains(next)) { toVisit.add(next); visited.add(next); } } } } //TODO: Are we supposed to return start or not? // bwem_assert(false); throw new IllegalStateException(); // return start; } public TilePosition breadthFirstSearch(TilePosition start, Pred findCond, Pred visitCond) { return breadthFirstSearch(start, findCond, visitCond, true); } public WalkPosition breadthFirstSearch(final WalkPosition start, final Pred findCond, final Pred visitCond, final boolean connect8) { if (findCond.isTrue(getData().getMiniTile(start), start, this)) { return start; } final Set<WalkPosition> visited = new TreeSet<>((a, b) -> { int result = Integer.compare(a.getX(), b.getX()); if (result != 0) { return result; } return Integer.compare(a.getY(), b.getY()); }); final Queue<WalkPosition> toVisit = new ArrayDeque<>(); toVisit.add(start); visited.add(start); final WalkPosition[] dir8 = { new WalkPosition(-1, -1), new WalkPosition(0, -1), new WalkPosition(1, -1), new WalkPosition(-1, 0), new WalkPosition(1, 0), new WalkPosition(-1, 1), new WalkPosition(0, 1), new WalkPosition(1, 1) }; final WalkPosition[] dir4 = { new WalkPosition(0, -1), new WalkPosition(-1, 0), new WalkPosition(1, 0), new WalkPosition(0, 1) }; final WalkPosition[] directions = connect8 ? dir8 : dir4; while (!toVisit.isEmpty()) { final WalkPosition current = toVisit.remove(); for (final WalkPosition delta : directions) { final WalkPosition next = current.add(delta); if (getData().getMapData().isValid(next)) { final MiniTile miniTile = getData().getMiniTile(next, CheckMode.NO_CHECK); if (findCond.isTrue(miniTile, next, this)) { return next; } if (visitCond.isTrue(miniTile, next, this) && !visited.contains(next)) { toVisit.add(next); visited.add(next); } } } } //TODO: Are we supposed to return start or not? // bwem_assert(false); throw new IllegalStateException(); // return start; } public WalkPosition breadthFirstSearch(final WalkPosition start, final Pred findCond, final Pred visitCond) { return breadthFirstSearch(start, findCond, visitCond, true); } public void drawDiagonalCrossMap(Position topLeft, Position bottomRight, Color col) { this.mapDrawer.drawLineMap(topLeft, bottomRight, col); this.mapDrawer.drawLineMap(new Position(bottomRight.getX(), topLeft.getY()), new Position(topLeft.getX(), bottomRight.getY()), col); } protected List<PlayerUnit> filterPlayerUnits(final Collection<Unit> units, final Player player) { // return this.units.stream().filter(u -> u instanceof PlayerUnit // && ((PlayerUnit)u).getPlayer().equals(player)).map(u -> (PlayerUnit)u).collect(Collectors.toList()); final List<PlayerUnit> ret = new ArrayList<>(); for (final Unit u : units) { if (u instanceof PlayerUnit) { final PlayerUnit playerUnit = (PlayerUnit) u; if (playerUnit.getPlayer().equals(player)) { ret.add(playerUnit); } } } return ret; } protected List<PlayerUnit> filterNeutralPlayerUnits(final Collection<Unit> units, final Collection<Player> players) { final List<PlayerUnit> ret = new ArrayList<>(); for (final Player player : players) { if (player.isNeutral()) { ret.addAll(filterPlayerUnits(units, player)); } } return ret; } public void setAreaIdInTile(final TilePosition t) { final Tile tile = ((TerrainDataInitializer) getData()).getTile_(t); // bwem_assert(tile.AreaId() == 0); // initialized to 0 if (!(tile.getAreaId().intValue() == 0)) { // initialized to 0 throw new IllegalStateException(); } for (int dy = 0; dy < 4; ++dy) { for (int dx = 0; dx < 4; ++dx) { final AreaId id = getData() .getMiniTile(t.toWalkPosition().add(new WalkPosition(dx, dy)), CheckMode.NO_CHECK) .getAreaId(); if (id.intValue() != 0) { if (tile.getAreaId().intValue() == 0) { ((TileImpl) tile).setAreaId(id); } else if (!tile.getAreaId().equals(id)) { ((TileImpl) tile).setAreaId(UNINITIALIZED); return; } } } } } public MutablePair<AreaId, AreaId> findNeighboringAreas(final WalkPosition p) { final MutablePair<AreaId, AreaId> result = new MutablePair<>(null, null); final WalkPosition[] deltas = { new WalkPosition(0, -1), new WalkPosition(-1, 0), new WalkPosition(+1, 0), new WalkPosition(0, +1) }; for (final WalkPosition delta : deltas) { if (getData().getMapData().isValid(p.add(delta))) { final AreaId areaId = getData().getMiniTile(p.add(delta), CheckMode.NO_CHECK).getAreaId(); if (areaId.intValue() > 0) { if (result.getLeft() == null) { result.setLeft(areaId); } else if (!result.getLeft().equals(areaId)) { if (result.getRight() == null || ((areaId.intValue() < result.getRight().intValue()))) { result.setRight(areaId); } } } } } return result; } public AreaId chooseNeighboringArea(final AreaId a, final AreaId b) { return this.neighboringAreaChooser.chooseNeighboringArea(a, b); } private static class NeighboringAreaChooser { private final BitSet areaPairFlag = new BitSet(800000); AreaId chooseNeighboringArea(final AreaId a, final AreaId b) { int aId = a.intValue(); int bId = b.intValue(); int cantor = (aId + bId) * (aId + bId + 1); if (aId > bId) { cantor += bId; } else { cantor += aId; } areaPairFlag.flip(cantor); return areaPairFlag.get(cantor) ? a : b; } } }