bwem.map.MapInitializerImpl.java Source code

Java tutorial

Introduction

Here is the source code for bwem.map.MapInitializerImpl.java

Source

// 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.CheckMode;
import bwem.area.TempAreaInfo;
import bwem.area.typedef.AreaId;
import bwem.tile.*;
import bwem.typedef.Altitude;
import bwem.unit.*;
import bwem.util.*;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openbw.bwapi4j.*;
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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class MapInitializerImpl extends MapImpl implements MapInitializer {

    private static final Logger logger = LogManager.getLogger();

    public MapInitializerImpl(BWMap bwMap, MapDrawer mapDrawer, Collection<Player> players,
            List<MineralPatch> mineralPatches, List<VespeneGeyser> vespeneGeysers, Collection<Unit> units) {
        super(bwMap, mapDrawer, players, mineralPatches, vespeneGeysers, units);
    }

    private native void initialize_native();

    @Override
    public void initialize(final boolean enableTimer) {
        //        initialize_native();

        final Timer overallTimer = new Timer();
        final Timer timer = new Timer();

        initializeTerrainData(getBWMap().mapWidth(), getBWMap().mapHeight(), getBWMap().getStartPositions());
        //    ///   bw << "Map::initialize-resize: " << timer.elapsedMilliseconds() << " ms" << endl; timer.reset();
        if (enableTimer) {
            logger.info("Map::initialize-resize: " + timer.elapsedMilliseconds() + " ms");
            timer.reset();
        }

        // Computes walkability, buildability and groundHeight and doodad information, using BWAPI corresponding functions
        ((TerrainDataInitializer) getData()).markUnwalkableMiniTiles(getBWMap());
        ((TerrainDataInitializer) getData()).markBuildableTilesAndGroundHeight(getBWMap());
        //    ///   bw << "Map::LoadData: " << timer.elapsedMilliseconds() << " ms" << endl; timer.reset();
        if (enableTimer) {
            logger.info("Map::LoadData: " + timer.elapsedMilliseconds() + " ms");
            timer.reset();
        }
        //
        ((TerrainDataInitializer) getData()).decideSeasOrLakes(BwemExt.lake_max_miniTiles,
                BwemExt.lake_max_width_in_miniTiles);
        //    ///   bw << "Map::DecideSeasOrLakes: " << timer.elapsedMilliseconds() << " ms" << endl; timer.reset();
        if (enableTimer) {
            logger.info("Map::DecideSeasOrLakes: " + timer.elapsedMilliseconds() + " ms");
            timer.reset();
        }

        initializeNeutralData(super.mineralPatches, super.vespeneGeysers,
                filterNeutralPlayerUnits(super.units, super.players));
        //    ///   bw << "Map::InitializeNeutrals: " << timer.elapsedMilliseconds() << " ms" << endl; timer.reset();
        if (enableTimer) {
            logger.info("Map::InitializeNeutrals: " + timer.elapsedMilliseconds() + " ms");
            timer.reset();
        }

        computeAltitude(getData());
        //    ///   bw << "Map::ComputeAltitude: " << timer.elapsedMilliseconds() << " ms" << endl; timer.reset();
        if (enableTimer) {
            logger.info("Map::ComputeAltitude: " + timer.elapsedMilliseconds() + " ms");
            timer.reset();
        }

        processBlockingNeutrals(
                getCandidates(getNeutralData().getStaticBuildings(), getNeutralData().getMinerals()));
        //    ///   bw << "Map::processBlockingNeutrals: " << timer.elapsedMilliseconds() << " ms" << endl; timer.reset();
        if (enableTimer) {
            logger.info("Map::processBlockingNeutrals: " + timer.elapsedMilliseconds() + " ms");
            timer.reset();
        }

        computeAreas(computeTempAreas(getSortedMiniTilesByDescendingAltitude()), BwemExt.area_min_miniTiles);
        //    ///   bw << "Map::computeAreas: " << timer.elapsedMilliseconds() << " ms" << endl; timer.reset();
        if (enableTimer) {
            logger.info("Map::computeAreas: " + timer.elapsedMilliseconds() + " ms");
            timer.reset();
        }

        getGraph().createChokePoints(getNeutralData().getStaticBuildings(), getNeutralData().getMinerals(),
                getRawFrontier());
        //    ///   bw << "Graph::createChokePoints: " << timer.elapsedMilliseconds() << " ms" << endl; timer.reset();
        if (enableTimer) {
            logger.info("Map::createChokePoints: " + timer.elapsedMilliseconds() + " ms");
            timer.reset();
        }

        getGraph().computeChokePointDistanceMatrix();
        //    ///   bw << "Graph::computeChokePointDistanceMatrix: " << timer.elapsedMilliseconds() << " ms" << endl; timer.reset();
        if (enableTimer) {
            logger.info("Map::computeChokePointDistanceMatrix: " + timer.elapsedMilliseconds() + " ms");
            timer.reset();
        }

        getGraph().collectInformation();
        //    ///   bw << "Graph::collectInformation: " << timer.elapsedMilliseconds() << " ms" << endl; timer.reset();
        if (enableTimer) {
            logger.info("Map::collectInformation: " + timer.elapsedMilliseconds() + " ms");
            timer.reset();
        }

        getGraph().createBases(getData());
        //    ///   bw << "Graph::createBases: " << timer.elapsedMilliseconds() << " ms" << endl; timer.reset();
        if (enableTimer) {
            logger.info("Map::createBases: " + timer.elapsedMilliseconds() + " ms");
            timer.reset();
        }

        //    ///   bw << "Map::initialize: " << overallTimer.elapsedMilliseconds() << " ms" << endl;
        if (enableTimer) {
            logger.info("Map::initialize Total: " + overallTimer.elapsedMilliseconds() + " ms");
            timer.reset();
        }
    }

    @Override
    public void initializeTerrainData(final int mapTileWidth, final int mapTileHeight,
            final List<TilePosition> startingLocations) {
        final MapData mapData = new MapDataImpl(mapTileWidth, mapTileHeight, startingLocations);
        final TileData tileData = new TileDataImpl(mapData.getTileSize().getX() * mapData.getTileSize().getY(),
                mapData.getWalkSize().getX() * mapData.getWalkSize().getY());
        super.terrainData = new TerrainDataInitializerImpl(mapData, tileData);
    }

    ////////////////////////////////////////////////////////////////////////
    // MapImpl::InitializeNeutrals
    ////////////////////////////////////////////////////////////////////////

    @Override
    public void initializeNeutralData(final List<MineralPatch> mineralPatches,
            final List<VespeneGeyser> vespeneGeysers, final List<PlayerUnit> neutralUnits) {
        super.neutralData = new NeutralDataImpl(this, mineralPatches, vespeneGeysers, neutralUnits);
    }

    ////////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////////
    // MapImpl::ComputeAltitude
    ////////////////////////////////////////////////////////////////////////

    // Assigns MiniTile::m_altitude foar each miniTile having AltitudeMissing()
    // Cf. MiniTile::Altitude() for meaning of altitude_t.
    // Altitudes are computed using the straightforward Dijkstra's algorithm : the lower ones are computed first, starting from the seaside-miniTiles neighbors.
    // The point here is to precompute all possible altitudes for all possible tiles, and sort them.
    @Override
    public void computeAltitude(final TerrainData terrainData) {
        final int altitudeScale = 8; // 8 provides a pixel definition for altitude_t, since altitudes are computed from miniTiles which are 8x8 pixels

        final List<MutablePair<WalkPosition, Altitude>> deltasByAscendingAltitude = getSortedDeltasByAscendingAltitude(
                terrainData.getMapData().getWalkSize().getX(), terrainData.getMapData().getWalkSize().getY(),
                altitudeScale);

        final List<MutablePair<WalkPosition, Altitude>> activeSeaSides = getActiveSeaSideList(terrainData);

        setHighestAltitude(setAltitudesAndGetUpdatedHighestAltitude(getHighestAltitude(), terrainData,
                deltasByAscendingAltitude, activeSeaSides, altitudeScale));
    }

    /**
     * 1) Fill in and sort DeltasByAscendingAltitude
     */
    @Override
    public List<MutablePair<WalkPosition, Altitude>> getSortedDeltasByAscendingAltitude(final int mapWalkTileWidth,
            final int mapWalkTileHeight, int altitudeScale) {
        final int range = Math.max(mapWalkTileWidth, mapWalkTileHeight) / 2 + 3; // should suffice for maps with no Sea.

        final List<MutablePair<WalkPosition, Altitude>> deltasByAscendingAltitude = new ArrayList<>();

        for (int dy = 0; dy <= range; ++dy) {
            for (int dx = dy; dx <= range; ++dx) { // Only consider 1/8 of possible deltas. Other ones obtained by symmetry.
                if (dx != 0 || dy != 0) {
                    deltasByAscendingAltitude.add(new MutablePair<>(new WalkPosition(dx, dy),
                            new Altitude((int) Math.round(Utils.norm(dx, dy) * altitudeScale))));
                }
            }
        }

        deltasByAscendingAltitude.sort(new PairGenericAltitudeComparator<>());

        return deltasByAscendingAltitude;
    }

    /**
     * 2) Fill in ActiveSeaSideList, which basically contains all the seaside miniTiles (from which altitudes are to be computed)
     *    It also includes extra border-miniTiles which are considered as seaside miniTiles too.
     */
    @Override
    public List<MutablePair<WalkPosition, Altitude>> getActiveSeaSideList(final TerrainData terrainData) {
        final List<MutablePair<WalkPosition, Altitude>> activeSeaSideList = new ArrayList<>();

        for (int y = -1; y <= terrainData.getMapData().getWalkSize().getY(); ++y) {
            for (int x = -1; x <= terrainData.getMapData().getWalkSize().getX(); ++x) {
                final WalkPosition walkPosition = new WalkPosition(x, y);
                if (!terrainData.getMapData().isValid(walkPosition)
                        || terrainData.isSeaWithNonSeaNeighbors(walkPosition)) {
                    activeSeaSideList.add(new MutablePair<>(walkPosition, Altitude.ZERO));
                }
            }
        }

        return activeSeaSideList;
    }

    //----------------------------------------------------------------------
    // 3) Dijkstra's algorithm to set altitude for mini tiles.
    //----------------------------------------------------------------------

    @Override
    public Altitude setAltitudesAndGetUpdatedHighestAltitude(final Altitude currentHighestAltitude,
            final TerrainData terrainData,
            final List<MutablePair<WalkPosition, Altitude>> deltasByAscendingAltitude,
            final List<MutablePair<WalkPosition, Altitude>> activeSeaSideList, final int altitudeScale) {
        Altitude updatedHighestAltitude = currentHighestAltitude;

        for (final MutablePair<WalkPosition, Altitude> deltaAltitude : deltasByAscendingAltitude) {
            final WalkPosition d = deltaAltitude.getLeft();
            final Altitude altitude = deltaAltitude.getRight();

            for (int i = 0; i < activeSeaSideList.size(); ++i) {
                final MutablePair<WalkPosition, Altitude> current = activeSeaSideList.get(i);
                if (altitude.intValue() - current.getRight().intValue() >= 2 * altitudeScale) {
                    // optimization : once a seaside miniTile verifies this condition,
                    // we can throw it away as it will not generate min altitudes anymore
                    Utils.fastErase(activeSeaSideList, i--);
                } else {
                    final WalkPosition[] deltas = { new WalkPosition(d.getX(), d.getY()),
                            new WalkPosition(-d.getX(), d.getY()), new WalkPosition(d.getX(), -d.getY()),
                            new WalkPosition(-d.getX(), -d.getY()), new WalkPosition(d.getY(), d.getX()),
                            new WalkPosition(-d.getY(), d.getX()), new WalkPosition(d.getY(), -d.getX()),
                            new WalkPosition(-d.getY(), -d.getX()) };
                    for (final WalkPosition delta : deltas) {
                        final WalkPosition w = current.getLeft().add(delta);
                        if (terrainData.getMapData().isValid(w)) {
                            final MiniTile miniTile = ((TerrainDataInitializer) terrainData).getMiniTile_(w,
                                    CheckMode.NO_CHECK);
                            if (((MiniTileImpl) miniTile).isAltitudeMissing()) {
                                if (updatedHighestAltitude != null
                                        && updatedHighestAltitude.intValue() > altitude.intValue()) {
                                    throw new IllegalStateException();
                                }
                                updatedHighestAltitude = altitude;
                                current.setRight(altitude);
                                ((MiniTileImpl) miniTile).setAltitude(altitude);
                            }
                        }
                    }
                }
            }
        }

        return updatedHighestAltitude;
    }

    @Override
    public void setHighestAltitude(final Altitude altitude) {
        super.highestAltitude = altitude;
    }

    //----------------------------------------------------------------------

    ////////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////////
    // MapImpl::processBlockingNeutrals
    ////////////////////////////////////////////////////////////////////////

    @Override
    public void processBlockingNeutrals(final List<Neutral> candidates) {
        for (final Neutral pCandidate : candidates) {
            if (pCandidate.getNextStacked() == null) { // in the case where several neutrals are stacked, we only consider the top one
                final List<WalkPosition> border = trimOuterMiniTileBorder(
                        getOuterMiniTileBorderOfNeutral(pCandidate));

                final List<WalkPosition> doors = getDoors(border);

                final List<WalkPosition> trueDoors = getTrueDoors(doors, pCandidate);

                markBlockingStackedNeutrals(pCandidate, trueDoors);
            }
        }
    }

    @Override
    public List<Neutral> getCandidates(final List<StaticBuilding> staticBuildings, final List<Mineral> minerals) {
        final List<Neutral> candidates = new ArrayList<>();
        candidates.addAll(staticBuildings);
        candidates.addAll(minerals);
        return candidates;
    }

    //----------------------------------------------------------------------
    // 1)  Retrieve the Border: the outer border of pCandidate
    //----------------------------------------------------------------------

    @Override
    public List<WalkPosition> getOuterMiniTileBorderOfNeutral(final Neutral pCandidate) {
        return BwemExt.outerMiniTileBorder(pCandidate.getTopLeft(), pCandidate.getSize());
    }

    @Override
    public List<WalkPosition> trimOuterMiniTileBorder(final List<WalkPosition> border) {
        border.removeIf(w -> (!getData().getMapData().isValid(w)
                || !getData().getMiniTile(w, CheckMode.NO_CHECK).isWalkable()
                || getData().getTile(w.toTilePosition(), CheckMode.NO_CHECK).getNeutral() != null));
        return border;
    }

    //----------------------------------------------------------------------

    /**
     * 2)  Find the doors in border: one door for each connected set of walkable, neighboring miniTiles.
     *     The searched connected miniTiles all have to be next to some lake or some static building, though they can't be part of one.
     */
    @Override
    public List<WalkPosition> getDoors(final List<WalkPosition> border) {
        final List<WalkPosition> doors = new ArrayList<>();

        while (!border.isEmpty()) {
            final WalkPosition door = border.remove(border.size() - 1);
            doors.add(door);

            final List<WalkPosition> toVisit = new ArrayList<>();
            toVisit.add(door);

            final List<WalkPosition> visited = new ArrayList<>();
            visited.add(door);

            while (!toVisit.isEmpty()) {
                final WalkPosition current = toVisit.remove(toVisit.size() - 1);

                final WalkPosition[] deltas = { new WalkPosition(0, -1), new WalkPosition(-1, 0),
                        new WalkPosition(+1, 0), new WalkPosition(0, +1) };
                for (final WalkPosition delta : deltas) {
                    final WalkPosition next = current.add(delta);
                    if (getData().getMapData().isValid(next) && !visited.contains(next)) {
                        if (getData().getMiniTile(next, CheckMode.NO_CHECK).isWalkable()) {
                            if (getData().getTile((next.toPosition()).toTilePosition(), CheckMode.NO_CHECK)
                                    .getNeutral() == null) {
                                if (BwemExt.adjoins8SomeLakeOrNeutral(next, this)) {
                                    toVisit.add(next);
                                    visited.add(next);
                                }
                            }
                        }
                    }
                }
            }

            border.removeIf(visited::contains);
        }

        return doors;
    }

    /**
     * 3)  If at least 2 doors, find the true doors in Border: a true door is a door that gives onto an area big enough
     */
    @Override
    public List<WalkPosition> getTrueDoors(final List<WalkPosition> doors, final Neutral pCandidate) {
        final List<WalkPosition> trueDoors = new ArrayList<>();

        if (doors.size() >= 2) {
            for (final WalkPosition door : doors) {
                final List<WalkPosition> toVisit = new ArrayList<>();
                toVisit.add(door);

                final List<WalkPosition> visited = new ArrayList<>();
                visited.add(door);

                final int limit = (pCandidate instanceof StaticBuilding) ? 10 : 400; //TODO: Description for 10 and 400?

                while (!toVisit.isEmpty() && (visited.size() < limit)) {
                    final WalkPosition current = toVisit.remove(toVisit.size() - 1);
                    final WalkPosition[] deltas = { new WalkPosition(0, -1), new WalkPosition(-1, 0),
                            new WalkPosition(+1, 0), new WalkPosition(0, +1) };
                    for (final WalkPosition delta : deltas) {
                        final WalkPosition next = current.add(delta);
                        if (getData().getMapData().isValid(next) && !visited.contains(next)) {
                            if (getData().getMiniTile(next, CheckMode.NO_CHECK).isWalkable()) {
                                if (getData().getTile(next.toTilePosition(), CheckMode.NO_CHECK)
                                        .getNeutral() == null) {
                                    toVisit.add(next);
                                    visited.add(next);
                                }
                            }
                        }
                    }
                }
                if (visited.size() >= limit) {
                    trueDoors.add(door);
                }
            }
        }

        return trueDoors;
    }

    /**
     * 4)  If at least 2 true doors, pCandidate is a blocking static building
     */
    @Override
    public void markBlockingStackedNeutrals(final Neutral pCandidate, final List<WalkPosition> trueDoors) {
        if (trueDoors.size() >= 2) {
            // Marks pCandidate (and any Neutral stacked with it) as blocking.
            for (Neutral pNeutral = getData().getTile(pCandidate.getTopLeft())
                    .getNeutral(); pNeutral != null; pNeutral = pNeutral.getNextStacked()) {
                ((NeutralImpl) pNeutral).setBlocking(trueDoors);
            }

            // Marks all the miniTiles of pCandidate as blocked.
            // This way, areas at trueDoors won't merge together.
            final WalkPosition pCandidateW = pCandidate.getSize().toWalkPosition();
            for (int dy = 0; dy < pCandidateW.getY(); ++dy) {
                for (int dx = 0; dx < pCandidateW.getX(); ++dx) {
                    final MiniTile miniTile = ((TerrainDataInitializer) getData())
                            .getMiniTile_(((pCandidate.getTopLeft().toPosition()).toWalkPosition())
                                    .add(new WalkPosition(dx, dy)));
                    if (miniTile.isWalkable()) {
                        ((MiniTileImpl) miniTile).setBlocked();
                    }
                }
            }
        }
    }

    ////////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////////
    // MapImpl::ComputeAreas
    ////////////////////////////////////////////////////////////////////////

    // Assigns MiniTile::m_areaId for each miniTile having AreaIdMissing()
    // Areas are computed using MiniTile::Altitude() information only.
    // The miniTiles are considered successively in descending order of their Altitude().
    // Each of them either:
    //   - involves the creation of a new area.
    //   - is added to some existing neighboring area.
    //   - makes two neighboring areas merge together.
    @Override
    public void computeAreas(final List<TempAreaInfo> tempAreaList, final int areaMinMiniTiles) {
        createAreas(tempAreaList, areaMinMiniTiles);
        setAreaIdAndLowestAltitudeInTiles();
    }

    @Override
    public List<MutablePair<WalkPosition, MiniTile>> getSortedMiniTilesByDescendingAltitude() {
        final List<MutablePair<WalkPosition, MiniTile>> miniTilesByDescendingAltitude = new ArrayList<>();

        for (int y = 0; y < getData().getMapData().getWalkSize().getY(); ++y) {
            for (int x = 0; x < getData().getMapData().getWalkSize().getX(); ++x) {
                final WalkPosition w = new WalkPosition(x, y);
                final MiniTile miniTile = ((TerrainDataInitializer) getData()).getMiniTile_(w, CheckMode.NO_CHECK);
                if (((MiniTileImpl) miniTile).isAreaIdMissing()) {
                    miniTilesByDescendingAltitude.add(new MutablePair<>(w, miniTile));
                }
            }
        }

        miniTilesByDescendingAltitude.sort(new PairGenericMiniTileAltitudeComparator<>());
        Collections.reverse(miniTilesByDescendingAltitude);

        return miniTilesByDescendingAltitude;
    }

    @Override
    public List<TempAreaInfo> computeTempAreas(
            final List<MutablePair<WalkPosition, MiniTile>> miniTilesByDescendingAltitude) {
        final List<TempAreaInfo> tempAreaList = new ArrayList<>();
        tempAreaList.add(new TempAreaInfo()); // tempAreaList[0] left unused, as AreaIds are > 0

        for (final MutablePair<WalkPosition, MiniTile> current : miniTilesByDescendingAltitude) {
            final WalkPosition pos = new WalkPosition(current.getLeft().getX(), current.getLeft().getY());
            final MiniTile cur = current.getRight();

            final MutablePair<AreaId, AreaId> neighboringAreas = findNeighboringAreas(pos);
            if (neighboringAreas.getLeft() == null) { // no neighboring area : creates of a new area
                tempAreaList.add(new TempAreaInfo(new AreaId(tempAreaList.size()), cur, pos));
            } else if (neighboringAreas.getRight() == null) { // one neighboring area : adds cur to the existing area
                tempAreaList.get(neighboringAreas.getLeft().intValue()).add(cur);
            } else { // two neighboring areas : adds cur to one of them  &  possible merging
                AreaId smaller = neighboringAreas.getLeft();
                AreaId bigger = neighboringAreas.getRight();
                if (tempAreaList.get(smaller.intValue()).getSize() > tempAreaList.get(bigger.intValue())
                        .getSize()) {
                    AreaId smallerTmp = smaller;
                    smaller = bigger;
                    bigger = smallerTmp;
                }

                // Condition for the neighboring areas to merge:
                //                any_of(StartingLocations().begin(), StartingLocations().end(), [&pos](const TilePosition & startingLoc)
                //                    { return dist(TilePosition(pos), startingLoc + TilePosition(2, 1)) <= 3;})
                boolean cppAlgorithmStdAnyOf = getData().getMapData().getStartingLocations().stream()
                        .anyMatch(startingLoc -> BwemExt.dist(pos.toTilePosition(),
                                startingLoc.add(new TilePosition(2, 1))) <= 3.0);
                final int curAltitude = cur.getAltitude().intValue();
                final int biggerHighestAltitude = tempAreaList.get(bigger.intValue()).getHighestAltitude()
                        .intValue();
                final int smallerHighestAltitude = tempAreaList.get(smaller.intValue()).getHighestAltitude()
                        .intValue();
                if ((tempAreaList.get(smaller.intValue()).getSize() < 80) || (smallerHighestAltitude < 80)
                        || ((double) curAltitude / (double) biggerHighestAltitude >= 0.90)
                        || ((double) curAltitude / (double) smallerHighestAltitude >= 0.90)
                        || cppAlgorithmStdAnyOf) {
                    // adds cur to the absorbing area:
                    tempAreaList.get(bigger.intValue()).add(cur);

                    // merges the two neighboring areas:
                    replaceAreaIds(tempAreaList.get(smaller.intValue()).getWalkPositionWithHighestAltitude(),
                            bigger);
                    tempAreaList.get(bigger.intValue()).merge(tempAreaList.get(smaller.intValue()));
                } else { // no merge : cur starts or continues the frontier between the two neighboring areas
                    // adds cur to the chosen Area:
                    tempAreaList.get(chooseNeighboringArea(smaller, bigger).intValue()).add(cur);
                    super.rawFrontier.add(new MutablePair<>(neighboringAreas, pos));
                }
            }
        }

        // Remove from the frontier obsolete positions
        rawFrontier.removeIf(f -> f.getLeft().getLeft().equals(f.getLeft().getRight()));

        return tempAreaList;
    }

    @Override
    public void replaceAreaIds(final WalkPosition p, final AreaId newAreaId) {
        final MiniTile origin = ((TerrainDataInitializer) getData()).getMiniTile_(p, CheckMode.NO_CHECK);
        final AreaId oldAreaId = origin.getAreaId();
        ((MiniTileImpl) origin).replaceAreaId(newAreaId);

        List<WalkPosition> toSearch = new ArrayList<>();
        toSearch.add(p);
        while (!toSearch.isEmpty()) {
            final WalkPosition current = toSearch.remove(toSearch.size() - 1);

            final WalkPosition[] deltas = { new WalkPosition(0, -1), new WalkPosition(-1, 0),
                    new WalkPosition(+1, 0), new WalkPosition(0, +1) };
            for (final WalkPosition delta : deltas) {
                final WalkPosition next = current.add(delta);
                if (getData().getMapData().isValid(next)) {
                    final MiniTile miniTile = ((TerrainDataInitializer) getData()).getMiniTile_(next,
                            CheckMode.NO_CHECK);
                    if (miniTile.getAreaId().equals(oldAreaId)) {
                        toSearch.add(next);
                        ((MiniTileImpl) miniTile).replaceAreaId(newAreaId);
                    }
                }
            }
        }

        // also replaces references of oldAreaId by newAreaId in getRawFrontier:
        if (newAreaId.intValue() > 0) {
            for (final MutablePair<MutablePair<AreaId, AreaId>, WalkPosition> f : super.rawFrontier) {
                if (f.getLeft().getLeft().equals(oldAreaId)) {
                    f.getLeft().setLeft(newAreaId);
                }
                if (f.getLeft().getRight().equals(oldAreaId)) {
                    f.getLeft().setRight(newAreaId);
                }
            }
        }
    }

    // Initializes Graph with the valid and big enough areas in tempAreaList.
    @Override
    public void createAreas(final List<TempAreaInfo> tempAreaList, final int areaMinMiniTiles) {
        final List<MutablePair<WalkPosition, Integer>> areasList = new ArrayList<>();

        int newAreaId = 1;
        int newTinyAreaId = -2;

        for (final TempAreaInfo tempArea : tempAreaList) {
            if (tempArea.isValid()) {
                if (tempArea.getSize() >= areaMinMiniTiles) {
                    //                    bwem_assert(newAreaId <= tempArea.id());
                    if (!(newAreaId <= tempArea.getId().intValue())) {
                        throw new IllegalStateException();
                    }
                    if (newAreaId != tempArea.getId().intValue()) {
                        replaceAreaIds(tempArea.getWalkPositionWithHighestAltitude(), new AreaId(newAreaId));
                    }

                    areasList.add(
                            new MutablePair<>(tempArea.getWalkPositionWithHighestAltitude(), tempArea.getSize()));
                    ++newAreaId;
                } else {
                    replaceAreaIds(tempArea.getWalkPositionWithHighestAltitude(), new AreaId(newTinyAreaId));
                    --newTinyAreaId;
                }
            }
        }

        getGraph().createAreas(areasList);
    }

    // Renamed from "MapImpl::SetAltitudeInTile"
    @Override
    public void setLowestAltitudeInTile(final TilePosition t) {
        Altitude lowestAltitude = new Altitude(Integer.MAX_VALUE);

        for (int dy = 0; dy < 4; ++dy)
            for (int dx = 0; dx < 4; ++dx) {
                final Altitude altitude = getData()
                        .getMiniTile(((t.toPosition()).toWalkPosition()).add(new WalkPosition(dx, dy)),
                                CheckMode.NO_CHECK)
                        .getAltitude();
                if (altitude.intValue() < lowestAltitude.intValue()) {
                    lowestAltitude = altitude;
                }
            }

        ((TileImpl) ((TerrainDataInitializer) getData()).getTile_(t)).setLowestAltitude(lowestAltitude);
    }

    // Renamed from "MapImpl::SetAreaIdInTiles"
    private void setAreaIdAndLowestAltitudeInTiles() {
        for (int y = 0; y < getData().getMapData().getTileSize().getY(); ++y)
            for (int x = 0; x < getData().getMapData().getTileSize().getX(); ++x) {
                final TilePosition t = new TilePosition(x, y);
                setAreaIdInTile(t);
                setLowestAltitudeInTile(t);
            }
    }

    ////////////////////////////////////////////////////////////////////////

    @Override
    public List<PlayerUnit> filterPlayerUnits(final Collection<Unit> units, final Player player) {
        return super.filterPlayerUnits(units, player);
    }

    @Override
    public List<PlayerUnit> filterNeutralPlayerUnits(final Collection<Unit> units,
            final Collection<Player> players) {
        return super.filterNeutralPlayerUnits(units, players);
    }

}