// AI 2.0 #region Core planning static public CardAIPlanData ProducePlan(CardAI ai, CardAIPlanData data, bool allowTwoStages) { if (ai.intensity < 1) { Debug.LogError("[ERROR]0 level for AI will not produce any plans!, Increase calculation intensity to minimum 1!"); return(new CardAIPlanData()); } NetBattlefield bf = data.bf; int apNextTurn = bf.APNextTurn(ai.playerID); int leftAP = bf.LeftAPThisTurn(ai.playerID) + apNextTurn; float value = bf.GetValueByCloneSimulation(ai.iterations, ai.r); List <NetCard> cards = CardsWithinAPRange(ai, bf, ai.totalAP); if (cards == null) { return(new CardAIPlanData()); } List <NetSkill> supportSkills2 = new List <NetSkill>(); List <NetSkill> skills = SelectSkillsToCast(cards); if (skills == null || skills.Count == 0) { Debug.LogWarning("[!! AI] Avaliable skills to cast = 0"); return(new CardAIPlanData()); } List <NetSkill> skills2 = null; List <int> importantPos = null; #region detect potential two-cast suggestions if (allowTwoStages) { List <NetCard> cards2 = CardsWithinAPRange(ai, bf, ai.totalAP - 1); if (cards2 != null) { skills2 = SelectSkillsToCast(cards2); //two cast simulation would be considered only in case of at least two skills //being of value for that activity if (skills2 != null && skills2.Count > 1) { supportSkills2 = WillDoTwoCastSimulation(ai, data, skills2); if (supportSkills2.Count > 0) { importantPos = FindImportantPositions(ai, data, supportSkills2); } //expensive casts which exhausts available AP will be casted as single-casts //support skills will be casted as single-casts //cheap enough non-supports will be BASE for multi-casts //then supports skills will be casted on top skills2 = skills2.Except(supportSkills2).ToList(); skills = skills.Except(skills2).ToList(); } } } #endregion #region produce single-cast results List <CardAIPlanData> plans = ProduceCastPlans(ai, data, skills, 1); if (plans != null && plans.Count > 0) { plans.Sort(PlanSorter); plans = ProduceRefinePlans(ai, data, plans); } #endregion #region produce two-cast results. //TODO start by doing first layer of casts, with preferences if possible if (skills2 != null && skills2.Count > 0) { List <CardAIPlanData> plans2 = ProduceCastPlans(ai, data, skills2, 1); if (plans2 != null && plans2.Count > 0) { plans2.Sort(PlanSorter); plans2 = ProduceRefinePlans(ai, data, plans2); plans.AddRange(plans2); } //if supports are possible validate do another single turn pass for remaining skills. if (supportSkills2 != null && supportSkills2.Count > 0) { //register important positions if any to ensure they are preferred during position planning. ai.preferredStrategicPlaces = importantPos; //First Cast plans2 = ProduceCastPlans(ai, data, skills2, 1); if (plans2 != null && plans2.Count > 0) { plans2.Sort(PlanSorter); plans2 = ProduceRefinePlans(ai, data, plans2); //Second Cast (aka support cast) //Use first plan and select boosts which seems to be most efficient among available options. //this does one base cast and then one boost for each which account for: AxB List <CardAIPlanData> supportPlans = new List <CardAIPlanData>(); if (plans2 != null && plans2.Count > 0) { for (int i = 0; i < ai.supportCasts && i < plans2.Count; i++) { CardAIPlanData plan = plans2[i]; supportPlans.AddRange(ProduceCastPlans(ai, plan, supportSkills2, 1)); } } // select the best result of AxB to find the best theoretical result after two casts //do deeper planning for the base positioning and then cast support skills on the chosen plan to //acquire actual final value supportPlans.Sort(PlanSorter); if (supportPlans.Count > 0) { CardAIPlanData cpd = supportPlans[0]; CardAIPlanData planBase = plans2.Find(o => o.firstPlay.skill == cpd.firstPlay.skill); CardAIPlanData result = ProduceSingleRefination(ai, data, planBase); if (result.valid) { List <CardAIPlanData> results = ProduceCastPlans(ai, result, supportSkills2, 1); if (results != null && results.Count > 0) { results.Sort(PlanSorter); results = ProduceRefinePlans(ai, data, results); plans.AddRange(results); } } } } } } #endregion #region compare all results and select the best // all results are compared based on how much AP they use. // positive gain produced by all results are based on final result and final AP cost // GainedAdvantage / (2 + AP used) which results in: // 1/2 multiplier for 1 AP actions // 1/3 multiplier for 2 AP actions // 1/4 multiplier for 3+ AP actions // Single-casts would be considered as if they would use-up AvaliableAP amount // to ensure we do not consider them better even though they cannot provide any better results //if two-cast actions are available to some actions // aka: there is no point in having cheaper cost if AP cannot be later used. if (plans.Count == 0) { Debug.LogWarning("[!! AI] Available plans = 0"); return(new CardAIPlanData()); } CardAIPlanData selected = plans[0]; float selectedV = float.MinValue; for (int i = 0; i < plans.Count; i++) { CardAIPlanData cpd = plans[i]; int newLeftAP = cpd.bf.LeftAPThisTurn(ai.playerID) + apNextTurn; int apUsed = leftAP - newLeftAP; float v = cpd.value - value; v = v / (1f + apUsed); if (selectedV < v) { selected = plans[i]; selectedV = v; } } #endregion return(selected); }