Civ1 explained: AI unit movements

darkpanda

Dark Prince
Joined
Oct 28, 2007
Messages
823
I intended to make a nice, well-compiled post to explain the details of AI Civs move their units around, but it's already taken me so much time to simply reverse-engineer half of the AI movement routine, that I decided to make a pre-weekend post, with nothing more than a pastebin link to a raw pseudo-code version of the reverse-engineering done so far.

It is just about half of the routine, but it already reveals many interesting things about bombers, fighters, nukes, settlers, defensive units, sea units, ...


The routine is now completely reversed in pseudo code, pastebin post here.

I may get back here and enhance this post with more elaborate explanations about this or that unit.

So this is really just for early sharing, prompting reviews and comments from whomever is interested.

Cheers!
 
Fascinating darkpanda. It will be really interesting once you get it all decoded. For example the "if(PerCivTechCount[civID] < PerCivTechCount[playerCivID])" line in one of the cases for AI choosing to build a city caught my eye. It looked pretty buried in other if statements, so maybe the overall effect is low, but does the AI player's ability to found new cities depend in part on the tech race? I'm sure you will continue to uncover lots of interesting logic...

if(unitType == 0x1A) { // if unit is Diplomat
...
deleteUnit(civID, unitID); // Diplomat is useless, just get rid of him already...
}

LOL - That's hilarious.

PS

Guess I will interject and tell everyone about my game tonight, took probably about 6 hours, emperor difficulty. Started alone on a small continent, supporting only around 5 cities that never grew past a size of 6 or so. There was some useless mountains and tundra in the north which I had to defend from recurrent barbarians, but just leaving a couple militia fortified in the mountains was enough to best the barbarian legions. Anyway instead of my usual strategy of bee-lining for Republic or Democracy, I decided to go for Monarchy instead and to conquer the world with knights. I researched Monarchy around 2500 BC, but I irrigated and mined my tiles as well as finished research on chivalry and mapmaking (the only tech needed to conquer the world!) before switching to Monarchy around 1440 BC. I had also built up a small fleet of diplomats whilst waiting for chivalry to make knights available. Building a trireme I found I could ferry troops across to land on my east or south side, later I would discover both landings to be part of the same continent. I invaded to the south first, bringing knights which met with eerie findings: evidence of enemy irrigation and roads, but no cities to stand for them. So I sent settlers in to take over these excellent plots, and started encountering Russian units (chariots) coming out of the mountains on the east. With some tactical troop movements, waiting for the chariots to come down from the mountains I swept upon them with my knights and turned those knights into veterans. I pressed onward into the continent which curved upwards into the original landing point I had found on the east of my own island, which by now had become a mean center of knight production. Forgoing science I set my tax rate to 100%, using the money to buy the knights (targeting a buy point of around 20/40 shields). A single trireme was all that was needed to ferry the knights onto the main continent on my east, where they met with the Russians... and set up a siege outside the city walls of Moscow. Not wanting to throw knights at city walls, I quickly summoned my five or six diplomats to perform the sabotage. City walls destroyed! Those poor Muscovites were slain from a city of size 6 or so, to a city of just 2 by the time my knights had piled in. Then pushing into the east, I conquered Leningrad before stopping to recoup... however I noticed that Kiev, which would turn out to be the last Russian city, was under attack by the French! I sent a couple knights to speculate on the fight, ready to steal into Kiev if the French attacked it to an undefended state; but instead the French destroyed Kiev outright, and with it the Russian civilization, before meeting me with their French catapult and chariot. My knights were not impressed and did not wish to speak to the French however; fighting on through a narrow pass with some hills and mountains on the bottom, and a passage of plains on the top, my knights channeled through to explore an expanding continent. City by city, I pressed on against the French, meanwhile developing the cities I had established and conquered -- an F4 page's worth of cities by around 1 AD. With my empire expanding I established a luxuries rate, first 20 or 30%, but as war pressed on from my far-away Monarchy capital, I would eventually up the luxuries to 50%, the rest taxes, building a few marketplaces as well to supplement what was otherwise, just temples. I acquired some useful tech via conquest, such as Trade, Navigation, and Mysticism (to improve temples), but mainly I had all the tech I needed. I built roads across the main continent to speed my military supply chain, and pressed on against the French with chariots and mighty (not useless!) diplomats. Bribery I did not engage in, but the diplomats were invaluable at felling those masonry walls around Paris, Tours, Orleans. The French were a Republic, and by embassy I learned that they continued making tech advances even without a capital; sending in a diplomat to investigate one of their cities, I observed that a single Courthouse along with market and library were enough to boost their trade goods beyond anything from my own cities. Briefly I toyed with the idea of sending caravans to establish trade routes... But what was I thinking? Onward! War! When my knights marched into Avignon I captured the Great Library from the French -- and I wondered if it was working, seeing as it brought me no tech discoveries. Then checking my foreign ministry, I realized that only the English and French, besides myself (playing Egyptians by the way), remained in the game at that point. Although we had started with seven civs, they had been killing each other on that main continent, it would seem! The last "French" city to fall into my hands was New York, in a cul-de-sac to the northeast; by now the train of knights marching across the continent, six paces at a time by road, was truly impressive, and more than enough to conquer the English in the east-southeast, who had even less tech than me and did not bother building city walls. In the end, I conquered the world in 1430 A.D., with the (too generous conquering bonus) score of 1514 (151%). Final tip: I had actually captured the Oracle from the Russians, which with temples was really helping my happiness! But I decided to spare Grenoble, one of the last major cities of the French, until I had built up an irresistible force to conquer it. I would have been better, probably, to conquer it sooner, because the French developed Religion -- negating my Oracle and forcing me to up my luxuries rate for an easy fix -- just shortly before I captured their last major science-scale city! Tying up the final mystery: The already irrigated plots I found in my first excursion overseas, were the remainders of the Mongol empire vanquished by the Russians, judging by the replay data.

Well, that was probably too long of a narrative.... I just wanted to thank darkpanda by pointing out that as he uncovers the game's secret internals, others such as myself are indeed still playing around with this game! Take care all and keep up the great work darkpanda!
 
That's a huge hijack indeed :) but nonetheless interesting. I have thought before of creating more CivDOS tools (maybe as part of JCivED) to ease sharing of epic games, such as reimplementing the replay feature in a web-supported way... Stories such as yours make such old ideas float back to the surface, and give me more incentive to make them happen :)

And thanks for the heads up, as always.
 
If you ever wondered about what's making AI units (non-Barbarian) pillage your improvements, here it is:

Code:
  //seg010_2192:
  if(UnitTypes[unit.typeID].totalMoves < 2) { // unit type has less than 2 total moves
    //seg010_21B4:
    if(distToClosestCity<4) { // closest city is less than 4 moves away
      //seg010_21BD:
      if(Cities[closestCity].ownerID == playerCivID) { // closest city belongs to player
        //seg010_21D5:
        if( (CivDiplomaticStatus[civID][playerCivID]&2) == 0 ) { // not at Peace with player
          //seg010_21EC:
          if( (getImprovements(unitX, unitY) & 0x6) != 0) { // there is Mine or Irrigation on unit square (do you see it coming?)
            //seg010_2201:
            if(Cities[closestCity].ownerID != civID) { // closest city does not belong to AI civ itself (useless because of player check above...)
              //seg010_220C:
              return 0x50; // 'P' char -> pillage the square
            }
          }
        }
      }
    }
  }
 
Without hacking, just playing with the rules, you would need to either:
- not improve terrain around your cities
- not allow enemy units within 3 moves of your city (at least enemy units with total moves lower than 2)
- or be at peace with AI civs! :) More precisely, that AI civs are at Peace with you, which is much more tricky...

For Barbarians, I didn't look at their dedicated logic yet. It will come, in time.
 
For barbarians, all I know is that only land-spawned barbarians pillage. Sea barbarians don't.
 
I am nearing completion of the reverse-engineering of this routine, but even before finishing, I believe I finally confirmed the usage of the unit byte field unknown9 that was gussed to be a directional byte: in the routine currently under analysis, this value is explicitly set to the best neighbour square's ID, to which the unit should move at its next turn.

The final block of logic is quite complex to decode, but shouldn't take too long... In the meantime, I am also re-posting a pastebin post with the pseudo-code version of the routine.


Post updated, see below.
 
Finally, I (think I) covered the entire routine for AI "unit's orders" routine (rather than 'movement'). The final block arguably contains most of the *juicy* logic, that assesses the 'desirability' for a unit to move to any of its 8 neighbours, depending on many various factors, such as its roles, the neighbour contents, the likelihood of battle wins, etc.

Here is a new pastebin post with the updated pseudo-code.

It is very large (the 3rd biggest routine in CIV.EXE), and thus quite complex to summarize in plain English, but I may still attempt to do so. In particular, I discovered some previously unknown data in there, most likely related to more "internal AI" assessment of geographic areas (so called 4x4 map "tiles"). I want to investigate their meaning first.
 
Finally, I (think I) covered the entire routine for AI "unit's orders" routine (rather than 'movement'). The final block arguably contains most of the *juicy* logic, that assesses the 'desirability' for a unit to move to any of its 8 neighbours, depending on many various factors, such as its roles, the neighbour contents, the likelihood of battle wins, etc.

Here is a new pastebin post with the updated pseudo-code.

It is very large (the 3rd biggest routine in CIV.EXE), and thus quite complex to summarize in plain English, but I may still attempt to do so. In particular, I discovered some previously unknown data in there, most likely related to more "internal AI" assessment of geographic areas (so called 4x4 map "tiles"). I want to investigate their meaning first.

Nice work :D sure it's a big routine but not much code at all compared to the entire program? If they had put more work into the AI and made that routine five to ten times longer, Civ 1 could've been a fun challenge.
 
And there's the logic for barbarian units:
https://pastebin.com/sGMHRnQS

It's much more shorter and simpler, of course. Many gotos here, mb just compiler optimizations, if not civilized MPS guys style. Anyway, I don't want to change anything.

edit: fixed errors
 
Last edited:
Top Bottom