public void givenTwoNodesThatAreEqual_assertEqual()
        {
            PathfindingNode one = new PathfindingNode(1.0f, 1.0f);
            PathfindingNode two = new PathfindingNode(1.0f, 1.0f);

            Assert.True(one.Equals(two));
        }
Example #2
0
        protected override async Task <Queue <TileRef> > Process()
        {
            if (_startNode == null ||
                _endNode == null ||
                Status == JobStatus.Finished)
            {
                return(null);
            }

            // If we couldn't get a nearby node that's good enough
            if (!PathfindingHelpers.TryEndNode(ref _endNode, _pathfindingArgs))
            {
                return(null);
            }

            var openTiles   = new PriorityQueue <ValueTuple <float, PathfindingNode> >(new PathfindingComparer());
            var gScores     = new Dictionary <PathfindingNode, float>();
            var cameFrom    = new Dictionary <PathfindingNode, PathfindingNode>();
            var closedTiles = new HashSet <PathfindingNode>();

            PathfindingNode currentNode = null;

            openTiles.Add((0.0f, _startNode));
            gScores[_startNode] = 0.0f;
            var routeFound = false;
            var count      = 0;

            while (openTiles.Count > 0)
            {
                count++;

                if (count % 20 == 0 && count > 0)
                {
                    await SuspendIfOutOfTime();
                }

                if (_startNode == null || _endNode == null)
                {
                    return(null);
                }

                (_, currentNode) = openTiles.Take();
                if (currentNode.Equals(_endNode))
                {
                    routeFound = true;
                    break;
                }

                closedTiles.Add(currentNode);

                foreach (var(direction, nextNode) in currentNode.Neighbors)
                {
                    if (closedTiles.Contains(nextNode))
                    {
                        continue;
                    }

                    // If tile is untraversable it'll be null
                    var tileCost = PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode);

                    if (tileCost == null || !PathfindingHelpers.DirectionTraversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, currentNode, direction))
                    {
                        continue;
                    }

                    var gScore = gScores[currentNode] + tileCost.Value;

                    if (gScores.TryGetValue(nextNode, out var nextValue) && gScore >= nextValue)
                    {
                        continue;
                    }

                    cameFrom[nextNode] = currentNode;
                    gScores[nextNode]  = gScore;
                    // pFactor is tie-breaker where the fscore is otherwise equal.
                    // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
                    // There's other ways to do it but future consideration
                    var fScore = gScores[nextNode] + PathfindingHelpers.OctileDistance(_endNode, nextNode) * (1.0f + 1.0f / 1000.0f);
                    openTiles.Add((fScore, nextNode));
                }
            }

            if (!routeFound)
            {
                return(null);
            }

            var route = PathfindingHelpers.ReconstructPath(cameFrom, currentNode);

            if (route.Count == 1)
            {
                return(null);
            }

#if DEBUG
            // Need to get data into an easier format to send to the relevant clients
            if (DebugRoute != null && route.Count > 0)
            {
                var debugCameFrom    = new Dictionary <TileRef, TileRef>(cameFrom.Count);
                var debugGScores     = new Dictionary <TileRef, float>(gScores.Count);
                var debugClosedTiles = new HashSet <TileRef>(closedTiles.Count);

                foreach (var(node, parent) in cameFrom)
                {
                    debugCameFrom.Add(node.TileRef, parent.TileRef);
                }

                foreach (var(node, score) in gScores)
                {
                    debugGScores.Add(node.TileRef, score);
                }

                foreach (var node in closedTiles)
                {
                    debugClosedTiles.Add(node.TileRef);
                }

                var debugRoute = new SharedAiDebug.AStarRouteDebug(
                    _pathfindingArgs.Uid,
                    route,
                    debugCameFrom,
                    debugGScores,
                    debugClosedTiles,
                    DebugTime);

                DebugRoute.Invoke(debugRoute);
            }
#endif

            return(route);
        }
Example #3
0
        protected override async Task <Queue <TileRef> > Process()
        {
            // VERY similar to A*; main difference is with the neighbor tiles you look for jump nodes instead
            if (_startNode == null ||
                _endNode == null)
            {
                return(null);
            }

            // If we couldn't get a nearby node that's good enough
            if (!Utils.TryEndNode(ref _endNode, _pathfindingArgs))
            {
                return(null);
            }

            var openTiles   = new PriorityQueue <ValueTuple <float, PathfindingNode> >(new PathfindingComparer());
            var gScores     = new Dictionary <PathfindingNode, float>();
            var cameFrom    = new Dictionary <PathfindingNode, PathfindingNode>();
            var closedTiles = new HashSet <PathfindingNode>();

#if DEBUG
            var jumpNodes = new HashSet <PathfindingNode>();
#endif

            PathfindingNode currentNode = null;
            openTiles.Add((0, _startNode));
            gScores[_startNode] = 0.0f;
            var routeFound = false;
            var count      = 0;

            while (openTiles.Count > 0)
            {
                count++;

                // JPS probably getting a lot fewer nodes than A* is
                if (count % 5 == 0 && count > 0)
                {
                    await SuspendIfOutOfTime();
                }

                (_, currentNode) = openTiles.Take();
                if (currentNode.Equals(_endNode))
                {
                    routeFound = true;
                    break;
                }

                foreach (var(direction, _) in currentNode.Neighbors)
                {
                    var jumpNode = GetJumpPoint(currentNode, direction, _endNode);

                    if (jumpNode != null && !closedTiles.Contains(jumpNode))
                    {
                        closedTiles.Add(jumpNode);
#if DEBUG
                        jumpNodes.Add(jumpNode);
#endif
                        // GetJumpPoint should already check if we can traverse to the node
                        var tileCost = Utils.GetTileCost(_pathfindingArgs, currentNode, jumpNode);

                        if (tileCost == null)
                        {
                            throw new InvalidOperationException();
                        }

                        var gScore = gScores[currentNode] + tileCost.Value;

                        if (gScores.TryGetValue(jumpNode, out var nextValue) && gScore >= nextValue)
                        {
                            continue;
                        }

                        cameFrom[jumpNode] = currentNode;
                        gScores[jumpNode]  = gScore;
                        // pFactor is tie-breaker where the fscore is otherwise equal.
                        // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
                        // There's other ways to do it but future consideration
                        var fScore = gScores[jumpNode] + Utils.OctileDistance(_endNode, jumpNode) * (1.0f + 1.0f / 1000.0f);
                        openTiles.Add((fScore, jumpNode));
                    }
                }
            }

            if (!routeFound)
            {
                return(null);
            }

            var route = Utils.ReconstructJumpPath(cameFrom, currentNode);
            if (route.Count == 1)
            {
                return(null);
            }

#if DEBUG
            // Need to get data into an easier format to send to the relevant clients
            if (DebugRoute != null && route.Count > 0)
            {
                var debugJumpNodes = new HashSet <TileRef>(jumpNodes.Count);

                foreach (var node in jumpNodes)
                {
                    debugJumpNodes.Add(node.TileRef);
                }

                var debugRoute = new SharedAiDebug.JpsRouteDebug(
                    _pathfindingArgs.Uid,
                    route,
                    debugJumpNodes,
                    DebugTime);

                DebugRoute.Invoke(debugRoute);
            }
#endif

            return(route);
        }
Example #4
0
        protected override async Task <Queue <TileRef> > Process()
        {
            if (_startNode == null ||
                _endNode == null ||
                Status == JobStatus.Finished)
            {
                return(null);
            }

            // If we couldn't get a nearby node that's good enough
            if (!PathfindingHelpers.TryEndNode(ref _endNode, _pathfindingArgs))
            {
                return(null);
            }

            var frontier  = new PriorityQueue <ValueTuple <float, PathfindingNode> >(new PathfindingComparer());
            var costSoFar = new Dictionary <PathfindingNode, float>();
            var cameFrom  = new Dictionary <PathfindingNode, PathfindingNode>();

            PathfindingNode currentNode = null;

            frontier.Add((0.0f, _startNode));
            costSoFar[_startNode] = 0.0f;
            var routeFound = false;
            var count      = 0;

            while (frontier.Count > 0)
            {
                // Handle whether we need to pause if we've taken too long
                count++;
                if (count % 20 == 0 && count > 0)
                {
                    await SuspendIfOutOfTime();

                    if (_startNode == null || _endNode == null)
                    {
                        return(null);
                    }
                }

                // Actual pathfinding here
                (_, currentNode) = frontier.Take();
                if (currentNode.Equals(_endNode))
                {
                    routeFound = true;
                    break;
                }

                foreach (var nextNode in currentNode.GetNeighbors())
                {
                    // If tile is untraversable it'll be null
                    var tileCost = PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode);
                    if (tileCost == null)
                    {
                        continue;
                    }

                    // So if we're going NE then that means either N or E needs to be free to actually get there
                    var direction = PathfindingHelpers.RelativeDirection(nextNode, currentNode);
                    if (!PathfindingHelpers.DirectionTraversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, currentNode, direction))
                    {
                        continue;
                    }

                    // f = g + h
                    // gScore is distance to the start node
                    // hScore is distance to the end node
                    var gScore = costSoFar[currentNode] + tileCost.Value;
                    if (costSoFar.TryGetValue(nextNode, out var nextValue) && gScore >= nextValue)
                    {
                        continue;
                    }

                    cameFrom[nextNode]  = currentNode;
                    costSoFar[nextNode] = gScore;
                    // pFactor is tie-breaker where the fscore is otherwise equal.
                    // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
                    // There's other ways to do it but future consideration
                    // The closer the fScore is to the actual distance then the better the pathfinder will be
                    // (i.e. somewhere between 1 and infinite)
                    // Can use hierarchical pathfinder or whatever to improve the heuristic but this is fine for now.
                    var fScore = gScore + PathfindingHelpers.OctileDistance(_endNode, nextNode) * (1.0f + 1.0f / 1000.0f);
                    frontier.Add((fScore, nextNode));
                }
            }

            if (!routeFound)
            {
                return(null);
            }

            var route = PathfindingHelpers.ReconstructPath(cameFrom, currentNode);

            if (route.Count == 1)
            {
                return(null);
            }

#if DEBUG
            // Need to get data into an easier format to send to the relevant clients
            if (DebugRoute != null && route.Count > 0)
            {
                var debugCameFrom = new Dictionary <TileRef, TileRef>(cameFrom.Count);
                var debugGScores  = new Dictionary <TileRef, float>(costSoFar.Count);
                foreach (var(node, parent) in cameFrom)
                {
                    debugCameFrom.Add(node.TileRef, parent.TileRef);
                }

                foreach (var(node, score) in costSoFar)
                {
                    debugGScores.Add(node.TileRef, score);
                }

                var debugRoute = new SharedAiDebug.AStarRouteDebug(
                    _pathfindingArgs.Uid,
                    route,
                    debugCameFrom,
                    debugGScores,
                    DebugTime);

                DebugRoute.Invoke(debugRoute);
            }
#endif

            return(route);
        }
Example #5
0
    public Path NavigateTo(Vector3Int from, Vector3Int to)
    {
        if (!HasNodeAt(from) || !HasNodeAt(to))
        {
            return(null);
        }
        List <PathfindingNode> nodes   = new List <PathfindingNode>();
        PathfindingNode        current = new PathfindingNode(from);
        PathfindingNode        toNode  = new PathfindingNode(to);

        for (int x = 0; x < Width; x += 1)
        {
            for (int y = 0; y < Height; y += 1)
            {
                if (!_nodes[x, y])
                {
                    continue;
                }
                PathfindingNode node = new PathfindingNode(x, y);
                nodes.Add(node);
                node.FScore = node.Heuristic(toNode);
                if (node.Equals(from))
                {
                    current = node;
                }
                if (node.Equals(to))
                {
                    toNode = node;
                }
            }
        }
        current.GScore = 0;
        List <PathfindingNode> closedSet = new List <PathfindingNode>();
        List <PathfindingNode> openSet   = new List <PathfindingNode> {
            current
        };

        while (openSet.Count > 0)
        {
            current = openSet.OrderBy(n => n.FScore).First();
            if (current.Equals(to))
            {
                return(current.ConstructPath());
            }
            openSet.Remove(current);
            closedSet.Add(current);
            foreach (PathfindingNode node in nodes.Where(n => n.IsNeighboor(current)))
            {
                if (closedSet.Contains(node))
                {
                    continue;
                }
                if (!openSet.Contains(node))
                {
                    openSet.Add(node);
                }
                int tentativeGScore = current.GScore + 1;
                if (tentativeGScore > AStarLimit)
                {
                    return(null);
                }
                if (tentativeGScore >= node.GScore)
                {
                    continue;
                }
                node.GScore = tentativeGScore;
                node.FScore = node.GScore + node.Heuristic(toNode);
                if (current.From != null)
                {
                    node.FScore += current.To(current.From).Equals(node.To(current)) ? 0 : 4;
                }
                node.From = current;
            }
        }
        return(null);
    }
Example #6
0
    /// <summary>
    /// Returns path between two nodes. On the path first node is start and last node is end.
    /// TODO: limit range for optimization?
    /// TODO: use this for all pathfinding
    /// TODO: diagonals?
    /// </summary>
    /// <param name="all_nodes"></param>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <returns></returns>
    public static List <PathfindingNode> Path(List <PathfindingNode> all_nodes, PathfindingNode start, PathfindingNode end, bool diagonal_movement)
    {
        List <PathfindingNode> path = new List <PathfindingNode>();
        List <PathfindingNode> Q    = new List <PathfindingNode>();
        Dictionary <PathfindingNode, float>           dist = new Dictionary <PathfindingNode, float>();
        Dictionary <PathfindingNode, PathfindingNode> prev = new Dictionary <PathfindingNode, PathfindingNode>();

        if (!all_nodes.Contains(end))
        {
            all_nodes.Add(end);
        }
        if (!all_nodes.Contains(start))
        {
            all_nodes.Add(start);
        }

        for (int i = 0; i < all_nodes.Count; i++)
        {
            dist.Add(all_nodes[i], float.MaxValue);
            prev.Add(all_nodes[i], null);
            Q.Add(all_nodes[i]);
        }
        dist[start] = 0.0f;

        ///////TODO: Check for impassable tiles and give them dist = float.MaxValue
        foreach (KeyValuePair <Coordinates.Direction, PathfindingNode> v in start.Get_Adjanced_Nodes(all_nodes.ToList(), diagonal_movement))
        {
            dist[v.Value] = Helper.Is_Diagonal(v.Key) ? Mathf.Sqrt(2.0f) * v.Value.Cost : v.Value.Cost;
            prev[v.Value] = start;
        }

        while (Q.Count > 0)
        {
            int   min_dist_index = 0;
            float min_dist       = -1.0f;
            for (int i = 0; i < Q.Count; i++)
            {
                if (dist[Q[i]] < min_dist || min_dist == -1.0f)
                {
                    min_dist_index = i;
                    min_dist       = dist[Q[i]];
                }
            }

            PathfindingNode u = Q[min_dist_index];
            Q.RemoveAt(min_dist_index);

            if (u.Equals(end))
            {
                while (prev[u] != null)
                {
                    path.Insert(0, prev[u]);
                    u = prev[u];
                }
                path.Add(end);
                break;
            }
            else
            {
                foreach (KeyValuePair <Coordinates.Direction, PathfindingNode> v in u.Get_Adjanced_Nodes(all_nodes, diagonal_movement))
                {
                    float alt = Helper.Is_Diagonal(v.Key) ? dist[u] + (Mathf.Sqrt(2.0f) * v.Value.Cost) : dist[u] + v.Value.Cost;
                    ///////TODO: Check for impassable tiles and give them dist = float.MaxValue
                    if (alt < dist[v.Value])
                    {
                        dist[v.Value] = alt;
                        prev[v.Value] = u;
                    }
                }
            }
        }

        if (path.Count == 1 && path[0] == end)
        {
            return(new List <PathfindingNode>());
        }

        return(path);
    }
Example #7
0
 public override bool Equals(object obj)
 {
     return(to.Equals(obj));
 }