Skip to content

Commit

Permalink
Add StopCondition to IndexedAStarPathFinder
Browse files Browse the repository at this point in the history
Use StopCondition instead of identity check when current node is being tested on equality with endNode during search.
  • Loading branch information
niemandkun committed Dec 23, 2018
1 parent 2d1523a commit 7c372d1
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class IndexedAStarPathFinder<N> implements PathFinder<N> {
NodeRecord<N>[] nodeRecords;
BinaryHeap<NodeRecord<N>> openList;
NodeRecord<N> current;
public StopCondition<N> stopCondition;
public Metrics metrics;

/** The unique ID for each search run. Used to mark nodes. */
Expand All @@ -69,7 +70,8 @@ public IndexedAStarPathFinder (IndexedGraph<N> graph) {
public IndexedAStarPathFinder (IndexedGraph<N> graph, boolean calculateMetrics) {
this.graph = graph;
this.nodeRecords = (NodeRecord<N>[])new NodeRecord[graph.getNodeCount()];
this.openList = new BinaryHeap<NodeRecord<N>>();
this.openList = new BinaryHeap<>();
this.stopCondition = new EqualsByReferenceStopCondition<>();
if (calculateMetrics) this.metrics = new Metrics();
}

Expand Down Expand Up @@ -111,8 +113,8 @@ protected boolean search (N startNode, N endNode, Heuristic<N> heuristic) {
current = openList.pop();
current.category = CLOSED;

// Terminate if we reached the goal node
if (current.node == endNode) return true;
// Terminate if we reached the stop condition
if (stopCondition.shouldStopSearch(current.node, endNode)) return true;

visitChildren(endNode, heuristic);

Expand Down Expand Up @@ -145,8 +147,8 @@ public boolean search (PathFinderRequest<N> request, long timeToRun) {
current = openList.pop();
current.category = CLOSED;

// Terminate if we reached the goal node; we've found a path.
if (current.node == request.endNode) {
// Terminate if we reached the stop condition; we've found a path.
if (stopCondition.shouldStopSearch(current.node, request.endNode)) {
request.pathFound = true;

generateNodePath(request.startNode, request.resultPath);
Expand Down Expand Up @@ -339,4 +341,25 @@ public void reset () {
openListPeak = 0;
}
}

/** This interface is used to define criteria to interrupt the search.
*
* @param <N> Type of node
*
* @author niemandkun */
public interface StopCondition<N> {
boolean shouldStopSearch(N currentNode, N endNode);
}

/** Default implementation of {@link StopCondition}, which compares two given nodes by reference.
*
* @param <N> Type of node
*
* @author niemandkun */
private static class EqualsByReferenceStopCondition<N> implements StopCondition<N> {
@Override
public boolean shouldStopSearch(N currentNode, N endNode) {
return currentNode == endNode;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
public class IndexedAStarPathFinderTest {

@Test
public void searchNodePath_WhenSearchingAdjacentTile_ExpectedOuputPathLengthEquals2 () {
public void searchNodePath_WhenSearchingAdjacentTile_ExpectedOutputPathLengthEquals2 () {
// @off - disable libgdx formatter
final String graphDrawing =
"..........\n" +
Expand Down Expand Up @@ -96,7 +96,7 @@ public void searchNodePath_WhenSearchingAdjacentTile_ExpectedOuputPathLengthEqua
}

@Test
public void searchNodePath_WhenSearchCanHitDeadEnds_ExpectedOuputPathFound () {
public void searchNodePath_WhenSearchCanHitDeadEnds_ExpectedOutputPathFound () {
// @off - disable libgdx formatter
final String graphDrawing =
".#.#.......#..#...............\n" +
Expand Down Expand Up @@ -138,7 +138,7 @@ public void searchNodePath_WhenSearchCanHitDeadEnds_ExpectedOuputPathFound () {
}

@Test
public void searchNodePath_WhenDestinationUnreachable_ExpectedNoOuputPathFound () {
public void searchNodePath_WhenDestinationUnreachable_ExpectedNoOutputPathFound () {
// @off - disable libgdx formatter
final String graphDrawing =
".....#....\n" +
Expand All @@ -164,6 +164,87 @@ public void searchNodePath_WhenDestinationUnreachable_ExpectedNoOuputPathFound (
Assert.assertFalse("Unexpected search result", searchResult);
}

@Test
public void searchNodePath_WhenGraphIsUpdatingOnTheFly_ExpectedFailToFindEndByReference() {
// @off - disable libgdx formatter
final String graphDrawing =
"...";
// @on - enable libgdx formatter

final MyDynamicGraph dynamicGraph = new MyDynamicGraph(
createGraphFromTextRepresentation(graphDrawing),
new MyNodesFactory() {
@Override
public MyNode getNewInstance(MyNode old) {
MyNode newNode = new MyNode(old.index, old.x, old.y, old.connections.size);
newNode.connections.addAll(old.connections);
return newNode;
}
}
);

final IndexedAStarPathFinder<MyNode> pathfinder = new IndexedAStarPathFinder<>(dynamicGraph);

final GraphPath<MyNode> outPath = new DefaultGraphPath<>();

// @off - disable libgdx formatter
// 012
// S.E 0
// @on - enable libgdx formatter
final boolean searchResult = pathfinder.searchNodePath(
dynamicGraph.graph.nodes.get(0),
dynamicGraph.graph.nodes.get(2),
new ManhattanDistance(),
outPath
);

Assert.assertFalse("Unexpected search result", searchResult);
}

@Test
public void searchNodePath_WhenGraphIsUpdatedOnTheFly_ExpectedSucceedToFindEndByEquals() {
// @off - disable libgdx formatter
final String graphDrawing =
"...";
// @on - enable libgdx formatter

final MyDynamicGraph dynamicGraph = new MyDynamicGraph(
createGraphFromTextRepresentation(graphDrawing),
new MyNodesFactory() {
@Override
public MyNode getNewInstance(MyNode old) {
MyNode newNode = new MyNodeWithEquals(old.index, old.x, old.y, old.connections.size);
newNode.connections.addAll(old.connections);
return newNode;
}
}
);

final IndexedAStarPathFinder<MyNode> pathfinder = new IndexedAStarPathFinder<>(dynamicGraph);
pathfinder.stopCondition = new IndexedAStarPathFinder.StopCondition<MyNode>() {
@Override
public boolean shouldStopSearch(MyNode currentNode, MyNode endNode) {
return currentNode.equals(endNode);
}
};

final GraphPath<MyNode> outPath = new DefaultGraphPath<>();

// @off - disable libgdx formatter
// 012
// S.E 0
// @on - enable libgdx formatter
final boolean searchResult = pathfinder.searchNodePath(
dynamicGraph.graph.nodes.get(0),
dynamicGraph.graph.nodes.get(2),
new ManhattanDistance(),
outPath
);

Assert.assertTrue("Unexpected search result", searchResult);
Assert.assertEquals("Unexpected number of nodes in path", 3, outPath.getCount());
}

private static MyGraph createGraphFromTextRepresentation (final String graphTextRepresentation) {
final String[][] tiles = createStringTilesFromGraphTextRepresentation(graphTextRepresentation);

Expand Down Expand Up @@ -229,7 +310,7 @@ private static String[][] createStringTilesFromGraphTextRepresentation (final St

private static class MyNode {

private final int index;
final int index;
private final int x;
private final int y;
private final Array<Connection<MyNode>> connections;
Expand All @@ -256,6 +337,22 @@ public String toString () {

}

private static class MyNodeWithEquals extends MyNode {

MyNodeWithEquals(int index, int x, int y, int capacity) {
super(index, x, y, capacity);
}

@Override
public boolean equals(Object other) {
if (!(other instanceof MyNode)) {
return false;
}
MyNode otherNode = (MyNode) other;
return this.index == otherNode.index;
}
}

private static class MyGraph implements IndexedGraph<MyNode> {

protected Array<MyNode> nodes;
Expand All @@ -280,6 +377,49 @@ public int getNodeCount () {
}
}

private interface MyNodesFactory {

MyNode getNewInstance(MyNode old);
}

/**
* Works like {@link MyGraph}, but each time when {@link MyDynamicGraph#getConnections} is called,
* it creates new instances of {@link MyNode} using given {@link MyNodesFactory}.
*/
private static class MyDynamicGraph implements IndexedGraph<MyNode> {

private MyGraph graph;
private MyNodesFactory factory;

MyDynamicGraph(MyGraph graph, MyNodesFactory factory) {
this.graph = graph;
this.factory = factory;
}

@Override
public Array<Connection<MyNode>> getConnections(MyNode fromNode) {
Array<Connection<MyNode>> connections = graph.getConnections(fromNode);
Array<Connection<MyNode>> newInstanceConnections = new Array<>(connections.size);
for (Connection<MyNode> connection : connections) {
newInstanceConnections.add(new DefaultConnection<>(
factory.getNewInstance(connection.getFromNode()),
factory.getNewInstance(connection.getToNode())
));
}
return newInstanceConnections;
}

@Override
public int getIndex(MyNode node) {
return graph.getIndex(node);
}

@Override
public int getNodeCount() {
return graph.getNodeCount();
}
}

private static class ManhattanDistance implements Heuristic<MyNode> {
@Override
public float estimate (final MyNode node, final MyNode endNode) {
Expand Down

0 comments on commit 7c372d1

Please sign in to comment.