/* * Perform all calculations that occur during a sub armies invasion of a city. * Assault template records some calculated values used in the FDiv process to prevent redundant calculations * Any sub army in the invasion is assumed to be strong enough to capture the corresponding fort * Once the fort is captured, the army will have it's health reduced, and if any healers are left alive, will heal * Once each army has performed attack/healing, they will be merged into a new army. * Remaining forts are merged into a new nation * New army/nation objects are created to prevent deep/shallow copy issues */ public SearchState Ftrans(SearchState initialState, InvasionWave transition) { List <ArmyBlueprint> armiesAfterAttack = new List <ArmyBlueprint>(); List <Fortification> captured = new List <Fortification>(); for (int subArmyIdx = 0; subArmyIdx < transition.Wave.Count; ++subArmyIdx) { AssaultTemplate assaultTemplate = transition.Wave[subArmyIdx]; // run assault simulation. Capture fort, deal damage to invaders, and heal up after if possible ArmyBlueprint afterAssault = assaultTemplate.AssaultFortification(); armiesAfterAttack.Add(afterAssault); // was there an unneccessary step in keeping units in reserve. Was not able to heal and could have contributed to the taking of a fort if (assaultTemplate.ToAssault.IsPlaceHolderFort && assaultTemplate.Attackers.CalculateArmyValue() == afterAssault.CalculateArmyValue()) { // assert false; return(null); } if (!assaultTemplate.ToAssault.IsPlaceHolderFort) // fort used to simulate units held in reserve { captured.Add(assaultTemplate.ToAssault); } } return(new SearchState(ArmyBlueprint.MergeSubArmies(armiesAfterAttack), new NationBlueprint(initialState.Prob.NationBlueprint, captured), initialState.Depth + 1, initialState, transition)); }
IEnumerator AnimateInvasion(InvasionSolution solution) { yield return(new WaitForFixedUpdate()); AnimationNavigationParent.SetActive(false); SimulationButtonParent.gameObject.SetActive(true); ToggleAutoContinue(true); ContinueButton.interactable = false; mIsWaitingOnContinue = false; float subArmyAnimationDuration = mAnimationDuration * AnimationSpeed; float assaultHealthDuration = mAnimationDuration * AnimationSpeed; float reformAnimationDuration = mAnimationDuration * AnimationSpeed; List <InvasionWave> bestSolution = solution.InvasionOrder; for (int i = 0; i < bestSolution.Count; ++i) { WaveText.text = "WAVE " + (i + 1); List <AssaultTemplate> attackWave = bestSolution[i].Wave; Debug.Assert(DefaultArmyNumber >= attackWave.Count); List <ArmyBlueprint> subArmies = new List <ArmyBlueprint>(); foreach (var wave in attackWave) { subArmies.Add(wave.Attackers); } FormSubArmies(subArmies, subArmyAnimationDuration); yield return(_WaitForInputAfterAnimation(subArmyAnimationDuration * 1.5f)); // display "Attack wave #" int subArmyIdx = 0; foreach (AssaultTemplate attack in attackWave) { ArmyBlueprint afterAttack = attack.AssaultFortification(false); mArmyControllers[subArmyIdx].UpdateHealth(afterAttack, assaultHealthDuration); if (!attack.ToAssault.IsPlaceHolderFort) { NationController.DestroyFort(attack.ToAssault, assaultHealthDuration); yield return(_WaitForInputAfterAnimation(assaultHealthDuration * 1.5f)); } afterAttack.Heal(); mArmyControllers[subArmyIdx].UpdateHealth(afterAttack, assaultHealthDuration); yield return(_WaitForInputAfterAnimation(assaultHealthDuration * 1.5f)); subArmyIdx++; } MergeSubArmies(reformAnimationDuration); NationController.UpgradeForts(reformAnimationDuration); yield return(_WaitForInputAfterAnimation(reformAnimationDuration * 1.5f)); } WaveText.text = "Invasion Complete!"; yield return(new WaitForSeconds(reformAnimationDuration * 1.5f)); }
public InvasionSolution(ArmyBlueprint initialArmy, NationBlueprint initialNation, ArmyBlueprint finalArmy, NationBlueprint finalNation, List <InvasionWave> invasionOrder, bool isCompleteSolution) { InvasionOrder = invasionOrder; InitialArmy = initialArmy; InitialNation = initialNation; FinalArmy = finalArmy; FinalNation = finalNation; IsCompleteSolution = isCompleteSolution; }
public void BeginSimulation() { Debug.Assert(mSearchComplete); InvasionInputParent.gameObject.SetActive(false); InvasionAnimationParent.gameObject.SetActive(false); InvasionResultsParent.SetActive(true); mInitialInvaders = new ArmyBlueprint(SoldierCounter.InvaderCount, HealerCounter.InvaderCount, ArcherCounter.InvaderCount); mInitialNation = new NationBlueprint(NationLayoutController.GetNationLayout()); StartCoroutine(_BeginSimulation()); }
public SearchState(ArmyBlueprint army, NationBlueprint defender, int depth, SearchState parent, InvasionWave fromParent) { if (depth == 0) { sStateNumber = 0; } mStateNumber = sStateNumber++; Depth = depth; Prob = new Prob(army, defender); ParentProblem = parent; TransitionFromParent = fromParent != null ? new InvasionWave(fromParent.Wave) : null; }
public void Cache(ArmyBlueprint invasionGroup) { if (!mPriorityCache.ContainsKey(invasionGroup.Size)) { HashSet <ArmyBlueprint> toAdd = new HashSet <ArmyBlueprint>(); toAdd.Add(invasionGroup); mPriorityCache.Add(invasionGroup.Size, toAdd); } else { mPriorityCache[invasionGroup.Size].Add(invasionGroup); } }
public bool IsCached(ArmyBlueprint invasionGroup) { if (mPriorityCache.ContainsKey(invasionGroup.Size)) { foreach (ArmyBlueprint skeleton in mPriorityCache[invasionGroup.Size]) { if (invasionGroup.Equals(skeleton)) { return(true); } } } return(false); }
/* * Driver for recursive division of invading army into all possible sub army invasions * Does future checks to see if there will ever be a valid solution coming from this leaf. * example: Checks if the army will ever be able to capture the strongest city */ public List <SearchState> Fdiv(SearchState state, bool keepArmyTogether) { //Debug.Assert(!state.IsSolved, "Attempting to apply Div on a solved problem"); // Get all attackable forts List <Fortification> fortifications = new List <Fortification>(state.Prob.NationBlueprint.GetBorderCities()); ArmyBlueprint invadingArmy = state.Prob.InvadingArmy; fortifications.Add(new Fortification()); // add a place holder fort for units to be held in reserve List <SearchState> problemDivisions = new List <SearchState>(); // TODO: pick most difficult city to capture. Temp patch just check the first Fortification hardestCity = fortifications[0]; // Since damage doesn't increase over time, if the army can't take the city now it will never be able to if (!mIsOptimized && invadingArmy.Damage < hardestCity.Defense) { return(problemDivisions); } // If the army is strong enough to take the city but doesn't have enough health, // and it either can't heal, or it's max health even after healing wont be enough to take the city, // abandon this search else if (!mIsOptimized && invadingArmy.Health <= hardestCity.Offense && !(invadingArmy.CanHeal && invadingArmy.MaxHealth > hardestCity.Offense)) { return(problemDivisions); } // come up with all possible transitions through recursing through all army combinations else { List <InvasionWave> armyDistributions = new List <InvasionWave>(); RecurseFdiv( new ArmyBlueprint(invadingArmy.Soldiers, invadingArmy.Healers, invadingArmy.Archers), 0, fortifications, new InvasionWave(), armyDistributions, keepArmyTogether); foreach (InvasionWave transition in armyDistributions) { SearchState nextState = Ftrans(state, transition); // if the transition proved to be wasteful, discard if (nextState != null) { problemDivisions.Add(nextState); } } } return(problemDivisions); }
// Helper function to convert the invasion stack into a solution public static InvasionSolution ToSolution(SearchState state, ArmyBlueprint initialArmy, NationBlueprint initialNation) { SearchState currentState = state; List <InvasionWave> invasionOrder = new List <InvasionWave>(); while (currentState.TransitionFromParent != null) { invasionOrder.Add(currentState.TransitionFromParent); currentState = currentState.ParentProblem; } invasionOrder.Reverse(); return(new InvasionSolution(initialArmy, initialNation, state.Prob.InvadingArmy, state.Prob.NationBlueprint, invasionOrder, state.IsSolved)); }
public void UpdateHealth(ArmyBlueprint afterAssault, float overSeconds) { for (int i = 0; i < afterAssault.Archers.Count; ++i) { InvaderController controller = mInvaderControllers[InvaderType.Archer][i]; controller.UpdateHealth(afterAssault.Archers[i], overSeconds); } for (int i = 0; i < afterAssault.Healers.Count; ++i) { mInvaderControllers[InvaderType.Healer][i].UpdateHealth(afterAssault.Healers[i], overSeconds); } for (int i = 0; i < afterAssault.Soldiers.Count; ++i) { mInvaderControllers[InvaderType.Soldier][i].UpdateHealth(afterAssault.Soldiers[i], overSeconds); } }
// Should only be called once when army is first being created public void Initialize(ArmyBlueprint army) { Initialize(); foreach (ushort archer in army.Archers) { InvaderController controller = Instantiate(InvaderPrefab, transform.parent); controller.Initialize(archer, InvaderType.Archer); mInvaderControllers[InvaderType.Archer].Add(controller); } foreach (ushort healer in army.Healers) { InvaderController controller = Instantiate(InvaderPrefab, transform.parent); controller.Initialize(healer, InvaderType.Healer); mInvaderControllers[InvaderType.Healer].Add(controller); } foreach (ushort soldier in army.Soldiers) { InvaderController controller = Instantiate(InvaderPrefab, transform.parent); controller.Initialize(soldier, InvaderType.Soldier); mInvaderControllers[InvaderType.Soldier].Add(controller); } LayoutArmy(); // create at position in army }
public Prob(ArmyBlueprint army, NationBlueprint defender) { InvadingArmy = army; NationBlueprint = defender; }
/* * Recurses through all possible army combinations into sub armies to attack each border city provided. * Checks if the sub army will be able to capture the assigned fort, and if not discards the combination * Sub Army combinations are cached to prevent situations such as 3 soldiers with the same health being split into armies of size 1 and being treated as distinct * Caching system uses a similarity value defined in Common Constants. * TODO: the similarity factor is too simplistic and should be scaled based on percent health, and how close to death the unit is, rather than a fixed value */ private static void RecurseFdiv(ArmyBlueprint armyToSubdivide, int currentGroup, List <Fortification> toCapture, InvasionWave currentWave, List <InvasionWave> possibleInvasionWaves, bool keepArmyTogether) { if (currentGroup == toCapture.Count - 1) // last fort to capture { if (!armyToSubdivide.IsEmpty) // units left over to subdivide { // put all units in last invasion group // ASSUMPTION: Last fort is always a place holder fort currentWave.Wave.Add(AssaultTemplate.GetAssualtTemplate(toCapture[currentGroup], armyToSubdivide)); } // add invasion wave to possible solutions possibleInvasionWaves.Add(currentWave); } else if (armyToSubdivide.IsEmpty) // No units left to allocate to the remaining sub armies { // add invasion wave up to this point to return list possibleInvasionWaves.Add(currentWave); } else { int armySize = armyToSubdivide.Size; // num remaining units to subdivide long numDifferentCombinations = (long)Mathf.Pow(2, armySize); IteratorCache invasionCache = new IteratorCache(); // iterate through all possible bitwise combinations of the subarmy // Combinations begin with all units being added as it orrients the leaf list to have a higher chance of // a successful solution and arriving at optimization values quicker. // Doing in reverse order (ie combinationNumber = 0, combinationNumber < NumDifferentCombinations) will // bias all units to be in reserve and take longer to arrive at a solution for (long combinationNumber = numDifferentCombinations - 1; combinationNumber >= 0; combinationNumber--) { ArmyBlueprint subArmy = new ArmyBlueprint(); ArmyBlueprint remainingArmy = new ArmyBlueprint(); #region Create SubArmy from combination number BitArray combinationArray = new BitArray(System.BitConverter.GetBytes(combinationNumber)); for (int j = 0; j < armySize; ++j) { if (j < armyToSubdivide.Soldiers.Count) // bit represents a soldier { if (combinationArray[j]) // put soldier in sub army { subArmy.Soldiers.Add(armyToSubdivide.Soldiers[j]); } else // keep soldier in reserve { remainingArmy.Soldiers.Add(armyToSubdivide.Soldiers[j]); } } else if (j < armyToSubdivide.Soldiers.Count + armyToSubdivide.Healers.Count) // bit is a healer { if (combinationArray[j]) // put healer in sub army { subArmy.Healers.Add(armyToSubdivide.Healers[j - armyToSubdivide.Soldiers.Count]); } else // keep healer in reserve { remainingArmy.Healers.Add(armyToSubdivide.Healers[j - armyToSubdivide.Soldiers.Count]); } } else // bit represents an archer { if (combinationArray[j]) // put archer in sub army { subArmy.Archers.Add(armyToSubdivide.Archers[j - armyToSubdivide.Soldiers.Count - armyToSubdivide.Healers.Count]); } else // keep archer in reserve { remainingArmy.Archers.Add(armyToSubdivide.Archers[j - armyToSubdivide.Soldiers.Count - armyToSubdivide.Healers.Count]); } } } #endregion // Only consider iteration if it can capture the city it was tasked to take if (combinationNumber == 0) // Empty sub army. { InvasionWave recursiveTransitionCopy = new InvasionWave(currentWave.Wave); RecurseFdiv(remainingArmy, currentGroup + 1, toCapture, recursiveTransitionCopy, possibleInvasionWaves, keepArmyTogether); } else if (!mIsOptimized || !invasionCache.IsCached(subArmy)) { // check is assualt will be successful and record values in assault template AssaultTemplate assaultTemplate = AssaultTemplate.GetAssualtTemplate(toCapture[currentGroup], subArmy); if (assaultTemplate != null /* is valid assault */) { invasionCache.Cache(subArmy); InvasionWave recursiveTransitionCopy = new InvasionWave(currentWave.Wave); recursiveTransitionCopy.Wave.Add(assaultTemplate); // recurse with remaining units not included in this sub army RecurseFdiv(remainingArmy, currentGroup + 1, toCapture, recursiveTransitionCopy, possibleInvasionWaves, keepArmyTogether); } // else discard combination } // Used for linear invasion strategies to prevent unneccessary combination checks. // jump to putting the army in reserve if it wasn't assigned to a fort if (keepArmyTogether && combinationNumber == numDifferentCombinations - 1) // first sub army created { combinationNumber = 1; // will become 0 next loop iteration and second army option will be empty } } } }
public SearchResults SearchForSolutions(ArmyBlueprint attackers, NationBlueprint defenders, bool optimize) { mInitialArmy = attackers; mInitialNation = defenders; mIsOptimized = true; /* = optimize; Replace after data collection*/ // Initialize start variables and start invasion search SearchState initialState = new SearchState(attackers, defenders, 0, null, null); mOpenLeafs.Clear(); // orrient the nations layout relative to the army. TODO: allow for dynamic invasion direction initialState.Prob.NationBlueprint.BeginInvasion(Vector2.right); // Do the default search keeping the units together mOpenLeafs.AddLast(initialState); mPruneSolutions = true; // only care about the best solution with army grouped up. Only record mMaxDepthOfTree = mBestSolutionDepth = -1; // max depth = -1 until a solution is found creating a limit to depth // Search for solutions with linear invasion strategy while (mOpenLeafs.Count > 0) { SearchState pop = mOpenLeafs.Last.Value; mOpenLeafs.RemoveLast(); Search(pop, true); } // record default invasion stats InvasionSolution linearSolution = null; if (mBestSolution == null) // no solution was found { // record the best result possible linearSolution = ToSolution(mBestPartialSolution, mInitialArmy, mInitialNation); } else { linearSolution = mBestSolution; } int bestLinearDepth = linearSolution != null ? mBestSolutionDepth : -1; // update the max depth of the optimized search to be the depth of the default time mMaxDepthOfTree = bestLinearDepth; //Debug.Log("Best Case Group Scenario took " + mMaxDepthOfTree + " turns to complete, needing " + (mMaxDepthOfTree - initialState.Prob.NationBlueprint.NumCitiesRemaining) + " turns to heal"); // reset search variables for the optimized search mPruneSolutions = optimize; mOpenLeafs.Clear(); mBestSolution = null; mBestPartialSolution = null; mOpenLeafs.AddLast(initialState); // search for parallel attack strategy solutions while (mOpenLeafs.Count > 0) { SearchState pop = mOpenLeafs.Last.Value; mOpenLeafs.RemoveLast(); Search(pop, false); } // Record optimized Solution InvasionSolution parallelSolution = null; if (mBestSolution == null) { parallelSolution = ToSolution(mBestPartialSolution, mInitialArmy, mInitialNation); } else { parallelSolution = mBestSolution; } int bestOptimizedDepth = parallelSolution != null ? mBestSolutionDepth : -1; return(new SearchResults(mNumLeafsCreated, mNumSolutionsFound, linearSolution, parallelSolution)); }