archive by month
Skip to content

AILien > XIMP

AILien scored a fine win over XIMP. The game plan to defeat Tomas Vajda’s carrier bot:

  1. Take 7 bases and grow a huge economy.
  2. Prevent XIMP from expanding beyond its natural.
  3. Don’t bother to fight the carriers, just accept losses. With enough units on the map, the carriers chose to destroy defenseless zerg units to protect protoss bases and did not visit a zerg base until too late.
  4. When hive tech is ready, adrenal glands for the zerglings and the ultralisk upgrades, go win. Zerg can send units faster than protoss can kill them.

Ha ha, so simple! AILien made hydralisks, but I didn’t see them fire a shot against carriers or interceptors. That would have been too easy! No, XIMP is not properly put in its place unless zerg handicaps itself with buggy hydras!

I’m too busy to pull images, but watch the replay at the link. Let’s see how long the replay stays online.

scourge

Why do so few zerg bots make scourge?

A pair of scourge costing 25/75 can kill a wraith or corsair costing 150/100, or even a valkyrie costing 250/125, such a favorable ratio that zerg can afford to lose some scourge along the way. If the enemy flees then the scourge may kill nothing, but to send the enemy running is a victory in itself. If you have a spire, then scourge is a cost-effective part of your air defense and reduces the need for hydralisks or spore colonies to protect overlords and mineral lines. Scourge moves fast, good for scouting, and scourge can intercept drops. Scourge is gas heavy, but zerg usually has more bases and therefore more gas. When facing air units, it is almost always a good idea to have some scourge.

Steamhammer is not skilled with scourge. It maneuvers poorly, failing to attack when it could or losing scourge unnecessarily. It attacks poorly, sending too many to one target and not enough to another. And it doesn’t know the first thing about coordinating scourge with other units. But its opponents are also weak at defending against scourge, and overall I believe that Steamhammer’s scourge is more than worth the cost. In tonight’s game versus Tyr by Simon Prins, Steamhammer scourged science vessels one after another, butting heads with the terran army and holding even—until finally one vessel lived long enough to get an irradiate off, creating an opening for terran to break out. Despite some losses, scourge more than earned its pay, and greater scourge skills might have saved the game.

When I play Steamhammer versions against each other, the fight often comes down to which side lands more scourge hits. Steamhammer does not know that in a muta-scourge versus muta-scourge battle, you commonly want to hit each enemy muta with 1 scourge to leave it too weak to fight on, rather than 2 scourge to kill it (wasting most of the explosive power of the second scourge). If I taught it that....

A zerg with razor-sharp scourge skills would be a terror of the air.

various Steamhammer developments

SSCAIT now recommends Steamhammer as a starter bot, as Krasi0 pointed out in a comment. Over time I expect this to add cooperation overhead and slow down my development, but also to end up improving my code quality. For now, since there are only a few forks, send bug reports and whatever to me by e-mail or as blog comments. It seems likely that more forks will follow, and I will have to bring up a public issue tracker one way or another. I’ll think of a plan.

Switching my web site to https is also important as people download more often. I have to invest time into that soon.

A minor update to Steamhammer’s web page adds a development roadmap, makes my e-mail address easier to find, and has a few other tweaks.

Steamhammer 1.1 development is running smooth. My change log is similar in size to the 1.0 change log, which took longer. The new zerg macro code is complete and working, though it needs adjustments to do fewer stupid things and more smart things—I won’t let the new release play worse than 1.0. I currently have (... counting ...) 10 critical bugs and a few other high priority issues. I’ve been both finding new bugs and retiring issues at a high rate, so it’s not clear how long it will be before release, but it could be this week.

More documentation is on the menu for the next version, so people can get underway more easily. I’ll make the release then write starter docs shortly after.

thou shalt not divide by zero

Steamhammer is mostly reliable, but does crash occasionally. When SSCAIT catches a crash, it saves a couple of low-level dump files that offer a hope of tracing the cause.

Today I got a game with a division by zero crash, which was infuriating. Division by zero must die! Zero tolerance of zero! I decided to eliminate all bad divisions by inspecting every division “/” and mod “%” in the code, though it was a little tricky when the comment delimiter is “//” and “%” is used in strings.

	// don't even enter the same city as division by zero

I did it, and shored up a few weak spots. No direct division by zero should be possible in Steamhammer. I also added error checks in the hope of catching bad parameters before they are passed to BWAPI, which looks like the cause of other rare crashes that I have never been able to trace.

I tell the story as an example. If you want to write reliable software, you have to be thorough. I fix all reproducible crashes immediately, so that (so far) crashes on the server are unreproducible and difficult to solve, but I don’t let that stop me.

how to beat Stone, according to AIL

I took a little time off from my main goals for Steamhammer 1.1 development to follow AIL’s advice on how to beat Stone. AILien’s implementation is in WorkerManager::handleCombatWorkers(), so I guess AILien’s drones are given combat orders when an enemy is very near. The suggestion was for drones to attack enemies that came within range 50 and were not moving.

My implementation is a little different. I put it in WorkerManager::updateWorkerStatus() and was careful to disrupt mining as little as possible. I tried ranges 48 and 64 and decided that 64 worked better. The else if condition ensures that a worker which has been fighting returns to mining as quickly as possible (idle workers are put back to work within the same frame). A possible disadvantage is that gas miners do not defend themselves (but they never did before, either, and it’s not hard to add).

		// If the worker is busy mining and an enemy comes near, maybe fight it.
		else if (workerData.getWorkerJob(worker) == WorkerData::Minerals)
		{
			BWAPI::Unit target = getClosestEnemyUnit(worker);

			if (target && !target->isMoving() && worker->getDistance(target) <= 64)
			{
				Micro::SmartAttackUnit(worker, target);
			}
			else if (worker->getOrder() != BWAPI::Orders::MoveToMinerals &&
				worker->getOrder() != BWAPI::Orders::WaitForMinerals &&
				worker->getOrder() != BWAPI::Orders::MiningMinerals &&
				worker->getOrder() != BWAPI::Orders::ReturnMinerals)
			{
				workerData.setWorkerJob(worker, WorkerData::Idle, nullptr);
			}
		}

I also had Steamhammer build a sunken when it could. That freed up zerglings from chasing SCVs so that they could pay a visit to Stone’s base. Games became short and decisive. In a test match, Steamhammer beat Stone 15-0. The closest it came to a loss was getting down to 3 drones before the sunken came on line; most games were one-sided.

Steamhammer 1.1 will take that much longer to come out, but it will be that much stronger. The bot should lose fewer drones to harassment by the enemy scout, which will help in many matchups.

Update: The code has a bug! To fix it, change this condition

			else if (worker->getOrder() != BWAPI::Orders::MoveToMinerals &&
				worker->getOrder() != BWAPI::Orders::WaitForMinerals &&
				worker->getOrder() != BWAPI::Orders::MiningMinerals &&
				worker->getOrder() != BWAPI::Orders::ReturnMinerals)

to this, adding one more check:

			else if (worker->getOrder() != BWAPI::Orders::MoveToMinerals &&
				worker->getOrder() != BWAPI::Orders::WaitForMinerals &&
				worker->getOrder() != BWAPI::Orders::MiningMinerals &&
				worker->getOrder() != BWAPI::Orders::ReturnMinerals &&
				worker->getOrder() != BWAPI::Orders::ResetCollision)

Drones (and I presume other workers) go into ResetCollision mode for just one moment as they finish mining minerals. The effects of the bug are subtle but far-reaching. Drones going toward the minerals have the mineral-mining job. Having finished mining, they hit ResetCollision and, since they are carrying minerals, end up with the return-cargo job until they return their minerals to the hatchery, when they go idle again and are reassigned to gather minerals. (Returning cargo is not part of UAlbertaBot. I added it to reduce unnecessary loss of resources.) Mining appears to proceed normally, but behind the scenes drones are switching jobs unpredictably. You can no longer accurately count your mineral drones. Other tasks looking for mineral drones do not accept return cargo drones, which can cause buildings to be constructed slightly later. The effects are small and not immediately noticeable, but harmful.

Sneaky sneaky one!

Tscmoo zerg is back!

Tscmoo zerg had been disappointing recently, but today it was updated and went on a rampage, scoring 4-2 against Killerbot, Bereaver (2 games), LetaBot, Krasi0, and Iron.

Tscmoo played similarly against all opponents, no matter the race. It opened 9 pool, safe against cheese and intimidating some enemies into overcommitting to defense. The rest is adaptive, but when the coast was clear, it followed up with two more hatcheries before getting gas and a hydralisk den—no matter the opponent. It dedicated itself to the long game by starting carapace upgrade before hydralisk speed or hydralisk range, an unusual and risky decision. And it took a distant “hidden” third base before spreading to the rest of the map, adding to the risk (Bereaver took advantage in one game).

Hydralisks were not always the right choice, but Tscmoo made them work with good surrounds, careful micro versus shuttle-reaver and spider mines, and distraction plays like 4-hydra drops. It made fewer mistakes.

Will Tscmoo zerg will fall back to another strategy if the hydras don’t work against some opponents? I guess we’ll see.

Steamhammer’s 2 strategies vs XIMP

Steamhammer 1.0 got to try both of its prepared strategies versus XIMP in close succession. Did somebody schedule the games?

In the first game (Brood War replay file), Steamhammer went mutalisks. XIMP counters with corsairs, so I was taking a risk. The strategy is inspired by Sijia Xu's Overkill, which plays a 2-hatch muta opening that smashes XIMP before protoss reacts.

Instead of a quick air attack like Overkill, Steamhammer went three hatch before pool and prepared a heavy blow. The build produces a large first wave. XIMP saw the lack of ground pressure and built fewer cannons than usual, only 5. The mutas started in on the cannons, but had only killed 2 before corsairs chased them away. With the slower build, XIMP was able to react correctly.

Steamhammer strikes the cannons

When Steamhammer has a spire and scouts air tech, it makes 1 pair of scourge as insurance. When it verifies enemy air units, it aims for exactly the right number of scourge to clear the enemy air force. The game became a muta-scourge versus corsair seesaw. XIMP saw air units and felt the need to keep investing in corsairs, so it could not make carriers. Steamhammer pruned back the corsair numbers with scourge, but was short of gas. Neither side could keep a large force in the air.

XIMP took a third, and Steamhammer took bases down the left side of the map, struggling to get more gas. But the new gas came online slowly, and zerg resorted to making zerglings—which is what it needed all along! The lings tore down the protoss natural, and then XIMP could not keep up. It was an entertaining game.

Steamhammer hits XIMP’s natural

In the second game (Brood War replay file), Steamhammer went hydralisks, the safe and natural choice. Hydras attacked toward the protoss natural and danced with the cannons and carriers for a while, killing some cannons and then most interceptors. When enough hydras had accumulated, they cleared the cannons. At the same time, scourge arrived and exploded carriers. The game looked over.

Steamhammer fights cannons and carriers

But no, no easy win. Steamhammer hit a bug which made it unable to produce any more hydras. Scourge were able to hold off carriers for a while, but without ground attack there was no hope. Solving all the similar bugs is a major goal of version 1.1.

Steamhammer beats Iron

Today Steamhammer 1.0 got to try, for the first time, its planned strategy versus Iron (Brood War replay file). It worked as intended, and Steamhammer won quickly. In the screenshot, Iron placed both its marines on the ramp, which made it easy. In my tests, the ramp still breaks and the bunker falls if Iron gets marines into the bunker.

Steamhammer breaks Iron’s ramp

I’m sure Igor Dimitrijevic will update Iron before long, but for the moment Iron loses to a standard 9 pool speedling opening—an opening that Steamhammer also plays 10% of the time versus other terrans, with fair success. Iron’s zergling defenses at that timing are not enough; the lings overwhelm the blocking SCVs and the bunker. Try the same against LetaBot and you will die!

By the way, getting zergling speed is critical versus terran. Zergling speed gives tactical and micro advantages against all races, but bots play poorly and don’t gain full value from the advantages versus protoss or zerg. But terrans have ranged units, and the gain from spending less time under fire before reaching the target is giant. It takes many more marines to overpower fast zerglings; marines should be unable to move out onto the map at all until academy tech. My suggestion: By the time you have 6 or more zerglings versus terran, you should at least be collecting gas to start Metabolic Boost. I think the main exceptions are unusual low-economy builds and zerglings for immediate emergency defense.

recreating the Newbie Zergrush strategies

Newbie Zergrush has vanished like the morning dew. If you want to test against its nonstandard strategies and missed the boat, I reconstructed two of them. You can configure Steamhammer 1.0 to play these openings. The experience is not 100% authentic, but it’s close. I made these by watching the build orders, not by copying the original configuration, so there may be slight differences. Also there should be code differences, so the way the openings play out should be a little different. It’s still a good test of whether your bot survives these aggressive strategies.

Newbie Zergrush played 7 pool 12 hatch versus zerg and 8 hatch 7 pool versus terran and, at least at first, protoss. It also had a mass sunken build which I did not try to recreate.

    "7Pool12Hatch"  : { "Race" : "Zerg", "OpeningBuildOrder" : ["drone", "drone", "drone", "spawning pool", "go scout location", "drone", "overlord", "zergling", "zergling", "zergling", "zergling", "zergling", "hatchery @ min only", "zergling", "zergling", "zergling"]},
    "8Hatch7Pool"   : { "Race" : "Zerg", "OpeningBuildOrder" : ["drone", "drone", "drone", "drone", "hatchery @ min only", "spawning pool", "go scout location", "drone", "overlord", "zergling", "zergling", "zergling"]},

I cheated and cut these down to keep the post short. At the end of each build order, use the power of copy-paste to extend the line of , "zergling" out a long distance. That’s because the strategy manager doesn’t know these openings, so it will start an unhelpful default strategy as soon as the opening book ends.

Get the Steamhammer 1.0 binary release from from my page or from SSCAIT. I wrote documentation for the config file. Add the two opening book lines above (with the extra zerglings) to the “Strategy” subsection (be careful about commas, JSON files are unforgiving). Then configure Steamhammer to play the opening against your bot. For example, if you’re testing a terran bot, one way is to rip out the original ZvT setting and replace it with "ZvT": "8Hatch7Pool",.

Steamhammer status update

Steamhammer 1.1 is in the works. It is currently short 1 major feature, 2 smaller features, and 7 critical bug fixes that I know of, so it is still a ways off. When it does come out, the headline feature will be the ability to transition from one unit mix to another in the middle game. Currently, all transitions are written into the opening book, obviously a severe limitation. The work involves completely rewriting the macro code, and I’m only part way through.

I keep changing my mind about the order of improvements. Here is an outline of what I’m thinking today, so that when I do something completely different, nobody will be surprised.

The basic order of middle game skills should be mutalisk skills, then lurker skills, then drop and runby skills (which are related in a way). For the benefit of people who fork Steamhammer, I want to make sure that all races work and that the bot can play random—it’s theoretically possible today, but I haven’t tested it so expect some bugs. It’s also a priority to upgrade base defense in general and worker defense in particular. In long macro games Steamhammer 1.0 does stuff which is bananasuperbeyond boneheaded, which I’ll have to ameliorate a little at a time. Scouting has got to be worked on and must tie into tactical decisions better. Upgrades need to be more aggressive, and the first hive units should be ultralisks. I really really want to make a start on opponent modeling soon. And I have many other plans that I’ve written down but not put into priority order.

Anyway, here are today’s plans for the headline features of the next few versions. I’m expecting many extra features and fixes in 1.1 and few extras in 1.2 (and beyond that I don’t want to predict).

  • 1.1 - transitions
  • 1.2 - test and fully support random play, like UAlbertaBot
  • 1.3 - mutalisk reign of terror

After starting work on 1.1, I’ve already changed my mind twice about what to do when, and I may rethink again without warning. I shouldn’t try to plan further ahead.

Now I need to put my head back down and return to work.

2016 and 2017

Now that the new year—wait wait wait, how did it get to be February already? Well, I’ll go ahead and ignore the mistake in the calendar. No need to file a bug report, I’m sure they’re on it.

2016 brought big progress in BWAPI bots. Killerbot by Marian Devecka finished #1 in SSCAIT 2015 with a 96% win rate in the round robin, seeming nearly unbeatable. But even after late updates, it landed #6 in the SSCAIT 2016 round robin with 85% win rate. The 5 bots that finished above it had all seen updates repeatedly during the year.

Iron brought hyperaggression and amazing micro. Krasi0 fixed crashes and weaknesses and became more aggressive. Sudden sensation Bereaver put together a variety of skills in a balanced package to reach the top ranks. ZerGreenBot showed us the first skilled overlord hunting and the first skilled reaver drops, both of which were then picked up by Bereaver and Tscmoo protoss. Zia gave a taste of zerg variety with opening learning. Even ZZZKBot by Chris Coxe, less heavily updated than the others, proved that it is still possible to beat many opponents with prepared strategies. Overall, the meta shifted somewhat away from rush strategies toward macro play, though rush defenses need to be strengthened further before the shift will be complete.

In 2017 I see progress speeding up further. The community is larger and more active. I see people in the chat talking about their plans for new skills. Openbw is preparing new tools. Bots are playing an ever-wider range of strategies. The level of play is higher, and bot authors are adapting to it in a virtuous cycle of improvement. 2016’s top Krasi0 and Iron are updated, and so is Tyr by Simon Prins. Many of the fresh bots popping up during the post-SSCAIT surge are starting out stronger than new bots did last year (except for Bereaver, of course). I think both AILien and my own Steamhammer are at risk of becoming top bots and having to work to stay there, and the new from-scratch bots like McRave and ZurZurZur are improving rapidly and may catch up.

Even today’s entrant “Newbie Zergrush” is causing trouble for one opponent after another with its simple nonstandard opening of 8 hatch 7 pool (at least against protoss and terran; it played differently in its one game vs zerg so far). That rush gets zerglings over a minute later than 4 pool, but with 2 hatcheries the numbers grow fast and it seems that many bots were caught sleepy-eyed. XIMP did not add cannons or send defenders in time, and Krasi0 was surprised by the timing too. Steamhammer, by the way, knows a similar opening of 9 hatch 9 pool 9 gas, which gets the first lings 2 seconds later (seriously!) and also researches speed and boots up a stronger economy with greater chances to transition later. Steamhammer only plays it versus zerg, though. Now I’m thinking I should have enabled it versus protoss and terran too.

In 2017 I predict multiple bots with new skills of taking island expansions and carrying out doom drops. For Steamhammer I also plan deeper map analysis so it can do things like backdoor attacks when the map allows it. The tactics and strategy in bot play will grow more complex, and everybody will be scrambling to keep up.

SSCAIT semifinals and third place playoff, part 2

Today is the second and more interesting part of the SSCAIT 2016 round of 4. Krasi0 fought XIMP, with the winner to move on to the final. The loser went to a 3rd/4th place playoff match against Iron.

Krasi0 vs XIMP

Game 1 was on Empire of the Sun. XIMP is the carrier bot. Terran Krasi0 saw what was coming and chose to attack with tanks and early goliaths before the carriers had all their interceptors. The tanks were somewhat inefficient in sieging down the cannons (most bots are), but they were fast enough. XIMP was in trouble regardless, but losing too many probes sealed it.

Game 2 was on Heartbreak Ridge, a 2-player map. It has many cliffs which favor carriers, but it also has low-ground bases and a short ground distance between the mains, which favors mech attacks. Krasi0 attacked with the same timing as the previous game. Krasi0 again killed the natural, but terran reinforcements chose a path through the center which took them next to the protoss base, distracting XIMP into playing correctly against the reinforcements instead of the main terran force. Krasi0 could have sent fresh units the other way around the center of the map, where carriers could not have engaged them until they arrived. Then the mech army would have stayed compact.

But as it happened, the diversion toward reinforcements and Krasi0’s difficulty in attacking the protoss main through the very narrow entrance meant that the carriers were able to build up interceptor numbers. XIMP defeated the goliaths and started chasing the terrans away. Krasi0 sent goliaths piecemeal toward the front and lost them a few at a time.

While one carrier group fought terran units around the center, another group departed on XIMP’s trademark edge crawl to survey the bases around the map and attack from the rear, a slow but dangerous plan. Both players were in some trouble. XIMP had only its main left and no long future unless it could take and hold another base, while Krasi0 had 2 bases but not enough anti-air to stop the carriers. What would happen?

XIMP’s front carriers played poorly and lost numbers. They could have returned through the center to cover while XIMP retook its natural, or they could have joined the attack on the main where cluttered buildings made goliaths awkward, or they could have circled the terran natural and attacked it from above the cliff, but they insisted on attacking from the front without the advantage of a cliff. With that decision, Krasi0 had the lead. Terran should have sent its ground army, minus air defense, to the protoss base, which would either cause a base race or bring the carriers back home to defend. Instead Krasi0 left tanks and vultures idling around the center of the map. Krasi0 did not use its lead.

Neither side was making the most incisive moves, and the outcome was unforeseeable. Fun game!

Krasi0 got a third base up. XIMP had no money but destroyed the terran main, slowly. Krasi0 did not try to float its factories, but rebuilt from scratch instead. Finally Krasi0 built up enough stuff to start hitting carriers, and that was enough.

Both players fought like lions. After mistakes all around, the victory was narrow. Krasi0 won and went to the finals.

Lesson: You never know what little thing will make the difference. A small improvement by XIMP (try to attack from above cliffs, or destroy bases more efficiently) or a minor weakness in Krasi0 (build the 3rd too late, be unable to recover from losing all tech) could have given us a different result.

Iron vs XIMP

Iron is far more aggressive than Krasi0. Iron attacked with infantry and tanks, sieged down the cannons, held off carriers with marines and turrets, finally added valkyries almost as an insult, and won easily. The second game went about the same way. A group of carriers escaped and caused damage, but only until the undefended protoss base died.

It’s not easy to beat XIMP, or more bots would do it, but Iron made it look easy.

the big BOSS bug with zerg

UAlbertaBot includes BOSS, the Build Order Search System, which accepts goals like “I want 12 zerglings and the speed upgrade” and calculates how to achieve them as quickly as possible, including creating the spawning pool and extractor if necessary, adding supply, building the ideal number of workers, and the exact spending sequence for everything. It does make approximations, but the build orders it produces should be close to optimal for the goal you give. UAlbertaBot by default uses BOSS to oversee its production as soon as it gets out of its opening build order.

Unfortunately BOSS also has limitations and bugs. If you give it a complex goal, it can’t run in real time (though you can still use it to create opening builds offline). And for zerg, BOSS has the bug that it will sometimes throw in an extra building that is not implied by your goal, usually an extractor, hydralisk den, or lair. I only wanted zerglings, why are you giving me a hydra den?

Blog reader Micky Holdorf looked into that bug and found the cause. Thanks for the report!

BOSS wants to search efficiently, so given your goal, it calculates the prerequisites so that it doesn’t waste time searching actions that don’t contribute to the goal. It calls this the goalMax, the most of each thing that you might want in order to achieve the goal. For terran and protoss, BOSS recursively calculates all the prerequisites and carefully counts each. Here is the code for zerg, from DFBB_BuildOrderSmartSearch::setPrerequisiteGoalMax():

    else if (getRace() == Races::Zerg)
    {
        _goal.setGoalMax(ActionTypes::GetActionType("Zerg_Spawning_Pool"), 1);
        _goal.setGoalMax(ActionTypes::GetActionType("Zerg_Extractor"), 1);
        _goal.setGoalMax(ActionTypes::GetActionType("Zerg_Lair"), 1);
        _goal.setGoalMax(ActionTypes::GetActionType("Zerg_Spire"), 1);
        _goal.setGoalMax(ActionTypes::GetActionType("Zerg_Hydralisk_Den"), 1);
    }

That’s not a calculation, that’s a stand-in! This part of the code was never written in the first place. I wonder if there was a bug in the recursive calculation for zerg, and Dave Churchill put in a stub until it could be dealt with?

Anyway, the correct fix is to write that code. One workaround is to delete the setGoalMax calls for zerg and always pass in goals that include their own prerequisites; you have to remember that you might need a spawning pool, or notice that you’ve lost yours and explicitly put it in. Micky Holdorf found another workaround with the same effect, which tells BOSS not to worry about prerequisites at all. In DFBB_BuildOrderStackSearch::generateLegalActions() change the line

            if (!goal.getGoal(actionType) && !goal.getGoalMax(actionType))

to this, which tells it that only goals and not prerequisites are legal actions:

            if (!goal.getGoal(actionType))

I don’t have any plans to write a fix for Steamhammer. If somebody sends me one I’ll certainly consider it! But Steamhammer is moving away from BOSS, because BOSS answers the opening question “how do I get this stuff as fast as possible?” and not the middlegame question “how do I spend my resources efficiently?”