private void planNextTargetGsAi() { BuildingGS targetGs = modifyGameStateForPersonality(this.targetGameStates[currentTargetGsIndex[indexAi]], aiPersonality); Debug.Log(aiCurrentGameState); ltpPlan(aiCurrentGameState, targetGs); }
public void hillClimb() { BuildingGS initialGS = new BuildingGS(); initialGS.setStockpile(ResourceType.Gold, 500); initialGS.addResourcePerTick(ResourceType.Gold, 0); initialGS.setStockpile(ResourceType.Stone, 500); initialGS.addResourcePerTick(ResourceType.Stone, 0); initialGS.setStockpile(ResourceType.Wood, 500); initialGS.addResourcePerTick(ResourceType.Wood, 0); BuildingGS targetGS = new BuildingGS(); //targetGS.setStockpile(ResourceType.Gold, 200); targetGS.addResourcePerTick(ResourceType.Gold, 5); //targetGS.setStockpile(ResourceType.Stone, 200); targetGS.addResourcePerTick(ResourceType.Stone, 5); // targetGS.setStockpile(ResourceType.Wood, 200); targetGS.addResourcePerTick(ResourceType.Wood, 5); //targetGS.setStockpile(ResourceType.Silver, 200); targetGS.addResourcePerTick(ResourceType.Silver, 5); //targetGS.setStockpile(ResourceType.Coal, 200); targetGS.addResourcePerTick(ResourceType.Coal, 5); //targetGS.setStockpile(ResourceType.Iron, 200); targetGS.addResourcePerTick(ResourceType.Iron, 5); //targetGS.setStockpile(ResourceType.Steel, 200); targetGS.addResourcePerTick(ResourceType.Steel, 5); UnityEngine.Debug.Log(Stopwatch.Frequency); UnityEngine.Debug.Log("StartTime: " + Stopwatch.GetTimestamp()); ltp.plan(initialGS, targetGS, callback); }
public QGameState(BuildingGS gs, QGameState parent, Work transitionWork, int costToGetHere) { this.gameState = gs; this.parent = parent; this.transitionWork = transitionWork; this.costToGetHere = costToGetHere; }
public new IBuilding deepClone(BuildingGS newGS) { PositionDependentBuilding result = this.clone(newGS); result.currentWorkerCount = this.currentWorkerCount; return(result); }
public override void plan(BuildingGS initialGS, BuildingGS targetGS, Func <Stack <Work>, bool> callback) { PriorityQueue <QPriority, QGameState> queue = new PriorityQueue <QPriority, QGameState>(); StartCoroutine(LTPEngine.BuildPlan(initialGS, targetGS, queue, base.processResult)); StartCoroutine(base.waitForFinish(callback)); }
public QPriority(QGameState node, BuildingGS targetGS, HiddenRequirement hiddenReq) { this.hiddenReq = hiddenReq; this.currentNode = node; this.targetGS = targetGS; this.distanceRuler = LTPHelper.estematedRemainingDistance(node.gameState, targetGS); }
public void testLTP() { BuildingGS initialGS = new BuildingGS(); initialGS.setStockpile(ResourceType.Gold, 1000); initialGS.addResourcePerTick(ResourceType.Gold, 0); initialGS.setStockpile(ResourceType.Stone, 1000); initialGS.addResourcePerTick(ResourceType.Stone, 0); initialGS.setStockpile(ResourceType.Wood, 1000); initialGS.addResourcePerTick(ResourceType.Wood, 0); BuildingGS targetGS = new BuildingGS(); targetGS.setStockpile(ResourceType.Gold, 2000); targetGS.addResourcePerTick(ResourceType.Gold, 10); targetGS.setStockpile(ResourceType.Stone, 2000); targetGS.addResourcePerTick(ResourceType.Stone, 5); targetGS.setStockpile(ResourceType.Wood, 2000); targetGS.addResourcePerTick(ResourceType.Wood, 6); targetGS.setStockpile(ResourceType.Silver, 200); targetGS.addResourcePerTick(ResourceType.Silver, 9); ltp.plan(initialGS, targetGS, callback); }
// Copy constructor public BuildingGS(BuildingGS other) { // NOTE: This will deepClone all the buildings in the provided other (IE: The populations will be coppied) // TODO: Do we really need to deep copy all the buildings? this.buildings = new Dictionary <BuildingType, List <IBuilding> >(); this.resourceStockpile = new Dictionary <ResourceType, int>(); this.openSpots = new Dictionary <BuildingType, Queue <IBuilding> >(); this.resourceMap = new ResourceMap(); this._resourceChangePerTick = new Dictionary <ResourceType, int>(); // Buildings foreach (KeyValuePair <BuildingType, List <IBuilding> > kvp in other.buildings) { foreach (IBuilding otherBuilding in kvp.Value) { // Clone and add every building to this game state // TODO: Do we have to clone? If we have units assigned then probably yes... IBuilding buildingClone = otherBuilding.deepClone(); this.forceAddBuilding(buildingClone); } } // Stockpile foreach (KeyValuePair <ResourceType, int> kvp in other.resourceStockpile) { this.addToStockpile(kvp.Key, kvp.Value); } // Resources per tick will be handled by adding buildings this.resourcesSpent = other.resourcesSpent; }
public static BuildingGS waitGameState(BuildingGS gs, int time) { BuildingGS endGS = new BuildingGS(gs); endGS.timePasses(time); return(endGS); }
public static readonly int MAX_DEPTH = 500000; // 500K searches public static int unaquirableResources(BuildingGS currentGS, BuildingGS targetGS) { // Are there any resources that are impossible to get at this moment (ignoring workers) HashSet <ResourceType> unaquirableResources = targetGS.getCPTResourceTypes(); unaquirableResources.ExceptWith(currentGS.slotsForIncome()); return(unaquirableResources.Count); }
PositionDependentBuilding(BuildingType bt, Vector2Int position, BuildingGS gs, int extractionRatePerWorker ) : base(bt, position) { this.gameState = gs; this.extractionRatePerWorker = extractionRatePerWorker; }
// This function represents an edge public static QGameState waitTransition(QGameState qe, int time) { BuildingGS endGS = waitGameState(qe.gameState, time); int timeCost = qe.costToGetHere + time; Work newWork = new Work(EWork.Wait, BuildingType.NONE, time); return(new QGameState(endGS, qe, newWork, timeCost)); }
public void accordionLTP(BuildingGS initialGS, BuildingGS targetGS, Func <Stack <Work>, bool> callback, int memoryBound = 20000) { PriorityQueue <QPriority, QGameState> accordionQ = new AccordionPriorityQueue(memoryBound); StartCoroutine(LTPEngine.BuildPlan(initialGS, targetGS, accordionQ, base.processResult)); StartCoroutine(base.waitForFinish(callback)); }
private List <IResourceNode> getRelevantResourceNodes(BuildingGS gameState) { // How many un-claimed nodes that produce this.outputResources() exist in the map? List <IResourceNode> result = new List <IResourceNode>(); foreach (ResourceType rt in base.outputResources()) { result.AddRange(gameState.getResourceNodes(this.pos, rt)); } return(result); }
////////////////////////////////////////////////// public static QGameState buyWorker(QGameState qGS, BuildingType bt) { BuildingGS newGameState = waitGameState(qGS.gameState, 1); newGameState.buyAndAssignWorker(bt); int costToGetHere = qGS.costToGetHere + 3; Work newWork = new Work(EWork.BuyAndAssignWorker, bt, 3); return(new QGameState(newGameState, qGS, newWork, costToGetHere)); }
public static QGameState buyBuilding(QGameState qGS, IBuilding newBuilding) { BuildingGS newGameState = waitGameState(qGS.gameState, newBuilding.timeToBuild()); newGameState.buyBuilding(newBuilding); int costToGetHere = qGS.costToGetHere + newBuilding.timeToBuild(); Work newWork = new Work(EWork.BuildBuilding, newBuilding.getBuildingType(), newBuilding.timeToBuild()); return(new QGameState(newGameState, qGS, newWork, costToGetHere)); }
private void updateTargetDisplay(BuildingGS gameState, int indexActor) { Debug.Log("actor to update target display: " + indexActor); ResourceDisplayController rdc = meTargetDisplay; if (indexActor == indexAi) { rdc = aiTargetDisplay; } foreach (ResourceType rt in gameState.getAllResourceTypes()) { rdc.updateCountAndRPT(rt, gameState.getStockpile(rt), gameState.getChangePerTick(rt)); } }
private void setStartGameState() { BuildingGS initialGs = new BuildingGS(); initialGs.setStockpile(ResourceType.Gold, 1000); initialGs.addResourcePerTick(ResourceType.Gold, 0); initialGs.setStockpile(ResourceType.Stone, 1000); initialGs.addResourcePerTick(ResourceType.Stone, 0); initialGs.setStockpile(ResourceType.Wood, 1000); initialGs.addResourcePerTick(ResourceType.Wood, 0); GameController.gameState = initialGs; aiCurrentGameState = new BuildingGS(initialGs); }
public void memoryBoundedLTP(BuildingGS initialGS, BuildingGS targetGS, Func <Stack <Work>, bool> callback, int memoryBound = 100000) { /** * Find a path from the initialGS to the targetGS. The resultant path (translted into an ordered list of Work) * will be pumped into the provided callback once it's ready. */ PriorityQueue <QPriority, QGameState> memoryBoundedQ = new MemoryBoundedPriorityQueue(memoryBound); StartCoroutine(LTPEngine.BuildPlan(initialGS, targetGS, memoryBoundedQ, base.processResult)); StartCoroutine(base.waitForFinish(callback)); }
public override bool Equals(object obj) { /** * Two Game States are equal if they have * - same resource stockpile * - same resource income per tick */ // TODO: Consider number of buildings an important field? Maybe how much free land is remaining? BuildingGS otherGS = obj as BuildingGS; if (otherGS == null) { return(false); } // Different number of fields => fail if (this.resourceStockpile.Count != otherGS.resourceChangePerTick.Count || this.resourceChangePerTick.Count != otherGS.resourceChangePerTick.Count) { return(false); } // Check type of resources int otherResourceCount; foreach (KeyValuePair <ResourceType, int> kvp in this.resourceStockpile) { if (!otherGS.resourceStockpile.TryGetValue(kvp.Key, out otherResourceCount)) { // Try to access a given key in the other dictionary // If it fails, return false return(false); } if (kvp.Value != otherResourceCount) { // If the key is contained, but the stockpile count is different return(false); } if (this.resourceChangePerTick[kvp.Key] != otherGS.resourceChangePerTick[kvp.Key]) { // If the change per tick is different return(false); } } return(true); }
public static HashSet <QGameState> getNeighbors(QGameState qEntry) { // For a given game state return all valid edges out of it HashSet <QGameState> result = new HashSet <QGameState>(); BuildingGS gs = qEntry.gameState; // Branches related to workers if (gs.canBuyWorker()) { // If we have the resources to build a new worker foreach (BuildingType bt in gs.getOpenSlots()) { // One branch for every possible type of worker slot we can fill QGameState neighbor = QGameStateFactory.buyWorker(qEntry, bt); result.Add(neighbor); } } // The length of all the no-op edges we want to consider HashSet <int> waitTimes = new HashSet <int>() { 10 }; // Branches related to Buildings // TODO: Why build a building if we can't populate it with a worker? foreach (BuildingType bt in BuildingFactory.allBuildings) { // One branch for every new possible building IBuilding possibleBuilding = BuildingFactory.buildNew(bt, -1, -1); // TODO: do we care about pos when doing A*? if (gs.canBuyBuilding(possibleBuilding)) { // If we can build this building, then add a branch QGameState neighbor = QGameStateFactory.buyBuilding(qEntry, possibleBuilding); result.Add(neighbor); } } // Add in some no-op edges foreach (int waitTime in waitTimes) { result.Add(QGameStateFactory.waitTransition(qEntry, waitTime)); } return(result); }
private void setTargetGameStates() { BuildingGS targetGs = new BuildingGS(); targetGs.setStockpile(ResourceType.Gold, 2000); targetGs.addResourcePerTick(ResourceType.Gold, 10); targetGs.setStockpile(ResourceType.Stone, 2000); targetGs.addResourcePerTick(ResourceType.Stone, 5); targetGs.setStockpile(ResourceType.Wood, 2000); targetGs.addResourcePerTick(ResourceType.Wood, 6); targetGs.setStockpile(ResourceType.Silver, 200); targetGs.addResourcePerTick(ResourceType.Silver, 9); targetGameStates.Add(targetGs); BuildingGS targetGs2 = new BuildingGS(); targetGs2.setStockpile(ResourceType.Gold, 4000); targetGs2.addResourcePerTick(ResourceType.Gold, 10); targetGs2.setStockpile(ResourceType.Stone, 4000); targetGs2.addResourcePerTick(ResourceType.Stone, 5); targetGs2.setStockpile(ResourceType.Wood, 4000); targetGs2.addResourcePerTick(ResourceType.Wood, 6); targetGs2.setStockpile(ResourceType.Silver, 400); targetGs2.addResourcePerTick(ResourceType.Silver, 9); targetGameStates.Add(targetGs2); BuildingGS targetGs3 = new BuildingGS(); targetGs3.setStockpile(ResourceType.Gold, 8000); targetGs3.addResourcePerTick(ResourceType.Gold, 20); targetGs3.setStockpile(ResourceType.Stone, 8000); targetGs3.addResourcePerTick(ResourceType.Stone, 20); targetGs3.setStockpile(ResourceType.Wood, 8000); targetGs3.addResourcePerTick(ResourceType.Wood, 20); targetGs3.setStockpile(ResourceType.Silver, 800); targetGs3.addResourcePerTick(ResourceType.Silver, 20); targetGameStates.Add(targetGs3); Debug.Log("target game states size: " + targetGameStates.Count); for (int indexActor = 0; indexActor < noOfActors; indexActor++) { updateTargetDisplay(targetGameStates[currentTargetGsIndex[indexActor]], indexActor); } }
public void ID_LTP(BuildingGS initialGS, BuildingGS targetGS, Func <Stack <Work>, bool> callback, int initialDepth = 100) { /** * Find a path from the initialGS to the targetGS. The resultant path (translted into an ordered list of Work) * will be pumped into the provided callback once it's ready. */ // First, save these incase pathfinding fails and we need to recur with larger depth this.initialGS = initialGS; this.targetGS = targetGS; this.callback = callback; this.ID_Queue = new IDPriorityQueue(initialDepth); StartCoroutine(LTPEngine.BuildPlan(initialGS, targetGS, ID_Queue, id_processResult)); StartCoroutine(base.waitForFinish(callback)); }
private bool isTargetReached(int indexActor) { BuildingGS gsToCheck = GameController.gameState; if (indexActor == indexAi) { gsToCheck = aiCurrentGameState; } BuildingGS targetGs = targetGameStates[currentTargetGsIndex[indexActor]]; foreach (ResourceType rt in gsToCheck.getAllResourceTypes()) { if ((gsToCheck.getStockpile(rt) < targetGs.getStockpile(rt)) || (gsToCheck.getChangePerTick(rt) < targetGs.getChangePerTick(rt))) { return(false); } } Debug.Log("Target reached for : " + indexActor); return(true); }
public static RemainingDistance estematedRemainingDistance(BuildingGS currentGS, BuildingGS targetGS) { // How far away are we from the target? RemainingDistance result = new RemainingDistance(); // Compare numerical distance foreach (ResourceType rt in targetGS.getAllResourceTypes()) { int currentStockpile = currentGS.getStockpile(rt); int targetStockpile = targetGS.getStockpile(rt); int stockpileDelta = Mathf.Max(0, targetStockpile - currentStockpile); result.updateStockpileDelta(stockpileDelta); int currentResourcePerTick = currentGS.getChangePerTick(rt); int targetResourcePerTick = targetGS.getChangePerTick(rt); int rptDelta = Mathf.Max(0, targetResourcePerTick - currentResourcePerTick); result.updateCPTDelta(rt, rptDelta); int currentBestResourcePerTick = currentGS.getBestPossibleChangePerTick(rt); int bestPossibleRPTDelta = Mathf.Max(0, targetResourcePerTick - currentBestResourcePerTick); result.updateBestPossibleCPTDelta(bestPossibleRPTDelta); if (currentResourcePerTick <= 0) { result.addInfinity(); } else { float exactWaitTime = stockpileDelta / (float)currentResourcePerTick; int estWaitTime = (int)(exactWaitTime + 0.5f); result.updateWaitTime(estWaitTime); float bestPossibleWaitTime = stockpileDelta / (float)currentBestResourcePerTick; int estBestWaitTime = (int)(bestPossibleWaitTime + 0.5f); result.updateBestPossibleWaitTime(estBestWaitTime); } } return(result); }
private BuildingGS modifyGameStateForPersonality(BuildingGS targetGsHuman, AIPersonalityType aiPersonality) { switch (aiPersonality) { case AIPersonalityType.GoldDigger: targetGsHuman.setStockpile(ResourceType.Gold, targetGsHuman.getStockpile(ResourceType.Gold) * 2); targetGsHuman.addResourcePerTick(ResourceType.Gold, targetGsHuman.getChangePerTick(ResourceType.Gold) * 2); break; case AIPersonalityType.Pragmatist: foreach (ResourceType rt in targetGsHuman.getStockpileResourceTypes()) { targetGsHuman.setStockpile(rt, (int)(targetGsHuman.getStockpile(rt) * 1.3)); } break; case AIPersonalityType.Warrior: case AIPersonalityType.ScienceGeek: case AIPersonalityType.Conservative: default: break; } return(targetGsHuman); }
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 bool canBuild(BuildingGS gameState) { // Can this building be built in the provided game state? return(getRelevantResourceNodes(gameState).Count > 0); }
public new IBuilding simpleClone(BuildingGS newGS) { return(this.clone(newGS)); }
private PositionDependentBuilding clone(BuildingGS newGS) { return(new PositionDependentBuilding(this.bt, new Vector2Int(pos.x, pos.y), newGS, this.extractionRatePerWorker)); }