public static int[] CalculateOptimalAssignment(List <Task> newGoals, List <CreatureAI> agents) { int numGoals = newGoals.Count; int numAgents = agents.Count; int maxSize = Math.Max(numGoals, numAgents); int[,] goalMatrix = new int[maxSize, maxSize]; const float multiplier = 100; if (numGoals == 0 || numAgents == 0) { return(null); } for (int goalIndex = 0; goalIndex < numGoals; goalIndex++) { Task goal = newGoals[goalIndex]; for (int agentIndex = 0; agentIndex < numAgents; agentIndex++) { CreatureAI agent = agents[agentIndex]; float floatCost = goal.ComputeCost(agent.Creature); int cost = (int)(floatCost * multiplier); if (!goal.IsFeasible(agent.Creature)) { cost += 99999; } if (agent.Creature.Status.IsAsleep) { cost += 99999; } cost += agents[agentIndex].Tasks.Count; goalMatrix[agentIndex, goalIndex] = cost; } } // Add additional columns or rows if (numAgents > numGoals) { int maxValue = GetMax(goalMatrix, numAgents, numGoals) + 1; for (int dummyGoal = numGoals; dummyGoal < maxSize; dummyGoal++) { for (int i = 0; i < numAgents; i++) { // If we have more agents than goals, we need to add additional fake goals // Since goals are in columns, we are essentially adding a new column. goalMatrix[i, dummyGoal] = maxValue; } } } else if (numGoals > numAgents) { int maxValue = GetMax(goalMatrix, numAgents, numGoals) + 1; for (int dummyAngent = numAgents; dummyAngent < maxSize; dummyAngent++) { for (int i = 0; i < numGoals; i++) { // If we have more goals than agents, we need to add additional fake agents // Since goals are in columns, we are essentially adding a new row. goalMatrix[dummyAngent, i] = maxValue; } } } return(goalMatrix.FindAssignments()); }
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); }