Esempio n. 1
0
        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);
        }
Esempio n. 2
0
        // 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);
        }