Skip to content

Commit 6d94e3c

Browse files
committed
Test graph connectivity on node invalidation
1 parent d1d2302 commit 6d94e3c

File tree

2 files changed

+149
-3
lines changed

2 files changed

+149
-3
lines changed

common/src/main/kotlin/com/lambda/pathing/dstar/DStarLite.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ class DStarLite(
150150
val current = graph.successors(v)
151151
val updated = graph.nodeInitializer(v)
152152
val removed = current.filter { (w, _) -> w !in updated }
153+
updateEdge(u, v, INF)
153154
removed.forEach { (w, _) ->
154155
updateEdge(v, w, INF)
155156
updateEdge(w, v, INF)

common/src/test/kotlin/pathing/DStarLiteTest.kt

Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ import kotlin.test.assertTrue
5151
*/
5252

5353
internal class DStarLiteTest {
54-
55-
private val blockedNodes = mutableSetOf<FastVector>()
56-
5754
// Simple Manhattan distance heuristic
5855
private fun manhattanHeuristic(a: FastVector, b: FastVector): Double {
5956
return (abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z)).toDouble()
@@ -269,4 +266,152 @@ internal class DStarLiteTest {
269266
val path = dStar.path()
270267
assertEquals(listOf(startNode), path) // Path is just the start/goal node
271268
}
269+
270+
@Test
271+
fun `invalidate node forces path recalculation on 6-conn graph`() {
272+
// Create a graph with a straight path from (0,0,0) to (5,0,0)
273+
val startNode = fastVectorOf(0, 0, 0)
274+
val goalNode = fastVectorOf(5, 0, 0)
275+
val localBlockedNodes = mutableSetOf<FastVector>()
276+
val graph = createGridGraph6Conn(localBlockedNodes)
277+
val dStar = DStarLite(graph, startNode, goalNode, ::manhattanHeuristic)
278+
279+
// Compute initial path
280+
dStar.computeShortestPath()
281+
val initialPath = dStar.path()
282+
283+
// Verify initial path is straight
284+
assertEquals(6, initialPath.size)
285+
assertEquals(startNode, initialPath.first())
286+
assertEquals(goalNode, initialPath.last())
287+
assertEquals(fastVectorOf(1, 0, 0), initialPath[1])
288+
assertEquals(fastVectorOf(2, 0, 0), initialPath[2])
289+
290+
// Invalidate a node in the middle of the path
291+
val nodeToInvalidate = fastVectorOf(2, 0, 0)
292+
localBlockedNodes.add(nodeToInvalidate)
293+
dStar.invalidate(nodeToInvalidate)
294+
295+
// Recompute path
296+
dStar.computeShortestPath()
297+
val newPath = dStar.path()
298+
299+
// Verify new path avoids the invalidated node
300+
assertTrue(nodeToInvalidate !in newPath, "Path should not contain the invalidated node")
301+
assertEquals(startNode, newPath.first())
302+
assertEquals(goalNode, newPath.last())
303+
304+
// The new path should be longer as it has to go around the blocked node
305+
assertTrue(newPath.size > initialPath.size, "New path should be longer than the initial path")
306+
}
307+
308+
@Test
309+
fun `invalidate multiple nodes forces complex rerouting on 6-conn graph`() {
310+
// Create a graph with a straight path from (0,0,0) to (5,0,0)
311+
val startNode = fastVectorOf(0, 0, 0)
312+
val goalNode = fastVectorOf(5, 0, 0)
313+
314+
// Pre-block nodes in the blockedNodes set before creating the graph
315+
val localBlockedNodes = mutableSetOf<FastVector>()
316+
val nodesToBlock = listOf(
317+
fastVectorOf(2, 0, 0), // Block straight path
318+
fastVectorOf(2, 1, 0), // Block one alternative
319+
fastVectorOf(2, -1, 0) // Block another alternative
320+
)
321+
322+
// Add nodes to blocked set before creating the graph
323+
localBlockedNodes.addAll(nodesToBlock)
324+
325+
// Create graph with pre-blocked nodes
326+
val graph = createGridGraph6Conn(localBlockedNodes)
327+
val dStar = DStarLite(graph, startNode, goalNode, ::manhattanHeuristic)
328+
329+
// Compute path with pre-blocked nodes
330+
dStar.computeShortestPath()
331+
val path = dStar.path()
332+
333+
// Print debug info about the path
334+
println("[DEBUG_LOG] Path with pre-blocked nodes:")
335+
path.forEach { node ->
336+
println("[DEBUG_LOG] - Node: $node (x=${node.x}, y=${node.y}, z=${node.z})")
337+
}
338+
339+
// Verify path avoids all blocked nodes
340+
nodesToBlock.forEach { node ->
341+
assertTrue(node !in path, "Path should not contain blocked node $node")
342+
}
343+
344+
// Verify path starts and ends at the correct nodes
345+
assertEquals(startNode, path.first())
346+
assertEquals(goalNode, path.last())
347+
348+
// The path should be longer than a straight line (which would be 6 nodes)
349+
assertTrue(path.size > 6, "Path should be longer than a straight line")
350+
}
351+
352+
@Test
353+
fun `nodeInitializer correctly omits blocked nodes from graph`() {
354+
// Create a set of blocked nodes
355+
val localBlockedNodes = mutableSetOf<FastVector>()
356+
val nodeToBlock = fastVectorOf(2, 0, 0)
357+
localBlockedNodes.add(nodeToBlock)
358+
359+
// Create graph with blocked nodes
360+
val graph = createGridGraph6Conn(localBlockedNodes)
361+
362+
// Check that the nodeInitializer correctly omits the blocked node
363+
val startNode = fastVectorOf(1, 0, 0) // Node adjacent to blocked node
364+
val successors = graph.successors(startNode)
365+
366+
// The blocked node should not be in the successors
367+
assertFalse(nodeToBlock in successors.keys, "Blocked node should not be in successors")
368+
369+
// Create a DStarLite instance and compute path
370+
val goalNode = fastVectorOf(3, 0, 0) // Goal is on the other side of blocked node
371+
val dStar = DStarLite(graph, startNode, goalNode, ::manhattanHeuristic)
372+
dStar.computeShortestPath()
373+
val path = dStar.path()
374+
375+
// Verify path avoids the blocked node
376+
assertTrue(nodeToBlock !in path, "Path should not contain the blocked node")
377+
assertEquals(startNode, path.first())
378+
assertEquals(goalNode, path.last())
379+
}
380+
381+
@Test
382+
fun `invalidate node updates connectivity on diagonal graph`() {
383+
// Create a graph with diagonal connectivity
384+
val startNode = fastVectorOf(0, 0, 0)
385+
val goalNode = fastVectorOf(2, 2, 0)
386+
val localBlockedNodes = mutableSetOf<FastVector>()
387+
val graph = createGridGraph18Conn(localBlockedNodes)
388+
val dStar = DStarLite(graph, startNode, goalNode, ::euclideanHeuristic)
389+
390+
// Compute initial path
391+
dStar.computeShortestPath()
392+
val initialPath = dStar.path()
393+
394+
// Initial path should be diagonal
395+
assertEquals(3, initialPath.size)
396+
assertEquals(startNode, initialPath.first())
397+
assertEquals(fastVectorOf(1, 1, 0), initialPath[1])
398+
assertEquals(goalNode, initialPath.last())
399+
400+
// Invalidate the diagonal node
401+
val nodeToInvalidate = fastVectorOf(1, 1, 0)
402+
localBlockedNodes.add(nodeToInvalidate)
403+
dStar.invalidate(nodeToInvalidate)
404+
405+
// Recompute path
406+
dStar.computeShortestPath()
407+
val newPath = dStar.path()
408+
409+
// Verify new path avoids the invalidated node
410+
assertTrue(nodeToInvalidate !in newPath, "Path should not contain the invalidated node")
411+
assertEquals(startNode, newPath.first())
412+
assertEquals(goalNode, newPath.last())
413+
414+
// The new path should go around the blocked diagonal
415+
assertTrue(newPath.size > initialPath.size, "New path should be longer than the initial path")
416+
}
272417
}

0 commit comments

Comments
 (0)