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; }
/* * 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)); }
/* * Driver for search logic. * i) Check if the chosen state is solved, and if so update solution records accordiningly * ii) If not solved, check for preliminary qualities that would disqualify this sub problem from being searched * iii) If not disqualified, run a recursive division on the invading army into all possible invasion groupings * Each combination found will be added as a new leaf on the tree */ public void Search(SearchState chosenState, bool keepArmyTogether) { if (chosenState.IsSolved) { mNumLeafsCreated++; #region SolutionFound // Check if the solution is relevant depending on which optimizations are being used and solution quality bool updateSolution = false; bool recordSolution = false; int numStepsInInvasion = chosenState.Depth; if (mBestSolution == null) // havent found a solution yet { recordSolution = updateSolution = true; if (mIsOptimized) { // only accept solutions that occur in as many turns as the new solution mMaxDepthOfTree = numStepsInInvasion; } } else if (numStepsInInvasion < mBestSolutionDepth) // found a quicker invasion strategy { recordSolution = updateSolution = true; mBestSolution = null; // reset solution tracking if (mIsOptimized) { mNumSolutionsFound = 0; mMaxDepthOfTree = numStepsInInvasion; } } else if (mBestSolutionDepth == numStepsInInvasion) // occurs in the same number of turns as previous solutions { recordSolution = true; if (chosenState.Prob.InvadingArmy.CalculateArmyValue() > mBestSolutionArmyValue) { updateSolution = true; } } else // worse quality of solution { recordSolution = !mIsOptimized; } if (recordSolution) { mNumSolutionsFound++; } // Create and store new solution if (updateSolution) { mBestSolution = ToSolution(chosenState, mInitialArmy, mInitialNation); mBestSolutionDepth = numStepsInInvasion; mBestSolutionArmyValue = chosenState.Prob.InvadingArmy.CalculateArmyValue(); } #endregion } else // sub problem is not solved. Check if sub problem can be divided { if (mBestSolution == null) // record partial solutions until an actual solution is found { if (mBestPartialSolution == null) { mBestPartialSolution = chosenState; mBestPartialSolutionValue = chosenState.Prob.ProblemValue; } else { // only update partial solution if it was able to take more cities than the previous, regardless of better army value if (chosenState.Prob.NationBlueprint.NumCitiesRemaining < mBestPartialSolution.Prob.NationBlueprint.NumCitiesRemaining) { float probValue = chosenState.Prob.ProblemValue; if (probValue < mBestPartialSolutionValue) { mBestPartialSolution = chosenState; mBestPartialSolutionValue = probValue; } } } } // check for disqualifying features of invasion state bool validState = true; if (chosenState.ParentProblem != null) { if (chosenState.ParentProblem.StateValue == chosenState.StateValue) // army did not heal nor was a city taken { // do nothing, this state didn't progress the invasion validState = false; } } // took more turns to complete than our max turn limit if (mMaxDepthOfTree != -1 && mIsOptimized && chosenState.Depth >= mMaxDepthOfTree) { validState = false; } // valid subproblem. Add all new leafs possible from this state if (validState) { List <SearchState> subProblems = Fdiv(chosenState, keepArmyTogether); mNumLeafsCreated += subProblems.Count; foreach (SearchState subProblem in subProblems) { mOpenLeafs.AddLast(subProblem); } } else { mNumLeafsCreated++; } } }
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)); }