Brad Wardell's site for talking about the customization of Windows.
Published on January 14, 2015 By Frogboy In GalCiv III Dev Journals

Well the party is almost over for you humans.

I’ve never hidden the fact that I consider human beings playing my AI a necessary evil.  I need your money to fund my AI work. Smile

This time around, I’ve got a talented young developer working with me.  He makes me feel old. Very. Very old. 

A lot has changed over the past 20 years.  It is still technically C++ but it’s all very different from what it used to be.

Example code

//GalCiv III:  Example code tags a planet as a potential target.

if(atWar)
        {
            FixedDecimal militaryPower = GetInterface<IStat>(*it)->GetStat(StatTypes::FactionPower, StatUsages::Actual);

            CBasicGameObject* pClosestPlanet = NULL;
            ULONG closestDistance = ULONG_MAX;

            const ObjectPtrList& ownedPlanets = GetInterface<IGC3Player>(*it)->GetOwnedPlanets();
            for(auto planetIt = ownedPlanets.Begin(); planetIt != ownedPlanets.End(); ++planetIt)
            {
                TilePair planetPos = GetInterface<IBasicGameObject>(*planetIt)->GetTilePosition().tile;
               
                BOOL isPlanetInRange = CGalaxy::GetInstance()->GetRangeSystem().IsWithinFleetRangeValue(m_pPlayer, planetPos.posX, planetPos.posY, rangeToUse);
                if(isPlanetInRange)
                {
                    ULONG planetDistance = CWorldSpace::HexDistance(capitalPos, planetPos);
                    if(planetDistance < closestDistance)
                    {
                        closestDistance = planetDistance;
                        pClosestPlanet = (*planetIt);
                    }
                }
            }

            if(pClosestPlanet && militaryPower < weakestPower)
            {
                weakestPower = militaryPower;
                pWeakestPlayer = (*it);
                pTargetPlanet = pClosestPlanet;
            }
        }

 

 

  //GalCiv II: I wrote this to control a transport

//*****************************************************************************
//* AIFindTransportDestination
//* Purpose:
//* Find a planet for a transport to go to
//*****************************************************************************
BOOL classCivilization::AIFindTransportDestination(PclassStarShip pShip)
{
 
   if(pShip->IsInFleet() && !pShip->IsFleet()) return false;


   //Preliminaries: Wait for escorts
   if(pShip->HasDestination() && pShip->IsOrbiting() == FALSE && !pShip->GetAttack() && !AIHostilesInSector(pShip->GetSectorID()) &&  g_pGalaxy->GetDistanceFromFriendlyPlanet(QueryID(),pShip->GetTileX(),pShip->GetTileY())<SECTOR_SIZE && pShip->ulTransportWaitTurns<5)
   {
      pShip->CancelDestination();
      pShip->SetMovesLeft(0);
      return false;
   }

   //Preliminaries: Wait for escorts
   if(pShip->IsOrbiting() == FALSE && !pShip->GetAttack() && !AIHostilesInSector(pShip->GetSectorID()) &&  g_pGalaxy->GetDistanceFromFriendlyPlanet(QueryID(),pShip->GetTileX(),pShip->GetTileY())<SECTOR_SIZE && pShip->ulTransportWaitTurns<5)
   {
      pShip->ulTransportWaitTurns++;
      return false;
   }

   //Step #0: Let's see if there are hostiles near by..
   if(AIHostilesInSector(pShip->GetSectorID()) && pShip->GetAttack()<10 && !pShip->IsOrbiting())
   {
      PclassStarShip pEnemy = pShip->FindClosestLocalEnemyShip(0);

      if (pEnemy && pShip->GetTurnsAway(pEnemy->GetTileX(),pEnemy->GetTileY()) < 2 && pEnemy->GetAttack() > pShip->GetAttack())
      {
         pShip->AITakeEvasiveAction(pEnemy->GetTileX(),pEnemy->GetTileY());
         return TRUE;
      }

   }

   //Step #1: Let's see if there's another undefended planet in sector.
   PclassPlanet pPlanet = AIFindUndefendedEnemyPlanet(pShip->GetSectorID());

 
   if(pPlanet && (!AIHostilesInSector(pPlanet->GetSectorID()) || pShip->GetAttack()>8))
   {
      pShip->CancelDestination();
      pShip->pPlanetDestination = pPlanet;
      return true;
   }

   //Step #2: Let's see where they're currently going.
   pPlanet = pShip->pPlanetDestination;
   if(pPlanet && ulIntelligence<40)
   {
      if(pPlanet->IsDefended() == FALSE && GetRelationsWith(pPlanet->GetOwner()) == AT_WAR)
         return true;
   }

   if(pPlanet)
   {
      if(pPlanet->IsDefended() == TRUE || (AIHostilesInSector(pPlanet->GetSectorID()) && !pShip->GetAttack()) )
         pShip->CancelDestination();
   
   }

 

   //Step #3: Let's look at our primary sector focus and use that.
   if(this->AIulSectorFocuses[0] != INVALID_SECTOR_FOCUS)
   {
        ULONG ulSectorsToFocusOn = this->GetNumSectorFocuses();   
        for (ULONG ulIndex = 0; ulIndex<ulSectorsToFocusOn; ulIndex++)
        {
           ULONG ulSectorID = this->AIulSectorFocuses[ulIndex];
           pPlanet = AIFindUndefendedEnemyPlanet(ulSectorID);
           BOOL bHostilesInSector = false;
           if(pPlanet)
           {
               bHostilesInSector = this->AIHostilesInSector(pPlanet->GetSectorID());


               if(this->ulIntelligence<50 || pShip->GetAttack()>5 )
                  bHostilesInSector = false;
           }
           if(pPlanet && pPlanet->ulAIInvadersAssigned[QueryID()]<4 && !bHostilesInSector )
           {
              pShip->CancelDestination();
              pShip->pPlanetDestination = pPlanet;
              return true;
           }
        }
   }

   //Step #4: Let's see if there are other planets to deal with.
   //Note that we won't go over to sectors with undefended planets
   //IF there are enemy ships in there
   pPlanet = pShip->FindClosestUndefendedEnemyPlanet(0);
   if(pPlanet && (AIHostilesInSector(pPlanet->GetSectorID() == FALSE || pShip->GetAttack()>0)))
   {
         pShip->CancelDestination();
         pShip->pPlanetDestination = pPlanet;
         return true;
   }

   //Step #4: We're AT the Rally Point
   PclassRallyPoint pOnRallyPoint = this->FindNearestRallyPoint(pShip->GetTileX(),pShip->GetTileY());
   if(pOnRallyPoint && pOnRallyPoint->GetDistanceInTiles(pShip) == 0)
   {
      pShip->GoHome();
   }
  
   //Step #5: What if there are hostiles here? Let's retreat
   if(this->AIHostilesInSector(pShip->GetSectorID() && !pShip->GetAttack()))
   {
      pShip->GoHome();
   }
 
   return false;

}


Comments (Page 1)
3 Pages1 2 3 
on Jan 14, 2015

Well this is probably the only time I actually look at a bunch of lines. I noticed there were no else statements. I didn't think c could do that. 

on Jan 14, 2015

DELETE

on Jan 14, 2015

Larsenex

I am not a person savvy enough to understand coding. 

 

Does this mean that coding today is faster, better more compact than what you did before?

 

I am very please to know you are taking a personal hand in the AI Brad. Your creative hand will shine through I have no doubt.

on Jan 14, 2015

Brad,

If i read the code correctly and there are no intended call side effects then your code is rather inefficient.

Example code


//GalCiv III:  Example code tags a planet as a potential target.

if(atWar)
        {
            FixedDecimal militaryPower = GetInterface(*it)->GetStat(StatTypes::FactionPower, StatUsages::Actual);

            CBasicGameObject* pClosestPlanet = NULL;
            ULONG closestDistance = ULONG_MAX;

            const ObjectPtrList& ownedPlanets = GetInterface(*it)->GetOwnedPlanets();
            for(auto planetIt = ownedPlanets.Begin(); planetIt != ownedPlanets.End(); ++planetIt)
            {
                TilePair planetPos = GetInterface(*planetIt)->GetTilePosition().tile;
               
                BOOL isPlanetInRange = CGalaxy::GetInstance()->GetRangeSystem().IsWithinFleetRangeValue(m_pPlayer, planetPos.posX, planetPos.posY, rangeToUse);
                if(isPlanetInRange)
                {
                    ULONG planetDistance = CWorldSpace::HexDistance(capitalPos, planetPos);
                    if(planetDistance < closestDistance)
                    {
                        closestDistance = planetDistance;
                        pClosestPlanet = (*planetIt);
                    }
                }
            }

            if(pClosestPlanet && militaryPower < weakestPower)
            {
                weakestPower = militaryPower;
                pWeakestPlayer = (*it);
                pTargetPlanet = pClosestPlanet;
            }
        }

A slightly better version might be:

Example code


//GalCiv III:  Example code tags a planet as a potential target.

if(atWar) 
        { 
            FixedDecimal militaryPower = GetInterface(*it)->GetStat(StatTypes::FactionPower, StatUsages::Actual);

            if (militaryPower < weakestPower) 
           {
                CBasicGameObject* pClosestPlanet = NULL; 

                ULONG closestDistance = ULONG_MAX;

                const ObjectPtrList& ownedPlanets = GetInterface(*it)->GetOwnedPlanets(); 
                for(auto planetIt = ownedPlanets.Begin(); planetIt != ownedPlanets.End(); ++  planetIt) 
                { 
                    TilePair planetPos = GetInterface(*planetIt)->GetTilePosition().tile; 
                
                    BOOL isPlanetInRange = CGalaxy::GetInstance()->GetRangeSystem().IsWithinFleetRangeValue(m_pPlayer, planetPos.posX, planetPos.posY, rangeToUse);
                    if(isPlanetInRange) 
                    { 
                        ULONG planetDistance = CWorldSpace::HexDistance(capitalPos, planetPos); 
                        if(planetDistance < closestDistance) 
                        { 
                            closestDistance = planetDistance; 
                            pClosestPlanet = (*planetIt); 
                        } 
                    } 
                }

                if (pClosestPlanet)
                {
                   weakestPower = militaryPower;
                   pWeakestPlayer = (*it);
                   pTargetPlanet = pClosestPlanet;
                }
            }
         }

This changes nothing to the logic, but prevents doing a lot of cpu cycles when you were going to discard the result because of an early stage known condition ("(militaryPower < weakestPower)").

I do not intend this as a criticism, but i thought that anything that can lighten the burden on the cpu would be beneficial to the game. Also since you are quite open about a lot of things i assumed you would not mind.

on Jan 14, 2015

You don't use C++ template meta-programming to encode behaviors as template base classes, and then compose them at compile-time as a list of behaviors?  That's just about the most beautiful (and insanely powerful) usage of C++.  It's the one thing C++ can do that Java/C# can't.

The interface programming is promising.  Does this mean you'll eventually allow pluggable modules, say from 3rd-party developers, as long as they adhere to your published interfaces?

The rigid flowchart-like decision tree ... doesn't knock my socks off.  Yet.

Prefer just-in-time local variable declarations, with 0 gap between where they're declared and where they're first used.  They don't have to go at the top any more.  Semantic gap is more of a drawback than table-of-contents is a benefit.

Prefer `T const` instead of `const T` everywhere.  Josuttis and Vandevoorde (both C++ compiler writers, both on the C++ language committee) put that as section 1.1 in their book on C++ template meta-programming.  I independently discovered it the hard way myself (and wrote my Appendix entry on it two years before they published).  Write template code of sufficient complexity, and you'll see.

Anyways, thanks for the glimpse of a code snippet.  It's always a brave thing to show off your stuff.

on Jan 14, 2015


Well the party is almost over for you humans.
I’ve never hidden the fact that I consider human beings playing my AI a necessary evil.  I need your money to fund my AI work. Smile
This time around, I’ve got a talented young developer working with me.  He makes me feel old. Very. Very old. 
A lot has changed over the past 20 years.  It is still technically C++ but it’s all very different from what it used to be.
Example code

//GalCiv III:  Example code tags a planet as a potential target.
if(atWar)
        {
            FixedDecimal militaryPower = GetInterface<IStat>(*it)->GetStat(StatTypes::FactionPower, StatUsages::Actual);
            CBasicGameObject* pClosestPlanet = NULL;
            ULONG closestDistance = ULONG_MAX;
            const ObjectPtrList& ownedPlanets = GetInterface<IGC3Player>(*it)->GetOwnedPlanets();
            for(auto planetIt = ownedPlanets.Begin(); planetIt != ownedPlanets.End(); ++planetIt)
            {
                TilePair planetPos = GetInterface<IBasicGameObject>(*planetIt)->GetTilePosition().tile;
               
                BOOL isPlanetInRange = CGalaxy::GetInstance()->GetRangeSystem().IsWithinFleetRangeValue(m_pPlayer, planetPos.posX, planetPos.posY, rangeToUse);
                if(isPlanetInRange)
                {
                    ULONG planetDistance = CWorldSpace::HexDistance(capitalPos, planetPos);
                    if(planetDistance < closestDistance)
                    {
                        closestDistance = planetDistance;
                        pClosestPlanet = (*planetIt);
                    }
                }
            }
            if(pClosestPlanet && militaryPower < weakestPower)
            {
                weakestPower = militaryPower;
                pWeakestPlayer = (*it);
                pTargetPlanet = pClosestPlanet;
            }
        }
 
 
  //GalCiv II: I wrote this to control a transport
//*****************************************************************************
//* AIFindTransportDestination
//* Purpose:
//* Find a planet for a transport to go to
//*****************************************************************************
BOOL classCivilization::AIFindTransportDestination(PclassStarShip pShip)
{
 
   if(pShip->IsInFleet() && !pShip->IsFleet()) return false;

   //Preliminaries: Wait for escorts
   if(pShip->HasDestination() && pShip->IsOrbiting() == FALSE && !pShip->GetAttack() && !AIHostilesInSector(pShip->GetSectorID()) &&  g_pGalaxy->GetDistanceFromFriendlyPlanet(QueryID(),pShip->GetTileX(),pShip->GetTileY())<SECTOR_SIZE && pShip->ulTransportWaitTurns<5)
   {
      pShip->CancelDestination();
      pShip->SetMovesLeft(0);
      return false;
   }
   //Preliminaries: Wait for escorts
   if(pShip->IsOrbiting() == FALSE && !pShip->GetAttack() && !AIHostilesInSector(pShip->GetSectorID()) &&  g_pGalaxy->GetDistanceFromFriendlyPlanet(QueryID(),pShip->GetTileX(),pShip->GetTileY())<SECTOR_SIZE && pShip->ulTransportWaitTurns<5)
   {
      pShip->ulTransportWaitTurns++;
      return false;
   }
   //Step #0: Let's see if there are hostiles near by..
   if(AIHostilesInSector(pShip->GetSectorID()) && pShip->GetAttack()<10 && !pShip->IsOrbiting())
   {
      PclassStarShip pEnemy = pShip->FindClosestLocalEnemyShip(0);
      if (pEnemy && pShip->GetTurnsAway(pEnemy->GetTileX(),pEnemy->GetTileY()) < 2 && pEnemy->GetAttack() > pShip->GetAttack())
      {
         pShip->AITakeEvasiveAction(pEnemy->GetTileX(),pEnemy->GetTileY());
         return TRUE;
      }
   }
   //Step #1: Let's see if there's another undefended planet in sector.
   PclassPlanet pPlanet = AIFindUndefendedEnemyPlanet(pShip->GetSectorID());
 
   if(pPlanet && (!AIHostilesInSector(pPlanet->GetSectorID()) || pShip->GetAttack()>8))
   {
      pShip->CancelDestination();
      pShip->pPlanetDestination = pPlanet;
      return true;
   }
   //Step #2: Let's see where they're currently going.
   pPlanet = pShip->pPlanetDestination;
   if(pPlanet && ulIntelligence<40)
   {
      if(pPlanet->IsDefended() == FALSE && GetRelationsWith(pPlanet->GetOwner()) == AT_WAR)
         return true;
   }
   if(pPlanet)
   {
      if(pPlanet->IsDefended() == TRUE || (AIHostilesInSector(pPlanet->GetSectorID()) && !pShip->GetAttack()) )
         pShip->CancelDestination();
   
   }
 
   //Step #3: Let's look at our primary sector focus and use that.
   if(this->AIulSectorFocuses[0] != INVALID_SECTOR_FOCUS)
   {
        ULONG ulSectorsToFocusOn = this->GetNumSectorFocuses();   
        for (ULONG ulIndex = 0; ulIndex<ulSectorsToFocusOn; ulIndex++)
        {
           ULONG ulSectorID = this->AIulSectorFocuses[ulIndex];
           pPlanet = AIFindUndefendedEnemyPlanet(ulSectorID);
           BOOL bHostilesInSector = false;
           if(pPlanet)
           {
               bHostilesInSector = this->AIHostilesInSector(pPlanet->GetSectorID());

               if(this->ulIntelligence<50 || pShip->GetAttack()>5 )
                  bHostilesInSector = false;
           }
           if(pPlanet && pPlanet->ulAIInvadersAssigned[QueryID()]<4 && !bHostilesInSector )
           {
              pShip->CancelDestination();
              pShip->pPlanetDestination = pPlanet;
              return true;
           }
        }
   }
   //Step #4: Let's see if there are other planets to deal with.
   //Note that we won't go over to sectors with undefended planets
   //IF there are enemy ships in there
   pPlanet = pShip->FindClosestUndefendedEnemyPlanet(0);
   if(pPlanet && (AIHostilesInSector(pPlanet->GetSectorID() == FALSE || pShip->GetAttack()>0)))
   {
         pShip->CancelDestination();
         pShip->pPlanetDestination = pPlanet;
         return true;
   }
   //Step #4: We're AT the Rally Point
   PclassRallyPoint pOnRallyPoint = this->FindNearestRallyPoint(pShip->GetTileX(),pShip->GetTileY());
   if(pOnRallyPoint && pOnRallyPoint->GetDistanceInTiles(pShip) == 0)
   {
      pShip->GoHome();
   }
  
   //Step #5: What if there are hostiles here? Let's retreat
   if(this->AIHostilesInSector(pShip->GetSectorID() && !pShip->GetAttack()))
   {
      pShip->GoHome();
   }
 
   return false;
}

Message Received and Acknowledged :N1 Beginning termination of Carbon based lifeforms on the 3rd rock from the Sol System

on Jan 14, 2015

I'm more of an ops guy than dev, so c++ isn't my strongest language, but I feel like I understood it decently enough to comment .    One thing that I notice is that it doesn't consider if getting to pClosestPlanet would require sending warships through $ThirdFactionSpace, that or it's part of another function.  


I make this commentr partly because larger galaxies mean that $A could be at war with $C even though $Bis between both of them (or at least significant chunks of between).  It might be a side effect of the beta 3 build0.61 AI, but I often find myself playing a chessmaster kingmaker that bankrolls nonthreats(to me) against PossibleThreats to act as catspaws to make sure they don't go passed PossibleThreat.  I'd love to see the ai consider who it has to cross to get to a target and more importantly use catspaws (including the player) sometimes rather than just their own direct brute force



With that said, thanks for taking the time to post about the ai coding itself.  Sometimes not knowing what kinds of things the ai considers can make modding more difficult or full of snipe hunts trying to guess.

on Jan 14, 2015

Interesting code.  The GC3 code looks pretty good, though I'm surprised he's explicitly declaring variable types; we generally use auto for most locals as per the "almost always auto" idiom http://herbsutter.com/elements-of-modern-c-style/

Also, interesting that he does

   for(auto planetIt = ownedPlanets.Begin(); planetIt != ownedPlanets.End(); ++planetIt) 

instead of simply

  for ( auto & planet : ownedPlanets )

Doesn't seem like there's necessarily a need to use an iterator in this case.

on Jan 14, 2015

Also, curious about the line

    TilePair planetPos = GetInterface<IBasicGameObject>(*planetIt)->GetTilePosition().tile; 

Is planet not derived from IBasedGameObject already?  Or is this some sort of component-based interface?

I.e. is there any reason you can't just do these 2 lines as

 

  for ( auto & planet : ownedPlanets )

  {

      auto planetPos = planet->GetTilePosition().tile?

      // other stuff



?

on Jan 14, 2015

Yow.  I'm about 7 years behind the C++ standard, with autos and stuff.  Might clean up some of my old code

Do function delegates work now?  e.g. to put a class member function directly into a map<string, ___>?  The problem used to be that you could not define the type of the most-general pointer-to-member, because different classes are distinct scopes, and there's no common pointer-to-member-function type that works across 2+ disjoint classes.  (So I fudged it by bit-converting them to/from doubles using a union, which Stroustrup explicitly says not to do )

I know Alexandrescu is in Sutter's group at Microsoft.  So ... is typeof now a C++ keyword?  Hehe -- he did all of his tricks with sizeof, which subsumes typeof.

on Jan 14, 2015

(reads all posts in this thread)

 

Um...I like candy..?

 

(walks away patting self on back for contribution)

 

on Jan 14, 2015


(reads all posts in this thread)

 

Um...I like candy..?

 

(walks away patting self on back for contribution)

 


 

LOL +1

on Jan 15, 2015

With all respect Mr. Wardell, as a programmer your code looks more structural code than object oriented. Your developer's code looks more object oriented. I dont know exacty ISO97 c++ standarts supports that features your developer coded.

on Jan 15, 2015

I guess all those searches for closest viable target explains why the AI empires often end up looking like circles. All I need is to head for the middle of the ring to find their starting location, and by extension their best planets.

on Jan 15, 2015

I'm not sure what this thread is about...

3 Pages1 2 3