public void GraphWithoutTeleportRule() { var cellGraph = generateTestGraph(); var startingCell = cellGraph.LookupCell(0, 0); var targetCell = cellGraph.LookupCell(2, 2); var graph = DijkstraWeightGraph.BuildDijkstraWeightGraph(cellGraph, startingCell, maxDistance: 0, allowNeighborTeleportation: false); var expectedPath = new Vector3Int[] { new Vector3Int(0, 0, 0), new Vector3Int(0, 1, 0), new Vector3Int(0, 2, 0), new Vector3Int(0, 3, 0), new Vector3Int(1, 3, 0), new Vector3Int(2, 3, 0), new Vector3Int(2, 2, 0), }; var expectedDistance = expectedPath.Length - 1; var(accessible, shortestPath) = graph.LookupShortestPath(targetCell); Assert.True(accessible); Assert.True(PathsMatch(shortestPath.Path, expectedPath)); var(accessible2, distance) = graph.LookupDistance(targetCell); Assert.True(accessible2); Assert.True(distance == expectedDistance); }
public void GraphWithTeleportRule() { var cellGraph = generateTestGraph(); var startingCell = cellGraph.LookupCell(0, 0); var targetCell = cellGraph.LookupCell(2, 2); var graph = DijkstraWeightGraph.BuildDijkstraWeightGraph(cellGraph, startingCell, maxDistance: 0, allowNeighborTeleportation: true); // Either of these is acceptable... var expectedPathOne = new Vector3Int[] { new Vector3Int(0, 0, 0), new Vector3Int(0, 1, 0), new Vector3Int(0, 2, 0), new Vector3Int(2, 2, 0), }; var expectedPathTwo = new Vector3Int[] // Hop the gate allowed, contrary to actual rules in original { // I might adjust that later, but for now it's expected new Vector3Int(0, 0, 0), new Vector3Int(1, 0, 0), new Vector3Int(2, 0, 0), new Vector3Int(2, 2, 0), }; var expectedDistance = expectedPathOne.Length - 1; var(accessible, shortestPath) = graph.LookupShortestPath(targetCell); Assert.True(accessible); Assert.True(PathsMatch(shortestPath.Path, expectedPathOne) || PathsMatch(shortestPath.Path, expectedPathTwo)); var(accessible2, distance) = graph.LookupDistance(targetCell); Assert.True(accessible2); Assert.True(distance == expectedDistance); }
/// <summary> /// Call this when something changes in the environment /// (Gate unlocked, for example) /// </summary> /// <param name="cellGraph">The current scene cell graph</param> public void RebuildGraph(LogicalCellGraph cellGraph) { if (playerPawn == null) { return; } LogicalCell commandPosition; if (hasCommitedCell) { commandPosition = cellGraph.LookupCell(lastCommittedCell.x, lastCommittedCell.y); } else { var playerPos = playerPawn.LogicalLocation; var playerCell = cellGraph.LookupCell(playerPos.x, playerPos.y); commandPosition = playerCell; } playerWeightGraph = DijkstraWeightGraph.BuildDijkstraWeightGraph( cellGraph, commandPosition, maxDistance: 6, allowNeighborTeleportation: true); }
public void GraphWithLimitedDistance() { var cellGraph = generateTestGraph(); var startingCell = cellGraph.LookupCell(0, 0); var targetCell = cellGraph.LookupCell(2, 2); var graph = DijkstraWeightGraph.BuildDijkstraWeightGraph(cellGraph, startingCell, maxDistance: 2, allowNeighborTeleportation: true); var(accessible, _) = graph.LookupShortestPath(targetCell); Assert.False(accessible); var(accessible2, _) = graph.LookupDistance(targetCell); Assert.False(accessible2); }
/// <summary> /// Should be called when the scene's graph changes /// (Ie. a locked gate has been lifted) /// </summary> /// <param name="cellGraph">The scene graph</param> public void RebuildGraph(LogicalCellGraph cellGraph) { if (enemyPawn == null) { return; } var enemyPos = enemyPawn.LogicalLocation; var enemyCell = cellGraph.LookupCell(enemyPos.x, enemyPos.y); enemyWeightGraph = DijkstraWeightGraph.BuildDijkstraWeightGraph( cellGraph, enemyCell, maxDistance: 0, allowNeighborTeleportation: true); }
/// <summary> /// Builds a new precalculated Dijkstra graph based on the starting location. /// </summary> /// <param name="graph">The cell graph we are working from</param> /// <param name="startingLocation">The location common to all futher distance calculations</param> /// <param name="maxDistance">The maximum distance away from the starting location the graph will store. If left as 0, it counts as infinite.</param> /// <param name="allowNeighborTeleportation">This game has special rules to allow traversal on non-adjacent cells. Passing false will disable this rule making it more like the assignment likely intended (but that would be less fun!)</param> /// <returns>The baked/calculated Dijkstra graph</returns> public static DijkstraWeightGraph BuildDijkstraWeightGraph(LogicalCellGraph graph, LogicalCell startingLocation, int maxDistance = 0, bool allowNeighborTeleportation = false) { DijkstraWeightGraph g = new DijkstraWeightGraph(); g.startingLocation = startingLocation; var distances = new Dictionary <LogicalCell, int>(); var path = new Dictionary <LogicalCell, LogicalCell>(); var remainingCells = new HashSet <LogicalCell>(); foreach (LogicalCell cell in graph.Cells) { distances[cell] = int.MaxValue; path[cell] = null; remainingCells.Add(cell); } distances[startingLocation] = 0; while (remainingCells.Any()) { LogicalCell currentCell = remainingCells.First(); int currentCellDistance = int.MaxValue; foreach (LogicalCell cell in remainingCells) { var d = distances[cell]; if (d < currentCellDistance) { currentCellDistance = d; currentCell = cell; } } remainingCells.Remove(currentCell); if (currentCellDistance != int.MaxValue && (maxDistance == 0 || currentCellDistance + 1 <= maxDistance)) { foreach (var neighbor in currentCell.Neighbors) { if (neighbor == null) { continue; } // The special rule for this game allows non-adjacent traversal on matching colors. if (!allowNeighborTeleportation) { bool adjacent = (Math.Abs(neighbor.X - currentCell.X) <= 1 && neighbor.Y == currentCell.Y) || (Math.Abs(neighbor.Y - currentCell.Y) <= 1 && neighbor.X == currentCell.X); if (!adjacent) { continue; // Skip } } if (!remainingCells.Contains(neighbor)) { continue; // Skip } int newDistance = currentCellDistance + 1; if (newDistance < distances[neighbor]) { distances[neighbor] = newDistance; path[neighbor] = currentCell; } } } } g.distancesByCell = distances; g.paths = path; return(g); }