ai civic code review

keldath

LivE LonG AnD PrOsPeR
Joined
Dec 20, 2005
Messages
7,558
Location
israel
hi

civ4 dear modders,

Though 7 is here, i still mod and code. ever lasting fun.

now, i have written a robust code for AI civics for my new/old government civic system.

base rules:

3 civic options.
1 parent
2 dependent civic options for the parent.

era convert limit : each era there are n times that these can be converted per Civic option
convert reduction works:
parent -> to a new parent -> -1 reduction:
when converting a parent: the dependent civic switch as well to the best choice -> no cost reduction for the dependent.

dependent civic -> convert to another dependent civic in the same parent :
limit reduction of -1 for the dependent civic option.


ai rules:
AI_DOCIVICS now split to :
AI_DOCIVICS - runs over regular civics, no dependency or parent.
AI_DODEPENDENTCIVICS - runs for only dependent.


AI_DODEPENDENTCIVICS code:
code is similar to AI_DOCIVICS with a key different.
instead of evaluating 1 civic,
the group of the parent and its 2 dependents are scored together.
in other words, AI_DODEPENDENTCIVICS runs only for the parent.
the best dependents available are found with a function for each parent.
and grouped together in a enum map variable.

xml tags in the parent civic type holds the dependent civics.
civicoption xml has a tag that marks that option as a parent type civic option. or a dependent civic option,

im attaching the robust code here , split into functions.
if anyone has any comments or suggestions, i would be very happy.

i addressed all civic calls in playerai and player,
stuff like votes , trade and diplo (most of them disabled for dependent civics for now).

AI_DODEPENDENTCIVICS:
Code:
CivicMap CvPlayerAI::AI_doDependentCivics()
{
    //bool checks  -> code removed to anothe fuunc
    // FAssert(AI_getCivicTimer() == 0); // Disabled by K-Mod

    CivicMap aeBestCivic;
    getCivics(aeBestCivic);
    int aiCurrentGroupValue = 0;
    CivicOptionTypes parentCivicOption = NO_CIVICOPTION;
    FOR_EACH_ENUM(CivicOption)
    {
        /* doto Civics parent - ignore normal civics AND CHILDREN*/
        //childrens value will be processed later in the function
        if (GC.getInfo(eLoopCivicOption).getParentCivicOption() > 1)
        {
            //the value wont be per civic, but for the whole group (unlike ai_docivics())
            //the iBestValue below is compared to this value , both must be the same - e.g group of the civics dependent and parent
            aiCurrentGroupValue = aiCurrentGroupValue + AI_civicValue(aeBestCivic.get(eLoopCivicOption));
            if (GC.getInfo(eLoopCivicOption).getParentCivicOption() == 2)
                parentCivicOption = eLoopCivicOption; //later usage, safe loops
        }
        //doto debug before suggested changes
        if (eLoopCivicOption != NO_CIVICOPTION)
            CvWString civicName = GC.getInfo(aeBestCivic.get(eLoopCivicOption)).getDescription();
    }
    
    int iAnarchyLength = 0;
    bool bWillSwitch;
    bool bWantSwitch;
    bool bFirstPass = true;
    do
    {
        bWillSwitch = false;
        bWantSwitch = false;
        
        //find best parent and best of its childs    
        int iBestGroupValue = -1;
        CivicMap bestGroupCivic = AI_bestGroupCivics(parentCivicOption, &iBestGroupValue);
        FOR_EACH_ENUM(CivicOption)
        {
            CivicTypes eNewCivic = bestGroupCivic.get(eLoopCivicOption);
            if (GC.getInfo(eLoopCivicOption).getParentCivicOption() < 1)
                continue;
            if (eNewCivic == NO_CIVIC)
                continue;

            //doto debug before suggested changes
            CvWString civicName = GC.getInfo(bestGroupCivic.get(eLoopCivicOption)).getDescription();

            /*  advc.001r: Same thing as under karadoc's "temporary switch" comment
                in the loop below */
            CivicTypes eOtherCivic = aeBestCivic.get(eLoopCivicOption);
            if (eOtherCivic == eNewCivic)
                continue;
            aeBestCivic.set(eLoopCivicOption, eNewCivic); // advc.001r
            int iTestAnarchy = getCivicAnarchyLength(aeBestCivic);
            aeBestCivic.set(eLoopCivicOption, eOtherCivic); // advc.001r
            /*  using ~30 percent as a rough estimate of revolution cost, and
                a low threshold regardless of anarchy just for a bit of inertia.
                reduced threshold if we are already going to have a revolution. */
            int iThreshold = 5;
            if (iTestAnarchy > iAnarchyLength)
            {
                iThreshold = (!bFirstPass || bWantSwitch ? 20 : 30);
                // <advc.132b>
                TeamTypes eMaster = getMasterTeam();
                if (getTeam() != eMaster && GET_TEAM(eMaster).isHuman())
                    iThreshold += 15;
                // </advc.132b>
            }
            if (100*iBestGroupValue > (100 + /* advc.131: */ (iBestGroupValue >= 0 ? 1 : -1) *
                iThreshold) * aiCurrentGroupValue)
            {
                FAssert(aeBestCivic.get(eLoopCivicOption) != NO_CIVIC);
                if (gPlayerLogLevel > 0) logBBAI("    %S decides to switch to %S (value: %d vs %d%S)", getCivilizationDescription(0), GC.getInfo(eNewCivic).getDescription(0), iBestGroupValue, iBestGroupValue, bFirstPass?"" :", on recheck");
                iAnarchyLength = iTestAnarchy;
                aeBestCivic.set(eLoopCivicOption, eNewCivic);
                aiCurrentGroupValue = iBestGroupValue;
                bWillSwitch = true;
            }
            else
            {
                if (100 * iBestGroupValue > /* advc.131: */ (iBestGroupValue >= 0 ? 120 : 80)
                    * aiCurrentGroupValue)
                {
                    bWantSwitch = true;
                }
            }
        }
        bFirstPass = false;
    } while (bWillSwitch && bWantSwitch);
    // Recheck, just in case we can switch another good civic without adding more anarchy.


    //doto debug before suggested changes
    FOR_EACH_ENUM(CivicOption)
    {
        if (eLoopCivicOption != NO_CIVICOPTION)
            CvWString civicName = GC.getInfo(aeBestCivic.get(eLoopCivicOption)).getDescription();
    }


    /*    finally, if our current research would give us a new civic,
        consider waiting for that. */
    if (iAnarchyLength > 0 && bWillSwitch)
    {
        TechTypes const eResearch = getCurrentResearch();
        if (eResearch != NO_TECH)
        {    // <advc.004x>
            int const iResearchTurns = getResearchTurnsLeft(eResearch, true);
            if (iResearchTurns >= 0 && // </advc.004x>
                iResearchTurns < 2 * CIVIC_CHANGE_DELAY / 3)
            {
                //doto civic dependency doto115
                //origical check if a civic is being reseached,
                //if so it will consider holding any revoluion to wait to get the tack

                FOR_EACH_ENUM2(Civic, eCivic)
                {

                    CvCivicInfo const& kCivic = GC.getInfo(eCivic);
                    /* doto Civics parent - only checking a parent here, after we will check for the children*/
                    if (GC.getInfo(kCivic.getCivicOptionType()).getParentCivicOption() != 2)
                        continue;

                    int iBestChildrenGroupValue = -1;//for later
                    CivicMap bestGroupCivic = AI_bestValueParentsChildCivics(eCivic, kCivic.getCivicOptionType(), &iBestChildrenGroupValue);

                    //doto debug, whos the best
                    FOR_EACH_ENUM(CivicOption)
                    {
                        if (eLoopCivicOption != NO_CIVICOPTION)
                            CvWString civicName = GC.getInfo(bestGroupCivic.get(eLoopCivicOption)).getDescription();
                    }


                    //doto civic dependency doto115;will check if there are parents or children is on the way.
                    //if the parent is not selected, we can ignore the cap.
                    /*
                        let see if we should wair to finish a reserach of a parent instead of converting now.
                        first we should see we arenot learning the tech the civic needs.
                        if we can do the civic now, so no need to wait
                        and if we cant do the civic now and we are not learning a tech that will reset the cap that is probabaly denieing the convert
                        we should not wait for the tech in these cases.
                        but,
                        the parent is not enough, before we can dismiss that we can do the parent, we need to make sure that we can convert to all the dependent children per dependent civics
                        the parent has.
                    */
                    bool isThereCapLimitandtheresearchwontresetit = (getCurrentEra() < GC.getInfo(eResearch).getEra()) && (getGovermentConversionCounter(GC.getInfo(eCivic).getCivicOptionType()) == 0);
                    if (kCivic.getTechPrereq() != eResearch || canDoCivics(eCivic) || !canDoCivics(eCivic) && !isThereCapLimitandtheresearchwontresetit)
                    {
                        int countHowManyChildren = 0;
                        int countHowManyChildrenfitTheTest = 0;
                        FOR_EACH_ENUM(CivicOption)
                        {
                            bool canDoAtLeastOneChild = false;
                            if (GC.getInfo(eLoopCivicOption).getParentCivicOption() == 1)
                            {
                                countHowManyChildren = countHowManyChildren + 1;
                                CivicTypes eChildCivic = bestGroupCivic.get(eLoopCivicOption);
                                CvCivicInfo const& kChild = GC.getInfo(eChildCivic);
                                //if the current parent is selected, we mustnot ignore the cap
                                /*
                                     assuming that we can convert to the parent,
                                     lets see that its not worth while for us to wait for a tech that will aloow us to convert to atleast
                                     1 of the child civics.
                                     cause if the suggested best group has 1 child that we are now researching for his tech,
                                     we should wait for that tech.
                                     3 conditions:
                                     if we are not researching the childs tech, we can continue
                                     if we can do the child civic and we have enough cap and the child paraent is already selected
                                     if the parent is not selected and we can do the child civic regardless of the cap
                                     if we are not learning a tech that will reset the cap and we can do the civic where the parent is selected and we can ignore the cap
                                     if we cant do the civic and the the target parent is not selected (we dont care for a cap in that case
                                     then we can say we can shift to the child.
                                */
                                bool isThereCapLimitandtheresearchwontresetit = getCurrentEra() < GC.getInfo(eResearch).getEra() && getGovermentConversionCounter(GC.getInfo(eChildCivic).getCivicOptionType()) == 0;
                                if (kChild.getTechPrereq() != eResearch || canDoCivics(eChildCivic) && eCivic == getCivics(kCivic.getCivicOptionType()) ||
                                    canDoCivics(eChildCivic, true) && eCivic != getCivics(kCivic.getCivicOptionType())
                                    || !isThereCapLimitandtheresearchwontresetit && canDoCivics(eChildCivic, false) && eCivic == getCivics(kCivic.getCivicOptionType())
                                    )
                                {
                                    countHowManyChildrenfitTheTest = countHowManyChildrenfitTheTest + 1;
                                }
                            }
                        }
                        if (countHowManyChildren == countHowManyChildrenfitTheTest)
                            continue;
                    }

                    //lets see what is the current best parent civic to be selected (or already selected)
                    CivicMap eOtherGroupCivic;
                    FOR_EACH_ENUM(CivicOption)
                    {
                        if (GC.getInfo(eLoopCivicOption).getParentCivicOption() > 0)
                        {
                            eOtherGroupCivic.set(eLoopCivicOption, aeBestCivic.get(eLoopCivicOption));
                        }
                    }
                    // advciv comment -> temporary switch just to test the anarchy length
                    //in order to test the anarchy below, 
                    //this will see what the anarchy would be if we change to the parent and children group
                    //that we dont have the tech for.
                    aeBestCivic.set(kCivic.getCivicOptionType(), eCivic);
                    FOR_EACH_ENUM(CivicOption)
                    {
                        if (GC.getInfo(eLoopCivicOption).getParentCivicOption() == 1)
                        {
                            aeBestCivic.set(eLoopCivicOption, bestGroupCivic.get(eLoopCivicOption));
                        }
                    }

                    if (getCivicAnarchyLength(aeBestCivic) <= iAnarchyLength)
                    {
                        /*    if the anarchy length would be the same,
                            consider waiting for the new civic. */

                        int iNewPottentialtoWaitParentValue = AI_civicValue(eCivic);
                        iNewPottentialtoWaitParentValue = iNewPottentialtoWaitParentValue + iBestChildrenGroupValue;

                        if (100 * iNewPottentialtoWaitParentValue >
                            (102 + 2 * iResearchTurns) *
                            aiCurrentGroupValue &&
                            iNewPottentialtoWaitParentValue > 0) // advc.131: Better to be safe
                        {
                            if (gPlayerLogLevel > 0) logBBAI("    %S delays revolution to wait for %S (value: %d vs %d)", getCivilizationDescription(0), kCivic.getDescription(0), iNewPottentialtoWaitParentValue, aiCurrentGroupValue);
                            AI_setCivicTimer(iResearchTurns * 2 / 3);
                            CivicMap aeCurrentCivics;
                            getCivics(aeCurrentCivics);
                            return aeCurrentCivics;
                        }
                    }

                    //doto civic dependency doto115 - upate all new/smae civics.
                    aeBestCivic.set(kCivic.getCivicOptionType(), eOtherGroupCivic.get(kCivic.getCivicOptionType()));//parent found or org civicorg
                    FOR_EACH_ENUM(CivicOption)
                    {
                        if (GC.getInfo(eLoopCivicOption).getParentCivicOption() == 1)
                        {
                            aeBestCivic.set(eLoopCivicOption, eOtherGroupCivic.get(eLoopCivicOption));
                            //doto civic dependency doto115 add the 
                        }
                    }

                }
            }
        } // <advc.131>
        if(iAnarchyLength > 0)
        {
            if(getGold() < (iAnarchyLength + std::min(0, getStrikeTurns() - 1))
                * -getGoldPerTurn())
            {
                CivicMap aeCurrentCivics;
                getCivics(aeCurrentCivics);
                return aeCurrentCivics;
            }
        } // </advc.131>
    }


    FOR_EACH_ENUM(CivicOption)
    {
        CvWString civicName = GC.getInfo(aeBestCivic.get(eLoopCivicOption)).getDescription();
    }

//doto civic dependency doto115
    return aeBestCivic;
}


AI_bestGroupCivics: finds the best group of parent and dependents
Code:
CivicMap CvPlayerAI::AI_bestGroupCivics(CivicOptionTypes parentCivicOption, int* iBestGroupValue)
{    
    /*
         this will calculate and find a group of civics:
         the best scored->
         parent + children civics
         for a given parent , it will take the best available child for each civicoption
         there can be 2 child civics of the same civicoption, so we keep the higher one.

    */
    
    //lets count how many civic parents are there.
    int howManyParentCivics = 0;
    int howManyCivics = GC.getNumCivicInfos();
    int howManyCivicsOption = GC.getNumCivicOptionInfos();
    int bestTotalGroupvalue = 0;
    
    //cache it in the file cache -> i think its better than passing maps around
    CivicMap aeBestLoopGroupCivic;
    getCivics(aeBestLoopGroupCivic);
    CivicMap aeBestCurrentGroupCivic;
    getCivics(aeBestCurrentGroupCivic);
    
    for (int ij = 0; ij < howManyCivics; ij++)
    {
        int currentTotalValue = 0;
        CvCivicInfo const& kloopParentCivic = GC.getInfo((CivicTypes)ij);
        
        if (!canDoCivics((CivicTypes)ij))
            continue;

        aeBestLoopGroupCivic.set(parentCivicOption, (CivicTypes)ij); //add in the sent parent
        int parentValue = AI_civicValue((CivicTypes)ij);//parent valu;e

        if (kloopParentCivic.getCivicOptionType() == parentCivicOption)
        {
            int numOfChildrenForThisParent = kloopParentCivic.getNumParentCivicsChildren();
            CivicTypes bestChildForThisOption = NO_CIVIC;
            int bestValueForBestChildrenPerCivic = 0;
            for (int o = 0; o < howManyCivicsOption; o++)
            {
                //o for option
                int bestChildValuePerOption = 0;
                for (int c = 0; c < numOfChildrenForThisParent; c++)
                {
                    //c for child
                    CivicTypes eChildCivic = kloopParentCivic.getParentCivicsChildren(c);

                    //if the target parent is not selected, we can ignore the child cap limit
                    //but if the parent is selected, the cap kicks in and if its 0, we cant use this child.
                    bool isSeelectedCurrentParent = (CivicTypes)ij == getCivics(parentCivicOption);
                    if (!canDoCivics(eChildCivic, true) && !isSeelectedCurrentParent
                        || (getGovermentConversionCounter(GC.getInfo(eChildCivic).getCivicOptionType()) == 0 && isSeelectedCurrentParent)
                            )
                    {
                        continue;
                    }

                    if ((CivicOptionTypes)o == GC.getInfo(eChildCivic).getCivicOptionType())
                    {
                        int cValue = AI_civicValue(eChildCivic);
                            if (cValue > bestChildValuePerOption)
                            {
                                bestChildValuePerOption = cValue;
                                aeBestLoopGroupCivic.set((CivicOptionTypes)o, eChildCivic);
                            }
                    }
                }
                bestValueForBestChildrenPerCivic = bestValueForBestChildrenPerCivic + bestChildValuePerOption;
            }

            currentTotalValue = bestValueForBestChildrenPerCivic + parentValue;

            //need to make sure that the looped parent, got candidate children for all of the civicoptions
            //he has. we wanna avoid having 1 child from parent a and one child from parent b.
            //we need both children to be of parent a
            //so isThereACivicOptionWithNoChildren will be true if at least one of the children of a the parent
            //was found suitable in the previous step
            bool isThereACivicOptionWithNoChildren = false;
            for (int ii = 0; ii < numOfChildrenForThisParent; ii++)
            {
                CivicTypes eChild = kloopParentCivic.getParentCivicsChildren(ii);
                if (eChild == aeBestLoopGroupCivic.get(GC.getInfo(eChild).getCivicOptionType()))
                {
                    isThereACivicOptionWithNoChildren = true;
                }
            }
            if (!isThereACivicOptionWithNoChildren)
                continue; //an incomeplete group was found, pass.

            if     (currentTotalValue > bestTotalGroupvalue)
            {
                //we got a new group with a higher score. sp update the new high score and update the
                // parent and children we wanna switch to
                bestTotalGroupvalue = currentTotalValue;
                for (int t = 0; t < howManyCivicsOption; t++)
                {
                    aeBestCurrentGroupCivic.set((CivicOptionTypes)t, aeBestLoopGroupCivic.get((CivicOptionTypes)t));
                }
            }
        }
        else
            continue;
    }
    
    if (iBestGroupValue)
        *iBestGroupValue = bestTotalGroupvalue;
    return aeBestCurrentGroupCivic;
}


AI_bestValueParentsChildCivics : finds the best dependent civics for a specific parent (no checks for candocivics)
Code:
CivicMap CvPlayerAI::AI_bestValueParentsChildCivics(CivicTypes parentCivic, CivicOptionTypes parentOption, int* iBestChildrenGroupValue)
{    
    /*
         this will calculate and find a group of civics:
         the best scored->
         parent + children civics
         for a given parent , it will take the best available child for each civicoption
         there can be 2 child civics of the same civicoption, so we keep the higher one.

        note, there is no check to see if the children candocivics -> this is for the wair for reseach partof the ai_dodependentcivics
    */
    
    //lets count how many civic parents are there.
    int howManyParentCivics = 0;
    int howManyCivics = GC.getNumCivicInfos();
    int howManyCivicsOption = GC.getNumCivicOptionInfos();
    int bestTotalGroupvalue = 0;
    
    //cache it in the file cache -> i think its better than passing maps around
    CivicMap aeBestChildrenForTheParentCivics;
    getCivics(aeBestChildrenForTheParentCivics);
    CvCivicInfo const& kParentCivic = GC.getInfo(parentCivic);
    FAssert(GC.getInfo(kParentCivic.getCivicOptionType()).getParentCivicOption() == 2)//make sure...
    int numOfChildrenForThisParent = kParentCivic.getNumParentCivicsChildren();
    int bestValueForTheSpecificChildren = 0;
    
    FOR_EACH_ENUM(CivicOption)
    {
        if (eLoopCivicOption != NO_CIVICOPTION)
            CvWString civicName = GC.getInfo(aeBestChildrenForTheParentCivics.get(eLoopCivicOption)).getDescription();
    }

    for (int o = 0; o < howManyCivicsOption; o++)
    {
        int bestChildValuePerOption = 0;
        for (int c = 0; c < numOfChildrenForThisParent; c++)
        {
            //c for child
            CivicTypes eChildCivic = kParentCivic.getParentCivicsChildren(c);
            
            //if the target parent is not selected, we can ignore the child cap limit
            //but if the parent is selected, the cap kicks in and if its 0, we cant use this child.
            //if (canDoCivics(eChildCivic, true) && parentCivic != getCivics(parentOption))
            //    continue;
            
            if ((CivicOptionTypes)o == GC.getInfo(eChildCivic).getCivicOptionType())
            {
                int cValue = AI_civicValue(eChildCivic);
                if (cValue > bestChildValuePerOption)
                {
                    bestChildValuePerOption = cValue;
                    aeBestChildrenForTheParentCivics.set((CivicOptionTypes)o, eChildCivic);
                }
            }                
        }
        bestValueForTheSpecificChildren = bestValueForTheSpecificChildren + bestChildValuePerOption;
    }
    
    if (iBestChildrenGroupValue)
        *iBestChildrenGroupValue = bestValueForTheSpecificChildren;

    FOR_EACH_ENUM(CivicOption)
    {
        if (eLoopCivicOption != NO_CIVICOPTION)
            CvWString civicName = GC.getInfo(aeBestChildrenForTheParentCivics.get(eLoopCivicOption)).getDescription();
    }

    return aeBestChildrenForTheParentCivics;
}

AI_doRevolution: cut away part from the ai_docivics that merges the 2 enum maps that finds to which civics to convert.
Code:
void CvPlayerAI::AI_doRevolution(CivicMap aeNormalCivics, CivicMap aeDependentCivics)
{
    CivicMap aeAllBestCivics;
    getCivics(aeAllBestCivics);
    FOR_EACH_ENUM(CivicOption)
    {
        //doto debug
        if (eLoopCivicOption != NO_CIVICOPTION)
            CvWString civicName = GC.getInfo(aeDependentCivics.get(eLoopCivicOption)).getDescription();
        
        if (GC.getInfo(eLoopCivicOption).getParentCivicOption() > 0)
            aeAllBestCivics.set(eLoopCivicOption, aeDependentCivics.get(eLoopCivicOption));
        else if (GC.getInfo(eLoopCivicOption).getParentCivicOption() < 1)
            aeAllBestCivics.set(eLoopCivicOption, aeNormalCivics.get(eLoopCivicOption));
    }
    if (canRevolution(aeAllBestCivics))
    {
        revolution(aeAllBestCivics);
        AI_setCivicTimer(getMaxAnarchyTurns() != 0 ? CIVIC_CHANGE_DELAY :
                GC.getDefineINT(CvGlobals::MIN_REVOLUTION_TURNS) * 2);
    }
}
 
Back
Top Bottom