static CardAIPlanData CastSkillOnece(CardAI ai, NetSkill ns, NetCard nc, CardAIPlanData data, NetBattlefield bf, IEnumerator <List <int> > targetEnumerator) { //structure makes a new copy CardAIPlanData result = data; //using previous bf allows to utilize already cached data, as well as share caching with following casts. //later we will use new bf to ensure we do not override the same bf. NetBattlefield prevBf = data.bf; IList <int> targets = GetPlayCardTargets(ai.FriendlyTurn, ns, nc, prevBf); if (targets == null || targets.Count < 1) { return(new CardAIPlanData()); } if (targetEnumerator == null) { targetEnumerator = GetTargetEnumerator(ns, nc, targets, ai); if (targetEnumerator == null) { return(new CardAIPlanData()); } } targetEnumerator.MoveNext(); List <int> selectedTargets = targetEnumerator.Current; if (selectedTargets == null || selectedTargets.Count == 0) { return(new CardAIPlanData()); } List <int> secTargets = GetSecondaryPlayTargets(ns, nc, prevBf, selectedTargets, ai.r); FInt skillDelay = ns.GetSkillDelay(prevBf); int cost = prevBf.GetCardCastingCostFast(ns.GetOwner(prevBf).CardID); //add cost if something need to be casted next turn by AI var ncp = bf.GetPlayerByPlayerID(ai.playerID); if (cost > ncp.ApLeft) { skillDelay += 2; } //operate from now on its own bf result.bf = bf; NetQueueItem q; if (ns.IsCastingSpell()) { q = new NetQueueItem(bf, ns, new NetListInt(selectedTargets), new NetListInt(secTargets), skillDelay, -1); } else { q = new NetQueueItem(bf, ns, null, null, skillDelay, selectedTargets[0]); } //if ap cost were larger than current ap then we will result in negative ap. //because its just simulation we do not care for that now, as the sum of this and next turn ap //for estimating cost would be identical ncp.ApLeft -= nc.GetCastingCost(); bf.PlayCard(q, ai.r); float value = bf.GetValueByCloneSimulation(ai.iterations, ai.r); result.value = value; result.AddCardPlay(nc.CardID, ns.SkillInBattleID, q); result.SetEnumerator(targetEnumerator); result.valid = true; return(result); }
// 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); }