archive by month
Skip to content

LetaBot’s wall 2 - the outline

Yesterday LetaBot found the center tile of the location it wanted to wall. Here is the outline of the rest:

  1. Precalculate some stuff.
  2. Initialize useHighGround to true.
  3. Call RecursiveWall() to calculate a wall or fail.
  4. If it failed, set useHighGround to false and call RecursiveWall() again.
  5. Do a final check: Is the wall in time?

The precalculation is surprisingly complicated. It looks like the main results are Closest and Furthest tiles in a bounding box that the wall must fit into. Closest is the tile in the bounding box closest to the original command center by ground and Furthest is the farthest reachable tile in the box. Both are required to be walkable and buildable.

If LetaBot can, it builds the wall on high ground. On many competitive maps, the main is on high ground with a ramp down, and the wall definitely belongs on high ground. On some the main is on level ground and the wall can be placed freely. On a few, like Jade, the main is on low ground with a ramp up. In that case, LetaBot will look to build its wall outside the main if it can. I’m not sure whether that’s good or bad; my Brood War knowledge is lacking. Conventional wisdom is that an up ramp means you should expand early so you don’t have to fight up your own ramp, so maybe any wall is a mistake. And I have seen LetaBot lose on Jade when its wall proved easy to attack and hard to defend. Can some terran expert tell us more?

The final check looks to see whether LetaBot has already started to build without waiting for the wall calculation to finish. Wall planning is done in a separate thread because it may take a long time. LetaBot is prepared for the case that wall planning takes too long and the bot has pre-empted the wall by starting to build elsewhere.

	  //also check if the producmanager isn't already building
	  //because if this is the case then don't go for a wall
	  bool StopWall = false;
	  //check if supply depot already build
	  BOOST_FOREACH( BWAPI::Unit* building , Broodwar->self()->getUnits() ){
		  if( building->getType().isBuilding() &&
			  building->getType() != BWAPI::UnitTypes::Terran_Command_Center ){
              StopWall = true;
		  }			  
	  }

	  if( ProdMan != NULL ){
	    if( ProdMan->TotalTypeInQueue( BWAPI::UnitTypes::Terran_Supply_Depot ) > 0
		    || ProdMan->TotalTypeInQueue( BWAPI::UnitTypes::Terran_Barracks ) > 0){
              StopWall = true;
	    }
	  }

	  if( StopWall == true ){
	     WallSound == false;
	     WallCalculated = false;
	  }

The first check looks for any existing building other than a command center, and the second check looks into the production manager’s queue for either a depot or a barracks. If either check finds that it is too late, the bot tells itself that no wall is possible after all.

Next: Starting on the details of RecursiveWall().

LetaBot’s wall 1 - locating the wall

LetaBot’s wall code is hard to follow, so I’ll take it in bite-size pieces. Today, deciding where the wall should go. It’s likely to be a good location for other defenses, too.

LetaBot can calculate how to wall off its main base with a barracks and 2 depots. It’s the classic terran wall. Unfortunately it doesn’t seem to recognize cases where fewer buildings are enough. The call to calculate the building locations for the wall is BuildingManager::WallOff(). If successful, it stores the calculated locations in the object for later use. From the BuildingManager class declaration in BuildingManager.h:

	bool WallCalculated;//True when the map calculation is done
	bool WallSound;
	BWAPI::TilePosition BarracksWall;
	BWAPI::TilePosition SupplyWall1;
	BWAPI::TilePosition SupplyWall2;

Below, I left out some confusing redundant code plus code with unrelated purposes.

void BuildingManager::WallOff(){

	BWAPI::TilePosition TileChoke;
		
	TileChoke = BWAPI::TilePosition( InfoMan->PosMainChoke );

It’s looking up a location recorded in the InformationManager. Here’s how InformationManager calculates it, at first naming the target choke BunkerChoke.

	BWTA::Region* natRegion = BWTA::getRegion( PosOurNat );
	BWTA::Region* mainRegion = BWTA::getRegion( PosOurBase );

	BWTA::Chokepoint* BunkerChoke = NULL;
	BOOST_FOREACH( BWTA::Chokepoint*  choke, BWTA::getChokepoints() ){
	   if( (choke->getRegions().first == natRegion && choke->getRegions().second == mainRegion) ||
		   (choke->getRegions().second == natRegion && choke->getRegions().first == mainRegion) ){
			BunkerChoke = choke;
	   }
   }

It walks through the choke points as reported by the Brood War Terrain Analysis library BWTA until it finds one which is between the main and the natural. In theory there could be more than one, but it doesn’t worry about that. The natural (at PosOurNat) is calculated earlier as the gas base which is closest to the main by ground distance. That should be fine for standard competitive maps, including maps with an internal mineral-only like Andromeda and Electric Circuit.

	if( BunkerChoke == NULL && mainRegion != NULL && natRegion != NULL ){
		int closestNat = 99999;
		BOOST_FOREACH( BWTA::Chokepoint*  choke, natRegion->getChokepoints() ){
		   if( BWTA::getGroundDistance(  BWAPI::TilePosition(choke->getCenter() ), OurBase ) < closestNat ){
			   BunkerChoke = choke;
			   closestNat = BWTA::getGroundDistance(  BWAPI::TilePosition(choke->getCenter() ), OurBase );
		   }
	   }
   }

If that doesn’t succeed because the map has no choke between the main and natural, it tries again. It could happen if the natural is internal to the main’s region or if the closest gas natural is farther and there are intervening regions (which is likely if the closest next base is mineral-only, as on Nostalgia or Korhal of Ceres). (It also fails on an island or semi-island map, but presumably LetaBot is not intended to play on those.) The code considers the chokes to the natural. It walks through these chokes and picks the one that is closest to the start location (OurBase) by ground distance. I’m not sure how well it works, but that’s what it says. It may mess up if there is an intervening region between the main and natural and the intervening region has another entrance. My first thought is to consider the chokes out of the main and do a path analysis: Go through the choke to the next region; from there, compute shortest ground paths to the possible enemy base locations; if none of the paths involves returning back through the main, then that choke is toward the enemy and may be worth walling.

   if( BunkerChoke != NULL){
     PosMainChoke = BunkerChoke->getCenter();
   }

If one of the two methods worked, then PosMainChoke is the center of BunkerChoke, all done.

Discussion. It looks like some maps will confuse the wall locating code, especially non-standard and old-school maps. If there is an internal natural, or an entrance to the main other than through the natural, LetaBot may do something unhelpful. For example, Alchemist (as played in CIG 2016) has two entrances to each base. It’s a 3-player map arranged as a big circle, so that the enemy is closer to one of your base entrances than to the other. This code will choose the natural base with its wall location sometimes toward the enemy and sometimes away—it probably should put the wall toward and the natural away (there are bases outside both doors). In another example, the old tournament map Bifrost has a front entrance to the main plus an internal mineral-only with a back entrance. Hypothetically, if we gave the internal base a geyser, then this code would build an unhelpful wall between the main and the internal natural, allowing enemy units that came through the back entrance access to the ledge above your mineral line. The right plan is to block the front and back entrances separately.

Even choosing a wall location can be tricky! LetaBot is optimized for the usual case, and nothing is wrong with that. It would be extra work for little gain to handle all cases well.

The name BunkerChoke is a hint: A wall is only one form of defense. The same choke may be good for other defenses, whether static defense or units standing guard. Even smart bots sometimes misplace their early defense. For example, watch Zia on Andromeda place early zerglings on guard well back from the ramp in the “choke to the main,” because it treats the high-ground mineral-only as a separate region. Or watch Skynet on Benzene misplace its early zealots, because there are two entrances to the main and Skynet does not realize that one of the entrances is blocked by map buildings. It may seem like an easy decision, but it’s hard.

Iron’s runby skill

Back in March, one of my early blog posts was about a game between Iron and Killerbot in which Iron successfully ran by Killerbot’s static defenses with vultures and SCVs and cleaned out the main. At the time Iron didn’t have the skills to seal the win, but it was a promising start.

Soon after, author Igor Dimitrijevic turned off the runby feature. I suppose his development strategy was to get the basics down before adding such a risky skill. But more recently the runby feature is back. I saw Iron do runbys in September already.

Here’s a game played today where Iron rolled Krasi0. Iron opened with two factories making vultures followed by a starport. Krasi0 made a barracks and then immediately expanded behind a bunker. With repair, the bunker can stop most early game harassment. It’s a good opening.

The moment that Iron gathered 3 vultures outside Krasio’s natural, it ran by the bunker and into the main, like so.

Iron runs by Krasio’s bunker

1 vulture was lost in the attempt. The other 2 stopped all mining in the main. Notice the kill count—that vulture got 8 SCVs and 1 newly-made Krasi0 vulture.

Iron’s vultures stop all mining in the main

Meanwhile, wraiths from the starport flew over as they were made and also stopped mining in Krasio’s natural. Krasi0 specializes in defense, but could make only feeble attempts to ward off the attack.

Runbys still rule, especially if you have aggressive micro skills like Iron. Lessons for the defender!

  1. If you see a runby coming at you, you may be able to pull workers to physically block the runby. Then your static defense will drive it off. It’s an advanced skill, though.
  2. You can place buildings to restrict the path for the runby. Krasi0 could have landed its barracks and/or built a depot near the bunker. Stopping runbys is one of the ideas behind IceBot’s building placement. Killerbot also has anti-runby building placement, though it’s not enough in itself to stop a runby.
  3. Krasi0 could have defended better after the runby. The SCVs were too fearful. Krasi0 needed to gather resources and produce defenders to defeat the few attacking units; it’s no use to retreat SCVs at the cost of resources needed to win. In recent years I notice that pros increasingly often leave workers at work when a base is under attack, instead of running them, and I assume that’s their reasoning.

Skynet’s LatencyTracker

Back when I was analyzing Skynet’s code, a class LatencyTracker came up. It sounds like it might have to do with latency in network games, doesn’t it? Latency is a confusing issue for bots, and Krasi0 asked me to look into it. It’s been on my to-do list ever since.

It turns out that LatencyTracker has nothing to do with network latency. It keeps track of the location and duration of storm and stasis. Oh well. :-(

It’s used in only a couple places in code like this. mUnit is of type BWAPI::Unit*. isStormInRange does what its name says: Is a storm calculated to be in range of the current unit? I’m not at all sure what the practical effect of this test is, since it looks redundant. Maybe it is working around a bug in some version of BWAPI, or maybe it is used with hypothetical units that BWAPI doesn’t know about. Or something.

bool UnitClass::isUnderStorm()
{
	if(exists())
		if(mUnit->isUnderStorm())
			return true;

	return LatencyTracker::Instance().isStormInRange(shared_from_this());
}

Bereaver status

Bereaver continues to score wins against its top rivals, but I’m not sure whether it has reached #1 on SSCAIT. Krasi0 and Iron dominate the weaker bots, winning nearly all games, while Bereaver still drops some games to them. It takes time and effort to make a bot solid against all the different strategies!

Bereaver loses some games to protoss bots with strong macro, such as Skynet, when Bereaver takes its natural too late. It can also lose to unusual strategies like XIMP’s carriers. Here Bereaver storms the carriers and takes their shields off—it wasn’t enough, they still had all their hit points. After a long fight, XIMP recorded the win.

Bereaver storms XIMP’s carriers

Bereaver’s zergling rush defense seems to work well against ZZZKBot, but shows weaknesses against variations. Here Zia opened 5 pool and, unlike ZZZKBot which suffered some pathing errors, immediately broke Bereaver’s ramp. The cannon-probe defense was firm and Bereaver held easily. So far so sound.

Zia breaks Bereaver’s ramp

But while ZZZKBot does the fastest possible 4 pool and can only follow up by sending more lings, Zia switched to drone production and teched to mutalisks off of 1 hatchery. There is no strong followup to a failed 5 pool and Bereaver could have shrugged off the weak air attack... if it hadn’t restricted its production to zealots. Even so, the zealots could have won the game if they had attacked instead of holding their ground.

Bereaver’s zealots stand around under air attack

Bereaver still put up a fight, but Zia added to its mutalisk numbers and finally won.

Against Overkill’s 9 pool, Bereaver still canceled its gateway at the first sight of the spawning pool, before the scouting probe had traveled far enough to see the drones. It was not a sound reaction to 9 pool. After losing the blocking probes, protoss was already behind in economy. Overkill aggressively went after more probes, and despite poor play later was ahead the whole game and won as it should.

Overkill goes after Bereaver’s probes

Notice Bereaver’s supply. With only 6 probes, it was impossible to catch up.

Bereaver may be the new #1

Bereaver is mounting a strong challenge for #1 on SSCAIT, and may have already reached it. It has been scoring wins against most of its rivals, including Krasi0, Iron, LetaBot and Tscmoo versions, and ZZZKBot. I haven’t seen a win against KillerBot, though.

Bereaver’s favored game plan is to outmacro its opponent and win with repeated direct frontal attacks, “damn the torpedoes” style. Against strong defenders like Krasi0 and IceBot that can lead to long games, but they aren’t strategically interesting.

Here Bereaver has defeated Krasio’s slightly sloppy early push in the center and broken into the terran natural. The zealots ignored the defending vultures and killed all the SCVs, bringing a quick win.

Bereaver damns the torpedoes, or at least the vultures

Bereaver has adequate skills with storm and reavers. Both could stand to be improved (but what couldn’t?). It knows how to cannon a ramp for defense (the early error where the cannons blocked the path is fixed). I think its most impressive special skill is entrance blocking, as here. It forms the zealot block with smooth ease, and dissolves it just as easily when it wants to let units through. It’s nothing compared to ZerGreenBot’s special skills of reaver drop, zealot bombing, and overlord hunting, but Bereaver is much stronger overall.

Bereaver blocks its ramp with zealots

Bereaver plays a different strategy against ZZZKBot’s 4 pool. As soon as it scouts the rush, it blocks its entrance with probes. Here it blocked the entrance much too early, while the zerglings were still in their eggs, and lost mining time needlessly.

Bereaver blocks its entrance with probes

Then it cancels its gateway, starts a forge instead, and cannons its main. The plan seems successful. It’s slow, but who cares about that when you win? The probe block is tight. The lings tend to suffer pathfinding failure and wander, but may soon kill a probe to break through, as here. The probes fought back, as you can see by the blood.

ZZZKBot kills a probe to open the entrance

Then Bereaver pulls probes to defend its cannons and easily holds. With its probe skills, it is not in any danger whatever. After this it can build up and win at its leisure.

Bereaver defends its cannons with probes

Rob Bogie’s MaasCraft has map problems

What is it with Rob Bogie’s SSCAIT version of MaasCraft (originally written by Dennis Soemers)? Why does it do so much better on some maps and so much worse on others, with far more variation than the rest of the bots?

I looked up MaasCraft’s results in the AIIDE 2014 and CIG 2014 competitions (it didn’t play in other years). Both map pools overlap with the SSCAIT map pool. CIG 2014 had an extra wide and diverse map pool, with some nonstandard maps, and it only overlaps a little. The overall row is across all maps in that competition, not only the ones in the table.

mapCIG 2014
win %
AIIDE 2014
win %
SSCAIT
Elo diff
Andromeda63%-361
Benzene55%+42
Circuit Breaker52%+246
Destination59%-313
Empire of the Sun61%-333
Fighting Spirit50%-418
Heartbreak Ridge61%-193
Icarus50%+291
Python60%-306
Tau Cross57%+365
overall55%59%0

I’m comparing raw win percentages with Elo differences, but the pattern is clear—I mean, the lack of pattern. The 2014 tournament results look normal, the SSCAIT results look extreme, and the two are nothing alike.

CIG 2014 and AIIDE 2014 both released source code. The MaasCraft source is exactly the same in both. It looks as though some difference or bug is affecting only the SSCAIT version under Rob Bogie.

Later today: First thoughts on the AIIDE 2016 results.

updates on ZerGreenBot and Bereaver

ZerGreenBot has been updated repeatedly, and now plays a richer game. I liked its PvZ best; it opens with a forge fast expand and follows up with corsairs, reavers, and zealots, a strategy that is somewhat similar to mainline human play.

Here are shots from a game on Benzene against Tscmoo zerg. ZerGreenBot opened pylon, forge, cannon, nexus, gateway, cannon. It looks to me like a sound wall-in in the correct place (a couple spots need blocking units to be tight). On maps that don’t allow a wall-in, like Destination, it necessarily builds something looser.

ZerGreenBot’s wall

ZerGreenBot’s first tech choice at the next level was stargate, starting air attack +1 before the stargate finished warping. Normal would be ground attack +1, but its choice seemed justified because the corsairs killed overlords throughout the game despite determined resistance. ZerGreenBot still tries its reaver drops versus zerg, but overlord hunting has become its top skill.

ZerGreenBot hunts overlords

Unfortunately ZerGreenBot’s ground units fought so poorly that their code must be buggy. Even the air units showed some stuck-in-a-loop behaviors. Tscmoo won a crushing ground victory in the center and easily took it home. If its author keeps up the good work, though, ZerGreenBot is a threat to reach the top ranks. It has reaver drop and overlord hunting skills beyond any other bot. I think it also has the best protoss wall.

Meanwhile, Bereaver is already a top bot. I watched it battle Tscmoo protoss on the 2-player map Destination. Bereaver made an ill-advised attack across the bridges into Tscmoo’s natural and lost too much army. Tscmoo should have consolidated its advantage by expanding and containing, but instead tried to cash it in immediately with its own attack across Bereaver’s bridges. It could have worked against a weaker bot, but Bereaver defended efficiently with cannons, a reaver, and a handful of troops. Tscmoo overpressed, fell behind, and lost. It was a decent win against a tough opponent.

Next: SSCAIT map comparisons.

ZerGreenBot does zealot bombs

A quick note: The current version of ZerGreenBot has given up on reaver drops (at least for now), but it does know how to bomb tanks with zealots. I think it’s the first bot with that skill.

Bereaver

Wow, did you see the new protoss bot Bereaver? It was uploaded today at SSCAIT. In its first game it failed to start (oops). But in its second game it played strikingly well against Krasi0, breaking Krasi0’s bunker in the early game even while expanding and teching; Krasi0 defended well and held, of course, because that’s what Krasi0 does. Then Bereaver put up a fierce fight in the middle game with reavers and high templar, repeatedly wearing down and breaking Krasi0’s pushes until Bereaver ran out of resources, unable to expand beyond its third in the face of terran map control.

Somebody with game-scheduling power must have seen the game too, because games against other top bots came up right away. Bereaver lost more than it won, but it did defeat IceBot despite misplacing its natural nexus and being unable to take a third, ignorant of how to clear the mine blocking the expansion spot.

I was also impressed with the game against Andrew Smith’s Skynet. Bereaver apparently diagnosed Skynet’s dark templar rush and prepared against it, cannoning and easily holding its ramp. The cannons were misplaced and blocked dragoons and reavers inside, a fatal blunder, but the basic skill is there.

It’s a great start for a new bot. Many rough edges are in plain sight, which means that improvements should come easily!

ZerGreenBot

The new protoss bot ZerGreenBot was uploaded at SSCAIT today. It describes itself as “terribad” and... I can’t disagree, but it’s fun. To defend its base it builds zealots and dragoons. These units only leave the base if they are lured out. To attack it sends a shuttle with 2 reavers. It seems to keep building shuttles and reavers from one robo, so whether the first lives or dies, more will fly out later to attack independently.

It never expands. It doesn’t scout until the shuttle flies around the map. If it happens to see the enemy natural first, it never seems to realize that the enemy must have a main too. The shuttle disregards danger. Sometimes it drops the reavers near their target, but on the wrong side of a cliff. In one game ZerGreenBot took a couple potshots at an unfinished spire, a good first target, but then moved on—and the attack was later cleared by the first mutalisk. And yet its manic shuttle-reaver micro is fun! The basic procedure seems to be drop, fire at whatever’s near, pick up, move a little around the outside of the base, drop, etc. It’s as if it were trying to duplicate the Berkeley Overmind’s “dismantle the enemy base from the outside in” tactics. When there are two shuttles, they do a wacky dance.

One thing the bot does right is that it keeps the shuttle always moving, so that it never has to accelerate from a stop. I take that as a sign that the author understands shuttle-reaver micro and merely hasn’t implemented much of it yet (because it is crazy hard).

In the games I’ve seen so far, opponents react poorly to the reaver drop. They don’t understand that the shuttle is a high priority target, and they don’t know how to escape or how to attack. Even with no other improvements, better shuttle-reaver control by itself might make ZerGreenBot a dangerous opponent for many bots, though probably not for the top tier. If the goal is to play strongly, I suggest this order of improvements: 1. Better choice of targets and drop locations. 2. Attention to avoiding danger. 3. Smarter scouting, so that the better choice of targets bites harder. And only then work on expanding and being more aggressive with the other units. Well, it’s only my first thought; I’m sure the author knows better than I do.

By the way, I think the name is a joke. “Zerg-reen” sounds like a zerg marine, everything that is not protoss.

Zia and its coat of many strategies

I had been hoping that Zia would start to choose between its openings, and now that it has I want to see how it’s doing. So I watched a bunch of replays. It’s using strategy learning, though I can’t say in what form. I predicted that choosing between its strategies would be advantageous, and it’s true to an extent.

With more variety, Zia has become more entertaining to watch. I like it. Zia plays these openings that I’ve seen:

  • 5 pool
  • 9 pool
  • 9 pool speed
  • 12 hatchery

I didn’t catch it playing overpool or 12 pool, which you might expect to be common.

Zia’s opening chat message gives a hint about its opponent model. It says “Nice to meet you!” for new opponents and “Hi again!” for opponents it has met before. And it either predicts a “harsh game” or claims “I may overwhelm you.” I think it picks the second message when it believes it has found a strong counter strategy.

Against opponents with a single strategy which is directly countered by one of these openings, like ZZZKbot’s 4 pool (hard countered by 9 pool plus a sunken so that the trickle of attacking lings has no chance whatever), Zia seems to learn the counter and should then win every game. Zia even managed to find a strategy that gives it a chance against IceBot—Zia won a game which brought out weaknesses in both bots, weaknesses I didn’t realize IceBot suffered from.

And I see signs that Zia adapts after the opening. For example, I saw it add a spire when it needed scourge for air defense. I get the impression that it decides flexibly between hydralisks, mutalisks, and lurkers for the middle game—at least it’s not hardwired, maybe it’s random, I hope it’s learned. I have seen it play 12 hatch, 11 pool, 10 gas and also 12 hatch, 11 gas, 10 pool; I hope it’s foreseeing how much gas it will want to boot up its future unit mix.

Playing many openings does have a disadvantage: It’s harder to play all of them well. It’s not enough to know the build, you have to know how to play it, and it adds up to a lot of knowledge. The worst is Zia’s 9 pool speed opening, which it plays in a strange way as a late zergling all-in: It makes 100% zerglings until it attacks around supply 20-24; if it fails, Zia doesn’t have enough economy for the middle game. (I expect a 9 hatch build would strike harder if you want to play that way.)

Zia still plays poorly overall, if you ask me. It needs to brush up on skills like not sending drones through the enemy army. It needs better scouting (it doesn’t send out its initial overlord), better tactics (no, don’t run up the ramp to fight the bunker! Hit the SCVs in the expansion first!), better engagement skills (big groups of zerglings should surround before attacking), and better micro (in lings versus zealots, retreat a ling that will die in 1 more hit). And stuff. It’s a hard game.

Zia’s current description is “Implementing more strats . . .” I guess the author has the most fun with that, which is all that really matters, but it’s not the way to a winning bot. Breadth of skills, not depth of skills: You gain more by reducing your weaknesses than by increasing your strengths. Zia already has relative strength in strategy, and will improve most with other skills.

Hmm, I should write a post about The Winning Attitude for authors of game programs. Only for those who seek the winning attitude, of course; it’s optional.

Tomorrow: Novelty maps.

both sides take the same base

Funny picture: Until just before this screenshot of a game between Igor Lacik's bot and Overkill, both sides were mining from this base. As you can see, terran ended up winning it... with a displaced command center.

funny image of zerg and terran taking the same base

Zia has memory

Zia was reuploaded today. Its new description says “It has memory, and is trying to play like people do.” It sounds like it can now choose among its strategies as I hoped. It will take a lot of games to see how well it works....

Skynet - wrapup

Here’s what I learned in analyzing Skynet.

Skynet’s skills come across to me as first drafts of what you’d really want to implement. In looking at each one, it’s easy to see points that could be improved and shortcuts that were taken to get it working more easily. On the other hand, Skynet has a lot of different skills; the shortcuts paid off in time saved.

In other words, Andrew Smith put priority on breadth of skills rather than depth of skills. And it’s hard to argue with his choice, because Skynet is a great success, still a strong and interesting bot though its last update was in 2013.

I think it’s a key point. In a lot of posts I point out, “a better way to do X would be....” But you should only care if X is a weak point in your bot. It is better to play adequately in all respects than to play well in some and poorly in others. You want to minimize your worst weaknesses.

If you’re perfect at choosing the right unit mix to counter your opponent but your micro sucks, you lose. If you’re perfect at micro but you choose a stupid unit mix, you lose. Better to be fair-to-middling at both. Breadth of skills first.

You may be able to lift code from Skynet for your own bot, if you don’t mind being infected by its GPL license. You’ll probably have to do a bit of adapting to fit it into your own framework, but the code has clear interfaces and the important working parts seem mostly independent of the rest of the program. Once you’ve seen an idea it shouldn’t be too hard to reimplement it, either.

Skynet - avoiding splash damage

Skynet tries to avoid some area-of-effect spells and splash damage from some attacks. This is the fanciest skill that I’m writing up.

You can only avoid stuff that you can see coming. Skynet tracks threats in class AOEThreatTracker (where AOE stands for Area Of Effect), which creates and stores AOEThreat objects. Here is the key code from AOEThreatTracker::update(), which is called once per frame from the main onFrame() in Skynet.cpp:

	for each(Unit unit in UnitTracker::Instance().selectAllEnemy())
	{
		const BWAPI::UnitType &type = unit->getType();
		if((type == BWAPI::UnitTypes::Protoss_Scarab || type == BWAPI::UnitTypes::Terran_Vulture_Spider_Mine) && mUnitThreats.count(unit) == 0)
		{
			AOEThreat newThreat = AOEThreat(new AOEThreatClass(unit));
			mAllThreats.insert(newThreat);
			mUnitThreats[unit] = newThreat;
		}
	}

	for each(BWAPI::Bullet* bullet in BWAPI::Broodwar->getBullets())
	{
		const BWAPI::BulletType &type = bullet->getType();
		if((type == BWAPI::BulletTypes::Psionic_Storm || type == BWAPI::BulletTypes::EMP_Missile) && mBulletThreats.count(bullet) == 0)
		{
			AOEThreat newThreat = AOEThreat(new AOEThreatClass(bullet));
			mAllThreats.insert(newThreat);
			mBulletThreats[bullet] = newThreat;
		}
	}

Skynet recognizes enemy reaver scarabs and spider mines as splash damage threats. (A bot that might lay mines of its own should also recognize its own spider mines as threats. Skynet does not use mind control.) It also recognizes psionic storm and EMP as area effect spells to try to dodge.

What does it do with the information it’s tracking? Here’s the public interface from AOEThreatTracker.h:

	void update();

	AOEThreat getClosestGroundThreat(const Position &pos) const;
	AOEThreat getClosestAirThreat(const Position &pos) const;
	AOEThreat getClosestEnergyThreat(const Position &pos) const;
	AOEThreat getClosestThreat(Unit unit) const;

	bool isTargetOfThreat(Unit unit) const;

Psionic storm is both an air threat and a ground threat. Scarabs and mines are ground threats. EMP is the only energy threat. But of the 5 informational methods, only getClosestThreat() and isTargetOfThreat() are used in the rest of the code; the other 3 get methods serve no purpose. getClosestThreat() takes a unit and considers whether it is an air or ground unit to find the closest threat. It counts EMP as a threat to spellcasters only, even though all protoss units will lose their shields to it. It’s a little inconsistent; corsairs count as spellcasters, but Skynet never researches disruption web.

The results of all this tracking work are used in only one place, in BasicUnitAction::update(), which is a micro action like the others we’ve seen in the last days. The BasicUnitAction of course has very low priority among micro actions, so if the unit under consideration is already doing something else (like dragging the mine that is the threat), then none of this happens.

	const bool isTargetOfThreat = AOEThreatTracker::Instance().isTargetOfThreat(mUnit);
	if(!isTargetOfThreat)
	{
		const AOEThreat &closestThreat = AOEThreatTracker::Instance().getClosestThreat(mUnit);
		if(closestThreat)
		{
			const int distanceToThreat = mUnit->getDistance(closestThreat->getPosition());
			if(distanceToThreat < closestThreat->getRadius()+32)
			{
				stayAtRange(mUnit, closestThreat->getPosition(), closestThreat->getRadius() + 64, distanceToThreat);
				return true;
			}
		}
	}

First it remembers whether the current unit mUnit is the target of the threat, for later use. The explicit target does not dodge the threat (sometimes it can’t). If the current unit is not the target, but it is within range of at least one threat, then it finds the closest threat and tries to get out of its range. stayAtRange() calculates a direction and moves that way. There’s no attempt to avoid multiple threats, which are likely for spider mines; Skynet is happy to flee one and run into another. Also, I notice that AOEThreatTracker keeps careful track of each threat radius, but this code ignores it and flees to a fixed radius. I assume that’s good enough? Anyway, this limitation may explain why Skynet does not try to dodge lurker spines, which affect a long narrow area.

If the current unit is the target, the unit stays put so that the other units around it know what to do to avoid the threat. currentTargetUnit is whatever the current unit is already shooting at. This is not best if under attack by a reaver scarab, but it does help nearby units know which way to flee.

	// If the target of a threat, dont do anything to cause it to move
	if(isTargetOfThreat)
	{
		if(currentTargetUnit)
			mUnit->attack(currentTargetUnit);
		else
			mUnit->stop();
		return true;
	}

Skynet has other interesting skills. See BlockedPathManager and MineBlockingMineralTask, for example. But that’s enough for now.

Tomorrow: Wrapup and lessons learned.