private static readonly int NODES_CHECKED_PER_YIELD = 150; // Check n nodes every frame public static IEnumerator BuildPlan(BuildingGS initialGS, BuildingGS targetGS, PriorityQueue <QPriority, QGameState> priorityQueue, Func <QGameState, bool> finishCallback) { int nodesChecked = 0; // Initialize data structures Dictionary <int, int> bestCostToGetHere = new Dictionary <int, int>(); HiddenRequirement hiddenReq = new HiddenRequirement(); // NOTE: Don't add the initial state to the bestCostToGetHere, that will be done automatically Work initialWork = new Work(EWork.EMPTY, BuildingType.NONE, 0); QGameState firstGS = new QGameState(initialGS, null, initialWork, 0); QPriority firstPriority = new QPriority(firstGS, targetGS, hiddenReq); priorityQueue.Enqueue(firstPriority, firstGS); int totalChecks = 0; while (priorityQueue.Count > 0) { nodesChecked++; if (nodesChecked >= NODES_CHECKED_PER_YIELD) { // TODO: There may be an off by 1 error, but not a major problem nodesChecked = 0; yield return(null); } totalChecks++; KeyValuePair <QPriority, QGameState> kvp = priorityQueue.Dequeue(); QGameState qe = kvp.Value; if (false) { Debug.Log("==============================================================================================="); Debug.Log("Cost to get here: " + qe.costToGetHere + " + " + kvp.Key.maxWaitTime); Debug.Log("incoming work: " + qe.transitionWork.workType + " " + qe.transitionWork.buildingType); Debug.Log(" + infinities : " + kvp.Key.numberOfInfinities); Debug.Log(" + unaquiriable : " + kvp.Key.unaquirableResourceCount); //Debug.Log(" + estTotalDist : " + kvp.Key.estTotalDist); Debug.Log(" + BestTotalDist : " + kvp.Key.bestMaxWaitTime); Debug.Log(" + totalCPTDelta : " + kvp.Key.totalCPTDelta); Debug.Log(" + preRecDelta : " + kvp.Key.reccomendedPreRecDelta); //Debug.Log(" + best CPTDelta : " + heuristic.bestCPTDelta); Debug.Log(" + fudge factor : " + kvp.Key.totalResourcesSpent); Debug.Log("iron cpt: " + qe.gameState.getChangePerTick(ResourceType.Iron)); Debug.Log("coal cpt: " + qe.gameState.getChangePerTick(ResourceType.Coal)); Debug.Log("Steel cpt: " + qe.gameState.getChangePerTick(ResourceType.Steel)); Debug.Log("building count: " + qe.gameState.totalBuildingCount()); Debug.Log("worker count: " + qe.gameState.totalWorkerCount()); } // Early exit conditions if (LTPHelper.estematedRemainingDistance(qe.gameState, targetGS).atTarget()) { // If we are 0 distance away from the target game state // IE: If we have found the target game state finishCallback(qe); yield break; } else if (totalChecks > LTPHelper.MAX_DEPTH) { finishCallback(null); yield break; } if (bestCostToGetHere.ContainsKey(qe.gameState.GetHashCode()) && bestCostToGetHere[qe.gameState.GetHashCode()] <= qe.costToGetHere) { // If we've already explored this game state // AND if some other path is to this game state is cheeper continue; } else { // Else, this Queue Entry represents a cheeper path to get to this node bestCostToGetHere[qe.gameState.GetHashCode()] = qe.costToGetHere; } foreach (QGameState neighbor in LTPHelper.getNeighbors(qe)) { // The neighbor already has an updated gamestate, a job and an updated cost if (bestCostToGetHere.ContainsKey(neighbor.gameState.GetHashCode()) && bestCostToGetHere[neighbor.gameState.GetHashCode()] <= neighbor.costToGetHere) { // If we already have a better way to get to the neighbor continue; } QPriority heuristic = new QPriority(neighbor, targetGS, hiddenReq); if (false) { Debug.Log("********************"); Debug.Log("** Adding neighbor: "); Debug.Log("** transition work: " + neighbor.transitionWork.workType + " " + neighbor.transitionWork.buildingType); Debug.Log(" + infinities : " + heuristic.numberOfInfinities); Debug.Log(" + unaquiriable : " + heuristic.unaquirableResourceCount); //Debug.Log(" + estTotalDist : " + kvp.Key.estTotalDist); Debug.Log(" + BestTotalDist : " + kvp.Key.bestMaxWaitTime); Debug.Log(" + totalCPTDelta : " + heuristic.totalCPTDelta); Debug.Log(" + preRecDelta : " + heuristic.reccomendedPreRecDelta); //Debug.Log(" + best CPTDelta : " + heuristic.bestCPTDelta); Debug.Log(" + fudge factor : " + heuristic.totalResourcesSpent); Debug.Log("iron cpt: " + neighbor.gameState.getChangePerTick(ResourceType.Iron)); Debug.Log("coal cpt: " + neighbor.gameState.getChangePerTick(ResourceType.Coal)); Debug.Log("Steel cpt: " + neighbor.gameState.getChangePerTick(ResourceType.Steel)); Debug.Log("building count: " + neighbor.gameState.totalBuildingCount()); Debug.Log("worker count: " + neighbor.gameState.totalWorkerCount()); } priorityQueue.Enqueue(heuristic, neighbor); } // End foreach neighbor } // End while queue is NOT empty finishCallback(null); yield break; }
public int CompareTo(object obj) { QPriority other = obj as QPriority; if (other == null) { return(-1); } // this thing is smaller than a non QPriority obj /** * -- Most important reasons -- * fewer infinities should win * access to more resources should win * shorter remaining distance should win * faster velocity should win * less resources spent should win * -- least important reasons -- * */ if (this.numberOfInfinities != other.numberOfInfinities) { return(this.numberOfInfinities - other.numberOfInfinities); } if (this.unaquirableResourceCount != other.unaquirableResourceCount) { return(this.unaquirableResourceCount - other.unaquirableResourceCount); } if (this.totalCPTDelta != other.totalCPTDelta) { return(this.totalCPTDelta - other.totalCPTDelta); } // Figure out which one has less "hidden" pre-recs that are unsatisfied if (this.estTotalDist != other.estTotalDist) { return(this.estTotalDist - other.estTotalDist); } // When nothing seems to be different, a assigning a worker operation always wins if (this.assignWorker != other.assignWorker) { if (this.assignWorker) { return(-1); } else { return(1); } } if (this.bestMaxWaitTime != other.bestMaxWaitTime) { return(this.bestMaxWaitTime - other.bestMaxWaitTime); } if (reccomendedPreRecDelta != other.reccomendedPreRecDelta) { return(reccomendedPreRecDelta - other.reccomendedPreRecDelta); } if (this.bestCPTDelta != other.bestCPTDelta) { return(this.bestCPTDelta - other.bestCPTDelta); } int myTotalBuildingCount = this.currentNode.gameState.totalBuildingCount(); int otherTotalBuildingCount = other.currentNode.gameState.totalBuildingCount(); if (myTotalBuildingCount != otherTotalBuildingCount) { return(myTotalBuildingCount - otherTotalBuildingCount); } // When nothing seems to be different, a wait operation always wins if (this.wait != other.wait) { if (this.wait) { return(-1); } else { return(1); } } // All things equal go for the cheaper option return(this.totalResourcesSpent - other.totalResourcesSpent); /* * Examples: * 10 vs 7 * 10 - 7 = 3 => 2nd entry goes first * * 7 vs 10 * 7 - 10 = -3 => 1st entry goes first */ }