AIIDE 2019 - looking into AITP
AITP follows an “aggressive defense” game plan, similar to SAIDA, where it builds up a strong ground army, then sets up tank lines in forward positions to constrict the enemy like a boa (preferably a snake, sometimes a feather boa). Its overall skill is far less, though; it finished second to last in AIIDE 2019. Overall, after reading some of the key code, I am not impressed (maybe you shouldn’t expect me to have been). The plans are ambitious but the work looks hasty, as if the authors underestimated it and had to rush to make the tournament. Still, the plans are ambitious, and that makes it somewhat interesting.
tactics
One of AITP’s first steps is to calculate map positions for a wall (supplyPoint
barracksPoint
bunkerPoint
), defensive locations (chokePoint1
through chokePoint4
) and offensive tank lines (frontLine1
through frontLine3
). How does it calculate them? They are hardcoded for every starting base on every map in InformationManager::initializePosition()
. That must be part of why AITP did not want to participate in the unknown map tournament. These positions seem to be the foundation on which all the tactical decisions are laid.
Spider mines are placed in neat diagonal double lines at locations offset from the defense line calculated by CombatCommander::updateDefenseLine()
, which looks mostly at the supply and the previous defense line and sets the current defense line to chokePoint2
(the natural choke) or to one of the precalculated frontLine
values. It’s simple and primitive, and there is not much examination of the game situation, a good starting try but nothing you expect to be strong. It’s silly sometimes; the defense line may be in front of an empty base that is away from the fight. CombatCommander::updateSpiderSquads()
sets up spiderSquad1
and spiderSquad2
—and also lays mines itself, calculating the offset steps and checking every vulture itself rather than leaving it to the squads. The intended separation of functions is not observed. Micro::LaySpiderMine()
is unused.
StrategyManager::shouldBuildTurret()
decides how many turrets should go where. Some turrets go to occupied choke points 1 and 2, some next to command centers, and some at the front lines computed above depending on certain “squad positions.” These so-called squad positions come from CombatCommander::getSquadPosition(std::string squadID)
, which takes a string that is called squad ID but is actually a 2-digit code that identifies a location rather than a squad. The location is usually an offset from the current defense line, but there are exceptions. The ID seems to choose one of a hardcoded set of offsets for the final position.
Units, of course, also go to the current defense line: That is the tank line, soon furnished with mines and turrets, that is meant to restrict the enemy. At some point CombatCommander::updateAttackEnemyBase()
becomes true and sets _aimEnemyBase
to the location of an enemy base to destroy. When this happens depends on what the current defense line is, but it’s another set of simple calculations using the closest friendly unit to various enemy bases.
what to build
I outlined the build order and unit mix decisions in the post how AITP played. There are game stages A, B and C and “modules” A1, A2 and so on for each game stage.
A1 is the antirush module that starts a barracks at 5, whose play was described in that post. It switches to the next module when there is a bunker and 4 marines and the engineering bay is started. One of AITP’s strategies was A1-B1-B2-C2. B1 makes SCVs up to 20, barracks up to 5, academy and medics, moves out at 10 marines, and switches to the next module after 10 minutes game time. B2 makes a bunker under certain conditions—but only if the previous module was A4. The modules are not in fact modular but know about each other. Then it makes SCVs and marines up to a smaller limit than B1, makes a factory and gets the upgrades and expansion. It switches when there are 2 command centers or the supply reaches 100 (really 100, not 50: int supplyUsed = BWAPI::Broodwar->self()->supplyUsed() / 2;
). Module C2 is the middlegame: It makes SCVs to the limit, adds factories, throws bunkers into the middle of the map, gets armory upgrades, and never switches.
Here’s an extract showing the strange over-specificity of AITP’s code, from StrategyManager::doSwitchModule()
which switches from the current module to the next one. The code does not simply parse out the strategy module names from the StrategyName
string, it handles each as a special case, sometimes taking extra actions that I would say should be factored out.
if (_currentModule == "A1") { if (Config::Strategy::StrategyName == "A1-B1-B2-C2") { _currentModule = "B1"; CombatCommander::Instance().clearSquad("bunkerSquad2"); } else if (Config::Strategy::StrategyName == "A1-B3-C2") { _currentModule = "B3"; CombatCommander::Instance().clearSquad("bunkerSquad2"); } }
AITP’s modules are not easy to read. I think Steamhammer’s explicit build orders plus zerg strategy rules are more perspicuous and no less expressive—but then I would say that, wouldn’t I?
Comments