Subsystem: AI - Adam-Poppenheimer/Civ-Clone GitHub Wiki
The AI subsystem is the most incomplete of the codebase. The design called for a fully-featured AI for each civ that could make at least reasonably intelligent decisions without hard-coding requirements or automatically spawning units. It, unfortunately, never came to fruition. But there is still some infrastructure for controlling units which can be analyzed.
Control of units is centered around the IUnitCommand interface, its implementations, and the classes that make use of it. An implementation of an IUnitCommand instructs a particular unit to take certain actions in pursuit of a particular goal. These goals are expected to take multiple frames, possibly several seconds, and so are run on a Unity Coroutine (which despite its name does not utilize multithreading). The UnitCommandExecuter class keeps track of which units have which commands and attempts to execute those commands when IterateAllCommands() is called. IterateAllCommands() doesn't (and shouldn't) lock the program, instead letting other code run while commands are being executed.
The IterateAllCommands coroutine keeps a list of all the units that still have unresolved commands available, and a queue (really a List) for each such unit. For each such unit, it takes the next command in its queue and checks its status. If that command has not started, its StartExecution() method is called. If the command has succeeded, it is removed from the unit's queue and the next command will be started on the following frame. If the command has failed, UnitCommandExecuter assumes that something has gone wrong and that the unit's remaining commands cannot be performed. It removes all associated commands from that unit and also removes the unit from its list of units actively performing commands. The unit is similarly removed when it runs out of commands.
The currently-implemented unit commands all pertain to the Barbarian subsystem, since that's the only subsystem that makes use of AI. It was decided to separate them into a separate AI subsystem because future non-barbarian Civilizations would need access to them.
AI also contains classes that generate influence maps. An influence map is effectively a layer applied over all the cells of the map, a float intended to represent the strength of some value. The InfluenceMaps class, the standard data structure for storing such data, is designed to be reused turn-over-turn for memory reasons, but are cleared and reassigned every turn. The main class designed to handle this is the somewhat inconveniently-named InfluenceMapLogic class, accessed via its interface.
There are currently 3 influence maps: AllyPresence, EnemyPresence, and PillagingValue. These are set up through a list of IInfluenceSources configured at design-time. These sources take the existence InfluenceMaps and a target civilization, and then fill in information for one or more of the maps relative to that civilization. The expectation is that civilization can then use the map to inform its decision-making.
Ideally IInfluenceSource implementations should be order-independent to avoid bugs, though they are run in the order they're configured. Both UnitInfluenceSource and ImprovementInfluenceSource follow this pattern.
Serving the influence maps, the AI subsystem also has a number of estimation tools, most notably UnitStrengthEstimator and UnitComparativeStrengthEstimator. The former determines a unit's strength by comparing it to the current cutting-edge of units discovered by all civilizations, simulating combat between the real unit and the cutting edge. The latter does do through combat alone.
The AI subsystem as a whole was designed to be as decoupled and extensible as possible, since major changes and additions to it were expected. Thus there are a sizable number of relatively small classes with dedicated interfaces that are used by only a single dependent.