// 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;
import bwapi.Pair;
import bwapi.WalkPosition;
import bwem.util.CheckMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* ChokePoints are frontiers that BWEM automatically computes from Brood War's maps.
* A ChokePoint represents (part of) the frontier between exactly 2 Areas. It has a form of line.
*
* A ChokePoint doesn't contain any MiniTile: All the MiniTiles whose positions are returned by its
* Geometry() are just guaranteed to be part of one of the 2 Areas.
* Among the MiniTiles of its Geometry, 3 particular ones called nodes can also be accessed using
* Pos(middle), Pos(end1) and Pos(end2).
* ChokePoints play an important role in BWEM:
* - they define accessibility between Areas.
* - the Paths provided by BWMap::GetPath are made of ChokePoints.
* Like Areas and Bases, the number and the addresses of ChokePoint instances remain unchanged.
*
* Pseudo ChokePoints:
* Some Neutrals can be detected as blocking Neutrals (Cf. Neutral::Blocking).
* Because only ChokePoints can serve as frontiers between Areas, BWEM automatically creates a
* ChokePoint for each blocking Neutral (only one in the case of stacked blocking Neutral).
* Such ChokePoints are called pseudo ChokePoints and they behave differently in several ways.
*/
public final class ChokePoint {
private final Graph graph;
private final boolean isPseudo;
private final int index;
private final Pair areas;
private final WalkPosition[] nodes;
private final List
* - Note: the returned value is contained in geometry()
*/
public WalkPosition getNodePosition(final Node node) {
if (!(node.ordinal() < Node.NODE_COUNT.ordinal())) {
graph.getMap().asserter.throwIllegalStateException("");
}
return this.nodes[node.ordinal()];
}
/**
* Pretty much the same as pos(n), except that the returned MiniTile position is guaranteed to be
* part of pArea. That is: BWMap::getArea(positionOfNodeInArea(n, pArea)) == pArea.
*/
public WalkPosition getNodePositionInArea(final Node node, final Area area) {
if (!(area.equals(this.areas.getLeft()) || area.equals(this.areas.getRight()))) {
graph.getMap().asserter.throwIllegalStateException("");
}
return area.equals(areas.getLeft())
? this.nodesInArea.get(node.ordinal()).getLeft()
: this.nodesInArea.get(node.ordinal()).getRight();
}
/**
* Returns the set of positions that defines the shape of this ChokePoint.
* - Note: none of these miniTiles actually belongs to this ChokePoint (a ChokePoint doesn't
* contain any MiniTile). They are however guaranteed to be part of one of the 2 areas.
* - Note: the returned set contains pos(middle), pos(END_1) and pos(END_2). If isPseudo(),
* returns {p} where p is the position of a walkable MiniTile near from blockingNeutral()->pos().
*/
public List
* - Time complexity: O(1)
* - Note: Corresponds to the length in pixels of getPathTo(cp). So it suffers from the same lack
* of accuracy. In particular, the value returned tends to be slightly higher than expected when
* getPathTo(cp).size() is high.
*/
public int distanceFrom(final ChokePoint chokePoint) {
return getGraph().distance(this, chokePoint);
}
/**
* Returns whether this ChokePoint is accessible from cp (through a walkable path).
* - Note: the relation is symmetric: this->accessibleFrom(cp) == cp->accessibleFrom(this)
* - Note: if this == cp, returns true.
* - Time complexity: O(1)
*/
public boolean accessibleFrom(final ChokePoint chokePoint) {
return (distanceFrom(chokePoint) >= 0);
}
/**
* Returns a list of getChokePoints, which is intended to be the shortest walking path from this
* ChokePoint to cp. The path always starts with this ChokePoint and ends with cp, unless
* accessibleFrom(cp) == false. In this case, an empty list is returned.
* - Note: if this == cp, returns [cp].
* Time complexity: O(1)
* To get the length of the path returned in pixels, use distanceFrom(cp).
* - Note: all the possible Paths are precomputed during BWMap::initialize().
* The best one is then stored for each pair of getChokePoints. However, only the center of the
* getChokePoints is considered. As a consequence, the returned path may not be the shortest one.
*/
public CPPath getPathTo(final ChokePoint cp) {
return getGraph().getPath(this, cp);
}
void onBlockingNeutralDestroyed(final Neutral pBlocking) {
if (pBlocking == null) {
throw new IllegalStateException();
}
if (!pBlocking.isBlocking()) {
graph.getMap().asserter.throwIllegalStateException("");
}
if (pBlocking.equals(this.blockingNeutral)) {
// Ensures that in the case where several neutrals are stacked, blockingNeutral points to the
// bottom one:
this.blockingNeutral =
getMap().getData().getTile(this.blockingNeutral.getTopLeft()).getNeutral();
if (this.blockingNeutral == null && getGraph().getMap().automaticPathUpdate()) {
this.isBlocked = false;
}
}
}
int getIndex() {
return this.index;
}
ChokePoint getPathBackTrace() {
return this.pathBackTrace;
}
void setPathBackTrace(final ChokePoint pathBackTrace) {
this.pathBackTrace = pathBackTrace;
}
@Override
public boolean equals(final Object object) {
if (this == object) {
return true;
} else if (!(object instanceof ChokePoint)) {
return false;
} else {
final ChokePoint that = (ChokePoint) object;
final boolean lel = this.areas.getLeft().equals(that.areas.getLeft());
final boolean ler = this.areas.getLeft().equals(that.areas.getRight());
final boolean rer = this.areas.getRight().equals(that.areas.getRight());
final boolean rel = this.areas.getRight().equals(that.areas.getLeft());
return lel && rer
|| ler && rel; /* true if area pairs are an exact match or if one pair is reversed. */
}
}
@Override
public int hashCode() {
int idLeft = areas.getLeft().getId().intValue();
int idRight = areas.getRight().getId().intValue();
if (idLeft > idRight) {
final int idLeftTmp = idLeft;
idLeft = idRight;
idRight = idLeftTmp;
}
return Objects.hash(idLeft, idRight);
}
/**
* ChokePoint::middle denotes the "middle" MiniTile of Geometry(), while ChokePoint::END_1 and
* ChokePoint::END_2 denote its "ends". It is guaranteed that, among all the MiniTiles of
* Geometry(), ChokePoint::middle has the highest altitude value (Cf. MiniTile::Altitude()).
*/
public enum Node {
END1,
MIDDLE,
END2,
NODE_COUNT
}
}