예제 #1
0
        private void FindSmallestNodeData(
            HashSet <TNode> keys,
            Dictionary <TNode, DynamicGraphNodeData <TNode, TCost, TEdge> > lookUp,
            out TNode smallestNode,
            out DynamicGraphNodeData <TNode, TCost, TEdge> smallestEdge
            )
        {
            Contract.Requires(keys != null);
            Contract.Requires(keys.Count > 0);
            Contract.Requires(lookUp != null);
            Contract.Requires(lookUp.Count > 0);
            Contract.Requires(lookUp.Count >= keys.Count);
            Contract.Requires(Contract.ForAll(lookUp.Values, x => x != null));
            Contract.Requires(Contract.ForAll(keys, key => key != null && lookUp.ContainsKey(key)));
            Contract.Ensures(Contract.ValueAtReturn(out smallestNode) != null);
            Contract.Ensures(Contract.ValueAtReturn(out smallestEdge) != null);

            using (var enumerator = keys.GetEnumerator()) {
                if (!enumerator.MoveNext())
                {
                    throw new ArgumentException("keys is empty", "keys");
                }
                var currentNode = enumerator.Current;

                //var smallest = new KeyValuePair<TNode, DynamicGraphNodeData<TNode, TCost, TEdge>>(currentNode, lookUp[currentNode]);
                smallestNode = currentNode;
                smallestEdge = lookUp[currentNode];
                Contract.Assume(smallestEdge != null);

                while (enumerator.MoveNext())
                {
                    currentNode = enumerator.Current;

                    var currentData = lookUp[currentNode];
                    Contract.Assume(currentData != null);

                    if (CostComparer.Compare(smallestEdge.Cost, currentData.Cost) > 0)
                    {
                        smallestNode = currentNode;
                        smallestEdge = currentData;
                    }
                }
            }
            if (null == smallestNode)
            {
                throw new ArgumentException("null keys are not allowed", "keys");
            }
            if (null == smallestEdge)
            {
                throw new ArgumentException("null edge contained in edge look-up", "lookUp");
            }
        }
예제 #2
0
        /// <summary>
        /// Finds a graph path from the <paramref name="start"/> node to the <paramref name="target"/> node.
        /// </summary>
        /// <param name="start">The node to begin the path search from.</param>
        /// <param name="target">The target node of the search.</param>
        /// <returns>The shortest path from the start node to the target node if one exists.</returns>
        /// <exception cref="System.ArgumentException">Thrown if a node or edge encountered within the graph is <c>null</c>.</exception>
        public ReadOnlyCollection <DynamicGraphNodeData <TNode, TCost, TEdge> > FindPath(TNode start, TNode target)
        {
            if (null == start)
            {
                throw new ArgumentNullException("start");
            }
            if (null == target)
            {
                throw new ArgumentNullException("target");
            }
            Contract.EndContractBlock();

            // initialize the look-ups
            var nodeDataCache = new Dictionary <TNode, DynamicGraphNodeData <TNode, TCost, TEdge> >(NodeComparer)
            {
                { start, new DynamicGraphNodeData <TNode, TCost, TEdge>(start, default(TCost), default(TEdge)) }
            };
            var visitRequired = new HashSet <TNode>(NodeComparer)
            {
                start
            };                                                              // NOTE: in order for a node to be in this collection it must have a corresponding key in the pathData dictionary.
            DynamicGraphNodeData <TNode, TCost, TEdge> nodeData;
            TNode currentNode;
            var   bestCompleteCost   = default(TCost);
            var   completeRouteFound = false;

            // generate the dynamic path information and find the shortest path
            while (visitRequired.Count != 0)
            {
                DynamicGraphNodeData <TNode, TCost, TEdge> currentNodeData;
                Contract.Assume(nodeDataCache.Count >= visitRequired.Count);
                Contract.Assume(Contract.ForAll(visitRequired, k => k != null && nodeDataCache.ContainsKey(k)));
                FindSmallestNodeData(visitRequired, nodeDataCache, out currentNode, out currentNodeData);
                visitRequired.Remove(currentNode);

                // logic to see if we can short out of checking this node due to it being too long
                if (completeRouteFound)
                {
                    if (CostComparer.Compare(bestCompleteCost, currentNodeData.Cost) <= 0)
                    {
                        continue; // this path is larger than or equal to the best found complete path
                    }
                    if (NodeComparer.Equals(currentNode, target))
                    {
                        bestCompleteCost = currentNodeData.Cost;
                    }
                }
                else if (NodeComparer.Equals(currentNode, target))
                {
                    bestCompleteCost   = currentNodeData.Cost;
                    completeRouteFound = true;
                }

                foreach (var neighborInfo in GetNeighborInfo(currentNode, currentNodeData.Cost))
                {
                    if (ReferenceEquals(null, neighborInfo) || ReferenceEquals(null, neighborInfo.Node))
                    {
                        continue;
                    }

                    if (nodeDataCache.TryGetValue(neighborInfo.Node, out nodeData))
                    {
                        Contract.Assume(nodeData != null);
                        if (CostComparer.Compare(neighborInfo.Cost, nodeData.Cost) < 0)
                        {
                            nodeData.Node = currentNode;
                            nodeData.Cost = neighborInfo.Cost;
                            nodeData.Edge = neighborInfo.Edge;
                            visitRequired.Add(neighborInfo.Node);
                        }
                    }
                    else
                    {
                        nodeDataCache.Add(
                            neighborInfo.Node,
                            new DynamicGraphNodeData <TNode, TCost, TEdge>(currentNode, neighborInfo.Cost, neighborInfo.Edge));
                        visitRequired.Add(neighborInfo.Node);
                    }
                }
            }

            // build the final result path
            if (nodeDataCache.TryGetValue(target, out nodeData))
            {
                Contract.Assume(nodeData != null);
                var pathResult = new List <DynamicGraphNodeData <TNode, TCost, TEdge> > {
                    new DynamicGraphNodeData <TNode, TCost, TEdge>(target, nodeData.Cost, nodeData.Edge)
                };
                currentNode = nodeData.Node;
                if (null == currentNode)
                {
                    return(null);
                }
                while (nodeDataCache.TryGetValue(currentNode, out nodeData))
                {
                    Contract.Assume(nodeData != null);
                    pathResult.Add(new DynamicGraphNodeData <TNode, TCost, TEdge>(currentNode, nodeData.Cost, nodeData.Edge));
                    if (Equals(currentNode, start))
                    {
                        break;
                    }
                    currentNode = nodeData.Node;
                    if (null == currentNode)
                    {
                        return(null);
                    }
                }
                pathResult.Reverse();
                return(new ReadOnlyCollection <DynamicGraphNodeData <TNode, TCost, TEdge> >(pathResult));
            }

            return(null); // no path was found
        }
예제 #3
0
        public static void AssignTasksGreedy(List <Task> newGoals, List <CreatureAI> creatures, int maxPerGoal)
        {
            string trackingString = "AssignTasksGreedy(" + newGoals.Count + ")";

            GamePerformance.Instance.StartTrackPerformance(trackingString);
            // We are going to keep track of the unassigned goal count
            // to avoid having to parse the list at the end of the loop.
            int        goalsUnassigned = newGoals.Count;
            List <int> counts          = new List <int>(goalsUnassigned);

            for (int i = 0; i < goalsUnassigned; i++)
            {
                counts.Add(0);
            }

            // Randomized list changed from the CreatureAI objects themselves to an index into the
            // List passed in.  This is to avoid having to shift the masterCosts list around to match
            // each time we randomize.
            List <int> randomIndex = new List <int>(creatures.Count);

            for (int i = 0; i < creatures.Count; i++)
            {
                randomIndex.Add(i);
            }

            // We create the comparer outside of the loop.  It gets reused for each sort.
            CostComparer toCompare = new CostComparer();

            // One of the biggest issues with the old function was that it was recalculating the whole task list for
            // the creature each time through the loop, using one item and then throwing it away.  Nothing changed
            // in how the calculation happened between each time so we will instead make a costs list for each creature
            // and keep them all.  This not only avoids rebuilding the list but the sheer KeyValuePair object churn there already was.
            List <List <KeyValuePair <int, float> > > masterCosts = new List <List <KeyValuePair <int, float> > >(creatures.Count);

            // We will set this up in the next loop rather than make it's own loop.
            List <int> costsPositions = new List <int>(creatures.Count);

            for (int costIndex = 0; costIndex < creatures.Count; costIndex++)
            {
                List <KeyValuePair <int, float> > costs = new List <KeyValuePair <int, float> >();
                CreatureAI creature = creatures[costIndex];

                // We already were doing an index count to be able to make the KeyValuePair for costs
                // and foreach uses Enumeration which is slower.
                for (int i = 0; i < newGoals.Count; i++)
                {
                    Task task = newGoals[i];
                    // We are checking for tasks the creature is already assigned up here to avoid having to check
                    // every task in the newGoals list against every task in the newGoals list.  The newGoals list
                    // should reasonably not contain any task duplicates.
                    if (creature.Tasks.Contains(task))
                    {
                        continue;
                    }

                    float cost = 0;
                    // We've swapped the order of the two checks to take advantage of a new ComputeCost that can act different
                    // if we say we've already called IsFeasible first.  This allows us to skip any calculations that are repeated in both.
                    if (!task.IsFeasible(creature.Creature))
                    {
                        cost += 1e10f;
                    }
                    cost += task.ComputeCost(creature.Creature, true);
                    costs.Add(new KeyValuePair <int, float>(i, cost));
                }
                // The sort lambda function has been replaced by an IComparer class.
                // This is faster but I mainly did it because VS can not Edit & Continue
                // any function with a Lambda function in it which was slowing down dev time.
                costs.Sort(toCompare);

                masterCosts.Add(costs);
                costsPositions.Add(0);
            }


            // We are going to precalculate the maximum iterations and count down
            // instead of up.
            int iters = goalsUnassigned * creatures.Count;

            while (goalsUnassigned > 0 && iters > 0)
            {
                randomIndex.Shuffle();
                iters--;
                for (int creatureIndex = 0; creatureIndex < randomIndex.Count; creatureIndex++)
                {
                    int        randomCreature = randomIndex[creatureIndex];
                    CreatureAI creature       = creatures[randomCreature];

                    List <KeyValuePair <int, float> > costs = masterCosts[randomCreature];
                    int costPosition = costsPositions[randomCreature];
                    // This loop starts with the previous spot we stopped.  This avoids us having to constantly run a task we
                    // know we have processed.
                    for (int i = costPosition; i < costs.Count; i++)
                    {
                        // Incremented at the start in case we find a task and break.
                        costPosition++;

                        KeyValuePair <int, float> taskCost = costs[i];
                        // We've swapped the checks here.  Tasks.Contains is far more expensive so being able to skip
                        // if it's going to fail the maxPerGoal check anyways is very good.
                        if (counts[taskCost.Key] < maxPerGoal)//  && !creature.Tasks.Contains(newGoals[taskCost.Key]))
                        {
                            // We have to check to see if the task we are assigning is fully unassigned.  If so
                            // we reduce the goalsUnassigned count.  If it's already assigned we skip it.
                            if (counts[taskCost.Key] == 0)
                            {
                                goalsUnassigned--;
                            }

                            counts[taskCost.Key]++;
                            creature.Tasks.Add(newGoals[taskCost.Key].Clone());
                            break;
                        }
                    }
                    // We have to set the position we'll start the loop at the next time based on where we found
                    // our task.
                    costsPositions[randomCreature] = costPosition;
                    // The loop at the end to see if all are unassigned is gone now, replaced by a countdown
                    // variable: goalsUnassigned.
                }
            }
            GamePerformance.Instance.EndTrackPerformance(trackingString);
        }