archive by month
Skip to content

a new secret weapon in ZvZ

Yesterday I watched a pro ZvZ game from 2010 where the winner played an opening I had not seen before. “Hmm, does that have good timings against overpool?” I thought. It seems to be a rare opening and is not documented on Liquipedia, but I coded up a version and tested it out. The scores are Steamhammer 1.2.2 set to play one fixed opening, versus each opponent.

victimscoreversion
Ailien13-2uploaded 4 April
Killerbot14-1SSCAIT 2016
Microwave14-10.15, uploaded 5 April
Steamhammer11-41.2.2, latest release with 11 ZvZ openings
Zia15-0uploaded 7 September

Holy Saint Isidore, Batman! Bots are not prepared for this!

Killerbot scored 1 win when Steamhammer made a tactical mistake of a kind that I intend to fix in the upcoming version. Microwave was able to exploit a risk inherent to the opening—once. Zia tried its builds one after another and lost with all of them. Ailien’s machine learning came the closest to finding an answer; it tried different tacks, and a few of the games were fierce. By the end of the match it seemed to be grasping at straws: Do hydras work? No! Do lurkers work? No! Every fixed opening has counterbuilds, and a human player who knew what was coming would find it easy to beat. It is possible that the counterbuilds are outside Ailien’s gamut, or take over 15 games to find. The release version of Steamhammer itself put up the strongest fight but was forced down.

Of course Steamhammer does not play fixed openings outside of tests like this. I think that adding the new opening to its repertoire is likely to make Steamhammer impervious for a long time to machine learning attacks on its openings.

I’ll be holding my secret weapon in reserve until version 1.2.3 comes out. Everything will be published then, but I also enjoy springing surprises. This will change the meta, and I would like to be 2 steps ahead.

binary anniversary

Yesterday’s post was the 256th blog entry, which makes it an important anniversary. How many blogs make it to 2^8 posts in hardly more than a year?

The first post was on 2016 March 4. In May I made only a few posts while I worked on the blog software, but even including the gap, I have posted on more days than not. As I write, the blog has 747 published comments.

A success! And my it be useful for BWAPI coders everywhere.

Steamhammer 1.2.2 change list

Steamhammer 1.2.2 is uploaded. It is a bug fix release, mostly to fix strength-reducing bugs introduced in 1.2.1.

Next will be 1.2.3, primarily a feature release to help all races play stronger and more interesting games, then 1.2.4 to fix lingering bugs.

bug fixes

6 bugs are fixed, 3 serious bugs that caused losses for all races in version 1.2.1, and 3 zerg bugs that are smaller and didn’t cause any losses that I could identify. There are still no known crashing bugs.

• The bug in retreating to the natural is fixed. When retreating with no fresh units on the way, Steamhammer now retreats to the natural if it has been taken, or to the main otherwise. Retreating blindly to the natural caused many losses when the bot did things like retreat from the main to the natural when the enemy was at the ramp. (Squad::calcRegroupPosition())

The retreat system is confused in the first place. The first choice of retreat point is the closest friendly unit that is not “near” the enemy. It is supposed to close up gaps in the formation—that is why it is called “regrouping”. But in practice the closest friendly unit might be anywhere, and Steamhammer may retreat to the natural, to the main, or in a direction that makes no sense at all. Only when there is no friendly unit to retreat toward does it retreat to the natural or main as appropriate.

• Errors related to pulling workers for emergency defense are fixed. There were subtle issues causing workers to be left in the base defense squad after they were supposed to be released. It sometimes caused weird worker dances and other bad behavior. I also tightened it up; workers are now pulled to fight a proxy only if no combat units are out yet, and to fight zerglings only when they come very close to the mineral line, and otherwise not at all. Steamhammer will self-destruct less often, and (I hope) continue to successfully defend itself when it can. (CombatCommander::updateBaseDefenseSquads()—I renamed it for consistency.)

• A change in 1.2.1 to put more idle workers back to work introduced a bug where it also put building workers back to work before they started building, sometimes delaying the start of construction by as much as a few seconds. When timing is critical, it causes losses. In particular, it caused the 9 hatch opening to lose to 9 pool, when it should defend in time. Excess worker pulling contributed. (WorkerManager::updateWorkerStatus())

Lukas Moravec has found another bug that makes buildings start later, but the effect is smaller. I decided to delay fixing it until I understand it thoroughly.

• Zerg mistakenly made sunkens to defend against air attack. Oops. (StrategyBossZerg::checkGroundDefenses())

• Zerg was ignoring its absoluteMaxDrones value and making too many drones in long games, cutting into the supply available for its army. (StrategyBossZerg::updateGameState())

• Zerg catches more cases of deadlocks that cause production freezes. It should only have happened when Steamhammer was already in trouble, though. (StrategyBossZerg::nextInQueueIsUseless())

other improvements

• I tuned up the ZvZ 12 pool opening, and adjusted some opening settings.

• The true/false configuration options KiteWithRangedUnits, WorkersDefendRush, and ScoutHarassEnemy can now be set differently per race, to better support playing random. The feature is not used in the default configuration; all races are set to the same value. I also removed UseSparcraftSimulation, since it was already unused in the code.

Stone

This version does do worse versus Stone. In a test match, it scored 12-3 with 2 close calls where it went down to 2 drones. When worker defense was first implemented, Steamhammer scored 15-0 with 1 close call where it was taken down to 3 drones. The cause seems to be the return cargo bug fix. The effect is hard to see, but it makes workers more likely to stop and defend themselves in spots where other workers will not help them out. I decided that the correct fix is to improve worker micro, and I didn’t want to try something so delicate in a bug fix release.

Steamhammer 1.2.2 getting extra tests

Steamhammer 1.2.2 needs no more work, but the SSCAIT stream seems to be down so I guess there’s no point in uploading it right away. I’ll use the time to run extra tests and make extra sure that it’s extra solid. The last release was not tested carefully enough, but this one should be good.

I put in 3 serious bug fixes that affect all races, 3 lesser bug fixes for zerg, and 2 minor improvements. Version 1.2.1 was weaker than 1.2; if I did my job, version 1.2.2 should make up the difference and then some.

Steamhammer is failing on bgh

It’s April 1, time for Big Game Hunters, and—ack! Steamhammer is refusing to start up on the map! Another bug to fix, and one that may not matter again until next year. I regularly try it on maps outside the SSCAIT pack, but not on maps this strange.... Should I add another test to my release checklist?

Update: It’s working for me locally, with the WebMaps version of Big Game Hunters. What version of bgh is SSCAIT using?

Steamhammer 1.2.1 docs, 1.2.2 plans

Documentation is written, a bit slowly, as usual. Updating for future versions will be quicker, also as usual. I am now allowed to touch the code again.

It’s short. Is it good? Will it help people get started (or decide whether to get started)? It’s only possible to write documentation if you know details, and it’s only possible to judge documentation if you don’t, so... I’m listening.

version 1.2.2

Version 1.2.1 seems to be 100 points weaker than 1.2, rather than 20 points stronger as I expected. Almost all the strength loss seems to be due to the single retreat-to-the-natural bug. Nevertheless, doing my best to follow the zero-one-infinity rule, I’ve identified a half-dozen quick fixes for version 1.2.2. Expect it in 2 days, plus or minus 1 day.

Steamhammer 1.2.1 early results

1. The feature of retreating to the natural base if it exists is buggy. Steamhammer always retreats to the natural, whether it has been taken or not, causing trouble especially in ZvZ games. This is hilarious, if “hilarious” means “it’s bad, but at least it’s easy to fix.” I added the feature at the last minute with only a brief test: Tally another loss for poor testing and inadequate commenting!

2. Meanwhile, units that retreat to recover from their damage still return to the main, not the natural. In general, the code has confusions between retreating the squad and retreating individual units to regenerate (which only applies to melee units, and only if configured). It doesn’t seem urgent to fix, though.

3. A unit which is inside the minimum range of a sieged tank refuses to retreat with its squad. It doesn't necessarily attack the tank, but it attacks something. This behavior has only triggered a couple times, but so far it looks like a successful improvement.

4. The system of pulling workers for defense is questionable. If it made correct decisions, it would be great. As it stands, it is sometimes good and sometimes bad, and I can’t judge whether it is worth it overall. It averages out pretty close to zero, maybe?

5. I thought the loss against Stone was due to a new weakness, but on a closer look I don’t see one. It’s the first loss against Stone since worker harass defense was implemented, so I’ll test more closely to make sure.

6. Speaking of worker harass, gas workers now defend themselves the same way as mineral workers. I don’t know whether this will turn out to be a win, because harassed gas workers get switched out; harassment can potentially disrupt gas mining. So far, this behavior has triggered against LetaBot, when it looked highly effective. But LetaBot is a lazy harasser, so the jury is still out.

7. Other surprise losses by Steamhammer were due to randomly choosing openings that the opponent countered. For example, in the first ever loss against protoss Lukas Moravec, Steamhammer went 5 pool, which was objectively poor against the 2 gate opening. Steamhammer nevertheless pulled ahead and was winning until it bungled its tech switch.

I’ll be inserting another bug-fix release to solve the retreat-to-natural bug before I get down to features.

Steamhammer 1.2.1 is out

Steamhammer 1.2.1 is out. All races should play more strongly. Steamhammer 1.2 kept up an average elo in the high 2200s, peaking over 2300. I think 1.2.1 is likely to average over 2300.

I was originally planning more improvements for protoss than for other races, but it turned out differently. Terran got the most improvements, and protoss the least. Terran needed them more. Version 1.2.2 is planned to focus more on features than on bugs, and will have some protoss features.

Web site update tomorrow, with source code.

I tried to make this change list more thorough and detailed than earlier ones, since more people are using Steamhammer code. It’s a nice long change list; I got a lot done this time.

config file

• Some options can be set differently for each race, to support playing random.

• New build order commands “go defensive” and “go aggressive”. Defensive means that your units stay at home for the time being; aggressive means that they seek out the enemy. The default is to be aggressive all the time, which is good for zerg. For terran, “go defensive” is good at the start of most openings. For protoss, I had the dark templar rush stay defensive (with its one zealot and one dragoon) until the dark templar start. If you don’t go aggressive in the build order, aggression automatically turns on when the opening build order is over. For now, the only way to stay defensive longer is to write a longer build order (or write code).

• New debug option drawUnitOrders. Besides unit orders, it draws on the screen what a research building is researching and what is going to come out of an egg, cocoon, or production building. I’ve been using the option often.

macro changes

• Updated terran and protoss openings and production code.

• Terran and protoss build more workers.

• All races pay closer attention to the configured maximum number of workers per mineral patch. WorkerManager::getMaxWorkers() gives the largest number of workers that can efficiently mine your existing bases, based on the configured values and the resources at the bases.

• The scout defense worker (used only by terran, in the default configuration) is less likely to move out while carrying minerals. Losing minerals is bad luck.

• More idle workers are recognized and put to work.

• Choosing a new main base caused mostly minor but widespread confusion, now fixed. Various bits of code were unclear about the difference between the start location and the current main base. The most obvious effect was that the first expansion sought after a new main base was chosen was always the start location, which had usually just been destroyed and might still be occupied by the enemy.

• Protoss is less likely to overstep the time limit when its main fills up with buildings. The problem turned out to be fundamental to the building placer, and I resorted to cheap hacks. Someday I’ll spend a few weeks to write a proper building planner with more capability. For now I tightened up protoss building placement so that more buildings fit, spread pylons farther apart so they power a larger area, and taught the production manager not to retry the building placer on every frame after it fails once (which caused a huge slowdown). All 3 measures together are still not sufficient, so I may do more in the next version. One cause is that BOSS orders up more gateways than it needs; its production plans are not as efficient as promised.

• The zerg strategy boss discounts extractors at destroyed bases. It was a minor bug that I could fix in passing.

• Zerg doesn’t make double queen’s nests, a rare bug.

• Zerg will no longer order up a hive while overlord speed was still researching in the lair, causing production to pause until overlord speed finished.

• When zerg has extra larvas and minerals and is short of gas, it goes ahead and makes zerglings. Version 1.2 will often wait for gas, letting minerals build up and reducing total production. Steamhammer’s late game armies will tend to be larger.

micro and tactics changes

• Turned special vulture kiting back on. Vulture micro should be improved.

• Terran by default uses the scout defense feature, unlike protoss and zerg. Terran needs to take extra measures to protect SCVs that are constructing buildings.

• Terran loads and unloads bunkers as needed. It’s not very skilled at it, but it does the basic job. I added a new 8RaxDefense opening to illustrate it (and also to hold rushes for which the bot’s micro is too weak).

• Bunkers can be placed tight up against obstacles.

• Terran builds comsat stations and scans cloaked units (even if it doesn’t have anything around to fight them).

• When terran needs emergency detection, it builds 3 turrets, not 2 like before. It seemed necessary.

• Tanks unsiege to fight buildings, or to target enemy units that are too close for siege fire. Tank sieging behavior is still impressively stupid, but it’s a little less bad.

• Protoss is configured to retreat zealots and dark templar which are seriously hurt, hoping they can regenerate shields. (Zerg has retreated weakened zerglings for a long time.) See the RetreatMeleeUnitShields and RetreatMeleeUnitHP config options.

• When zerg sees vultures, it builds a sunken in its natural (unless it already has one). It’s a simple measure to reduce the harm of vulture raids. When Steamhammer accidentally places the sunken in a good spot, it holds out much better against Iron in particular. Future versions will put up stronger defenses.

• A zerg broodling attacks and does not retreat. It doesn’t live long, it can’t afford to waste time.

• For all races, a unit directly next to a sieged tank attacks and does not retreat. If it retreats, it will likely die to siege fire; better to do damage instead.

• Units retreat to the natural, not the main, if the natural has been taken. As suggested by MicroDK. It turned out to be only a couple lines of code, so I threw it in.

• Reavers are targeted with higher priority. It was an oversight.

• Gas workers now defend themselves against harassment, the same as mineral workers. It should reduce worker losses a little more.

• Bugs related to pulling workers for base defense are fixed. The main symptom was that workers at all bases froze in fear, even when another base entirely was under attack. The problem was that putting workers in a base defense squad did not change their job. I had assumed without checking that they would be given the job CombatWorker, but instead they kept the MineralWorker job, so the mineral worker self-defense code interfered. Now workers pulled for defense become combat workers, and WorkerManager is responsible for their combat orders; the squad knows not to issue orders of its own, and resets the workers to idle when they are released. I tried to make WorkerManager a trifle smarter about combat; we will see how that turned out. Finally, I adjusted the conditions under which workers are pulled and tried to make sure that they are never pulled across the map. Zergling rushes are defended again, if configured, and it is turned on by default although I’m not sure it’s a good idea—it sometimes helps and sometimes hurts. In any case, the worst misbehavior should be gone.

• Base defense against air attack now accounts for turrets, cannons, and spore colonies. Fewer defenders are called home if there is static defense to help. (Base defense against ground attack already accounted for static defense.)

• Steamhammer used to go on the attack when its supply went over 170, and revert to normal behavior when its supply fell below 170. When production was high enough it worked well, but other times it caused loops: “Attack! Oh, retreat. Attack! Oh, retreat....” I added hysteresis. Now it starts the attack when supply reaches 195, and does not stop until it falls below 160.

code fixes

• Some cleanups in CombatCommander.

• UAlbertaBot often uses double instead of int where it calls unit->getDistance(), which returns int. It’s not a bug, but it causes unnecessary computation. I fixed some of them, but not all.

ProductionManager works around a BOSS bug in ordering up comsat stations. If you add a second CC and want a total of 2 comsats when you already have 1, BOSS sometimes orders up 2 new comsats instead of 2 comsats total.

• Removed unused code from CombatCommander, RangedManager and MeleeManager.

• Fixed a sneaky crashing bug in StrategyBossZerg::checkGroundDefenses(), the last known crashing bug.

simple SSCAIT rating statistics

I pulled down a rating table from today and calculated a few simple statistics.

countmean elomedian elo
terran2419651884
protoss1820072050
zerg1620132017
random419011909

1. Terran is the most popular.

2. The fact that the mean is higher than the median for terran implies that a few terran bots stand out (like #1 Iron and #2 Krasi0), but most terran bots are weaker. Terran seems more difficult than protoss or zerg.

3. The higher median for protoss over zerg suggests that protoss may be easier to get strong with (contrary to my opinion).

4. Random struggles, as you might expect, but still has a higher median elo than terran.

If you look over the colors on the rating table, you’ll see that terran bunches up toward the bottom and zerg bunches more toward the middle, while protoss is spread throughout. In the upper part of the table, protoss and zerg seem pretty equally represented, though. There is not much difference between them.

Also, 62 bots is a lot!

bugs that do not go quietly

I recently fixed 2 bugs that then turned around and bit me.

I fixed a bug that caused Steamhammer to make double queen’s nests in around 1 game out of 150. It was a simple missing condition. But I made a typo in the fix: Parentheses in a function call swallowed up the next condition in the if. In any safe language it would have failed to typecheck, but in any case, the result was that Steamhammer made 3 or more queen’s nests in every game. I think C++ was laughing at me—”Nyah nyah! I’m too sharp a knife for you!”

I also tracked down and fixed the last known crashing bug, a subtle oversight in the strategy boss that causes crashes in around 1% of games. I brought up a game for a first test of the fix and... crash! Somehow I replaced a rare crash with a frequent crash, and even though I only touched a little code, I haven’t found it yet.

Well, it probably won’t take long—though who knows? After that I have 2 other critical bugs, and then 1.2.1 will be ready.

By the way, Steamhammer does suffer from one other crash besides the “last known” crashing bug. In rare cases, it crashes on startup. But the crash happens before any Steamhammer code executes, so there’s not much I can do about it without taking a long detour.

Microwave

The long-awaited Microwave by MicroDK was uploaded today. Congratulations to MicroDK on an outstanding start, 11-4 as I write including a win over #1 Iron.

One of Microwave’s favorite moves seems to be walking a line of sunkens toward its ramp. It’s kind of cute, and I imagine it’s effective against many bots.

Though Microwave is a fork of Steamhammer 1.0, it seems to borrow some behaviors from later Steamhammer versions too. I noticed that it made 2 evolution chambers but only used 1 of them, a UAlbertaBot limitation. I mentioned the fix to that problem in a comment to AIL, but let me bring it out front: In ProductionManager::getProducer(), add the lines

		if (unit->isUpgrading())   { continue; }
		if (unit->isResearching()) { continue; }

alongside the similar checks.

It also seemed to stumble into bunker range. Steamhammer 1.1 and later do that less often because they understand the range of a bunker. I don’t think of it as a bug fix; it swaps one poor behavior for another that is usually less bad. In Squad::needsToRegroup() where it calculates range, I changed the call to

          int range = UnitUtil::GetAttackRangeAssumingUpgrades(eui.second.type, u->getType());

which I rewrote to pessimistically assume that enemy units have their range upgrades. It also adds in the range bonus of a bunker. UAlbertaBot originally ignores both. Someday I’ll detect these silent upgrades and whether the bunker is loaded, but not yet.

corsair-dark templar

There are two ways to make headway: You can execute your plan better, or you can execute a better plan. In bot development, I think it’s right to spend most effort on polishing what is already done, or adjusting your plan detail by detail. No matter what plan you follow, you’ll meet a huge number of bugs and weaknesses, and they have to be taken one at a time. All the roads are long.

But tscmoo has fun trying out different plans altogether, and tscmoo is a frequent tournament winner. Isn’t there a connection? When you try something new, your opponents will not be ready for it. And we know of strong plans which no bot tries.

Corsair-dark templar is an example. Sair-DT is for protoss versus zerg: Corsairs chase away or kill overlords in an area, and then the dark templar have free rein. I wouldn’t say that sair-DT is a strategy; it is a class of tactical plans which can be fit into a strategy.

I sometimes see bots with corsair skills clear an area of overlords, but the bots do not seem to realize the opportunity. There is a reason they don’t, of course; sair-DT play requires coordination. The corsairs and the dark templar must be ready at the same time to attack the same location, and they have to work together.

But defending against sair-DT also requires coordination, and bots are liable to be poor at it. Think of what Steamhammer does: It makes hydras and individually chases after each corsair that enters its base, scattering its forces. 4 dark templar, even if detected, could kill the scattered hydras one at a time and slip away safely because the pressure on the corsairs is released. Each unit supports the others; it’s not easy to implement, but by the same token it is not easy to defend against.

Here is a common pattern in human games. Zerg owns the natural and main of some start location. Corsairs are flying about, so zerg makes hydras for defense and holds them in front of the natural and collects overlords there. The hydras defend both bases by ground and defend the overlords, while the overlords detect any DTs that try to sneak past. There is still one weak point. Protoss sends corsairs through the main while at the same time dropping DTs from a shuttle in a far corner. When the sairs see that the defenses are weak enough, the dark templar move in and snipe any drones that don’t escape fast enough, then go to work on buildings. Zerg rushes to defend and will try to scourge the shuttle; if the shuttle lives, the DTs will load back up and get away free, counting their loot. I find it hard to imagine any current zerg bot defending well against such a sophisticated attack.

It was tempting, but I decided not to implement sair-DT myself, because it is too far off my path. Anybody could fork Steamhammer and get coding. Or if you have a protoss bot that already knows corsair skills....

resilience in the face of stupidity

Steamhammer 1.1.1 has a lot of resilience improvements over version 1.0. It can rebuild from scratch in a new base after its main is destroyed, for example. I thought that disaster resilience added little to the bot’s strength. In Starcraft, surviving a near-death experience usually means that death is still near.

But lately I notice that Steamhammer 1.1.1 is more consistent than earlier versions. It is rarely upset by much weaker bots, saving many elo points. Unlike in the tournament (version 0.2), most of its upsets are at the hands of opponents which are at least average in skill—and Steamhammer retains its knack of occasionally upsetting stronger opponents.

This game Steamhammer-UPStarcraftAI (elo 2303 versus 1825, compared to the average of 2000) was extremely badly played by both sides. Don’t watch this if you have sensitive eyes, it will hurt! Terran UPStarcraftAI blocked some of its own forces behind its mineral line and was unable to concentrate its strength, while Steamhammer made awful attacks and frittered its mutalisks away, then was unable to defend its natural and lost it. UPStarcraftAI did not know how to proceed and left its victorious army standing idle, and Steamhammer tried to help by suiciding drones and moving zerglings back and forth through the marine formation without fighting (it couldn’t decide which base to retreat to).

Steamhammer didn’t recover neatly, but it did recover. It cleared the idle terrans, built too many drones and took the map while getting upgrades, and finally realized that maybe an army would be useful after all and went ultra-ling, after which (despite some more bad play) the outcome was clear.

Another awful recent game is Steamhammer-DAIDOES (elo 1778), where Steamhammer lost its main but managed to power through its own stupidity—which included stopping at zergling tech level because of a stuck drone—and barely win after mining out.

Resilience against near-fatal disasters is good. Skill in one dimension can rescue blunders in another. The games saved are the ones that would have cost the most elo points—because against a stronger opponent, the game is not saved.

configure Randomhammer differently for each race

I added a feature to make random play more configurable. Steamhammer 1.2 (which is identical to Randomhammer 1.2) has configuration lines to set the maximum number of workers per mineral patch before a base is “full”.

"Terran_WorkersPerPatch"    : 2.8,
"Protoss_WorkersPerPatch"   : 2.6,
"Zerg_WorkersPerPatch"      : 1.8,

For the next version I simplified it (and decreased the numbers, they were too high; someday I’ll do mineral locking and maybe drop this).

"WorkersPerPatch"           : { "Zerg" : 1.6, "Protoss" : 2.3, "Terran" : 2.6 },

And I allow the same syntax for several other config options in the Micro and Macro sections.

"RetreatMeleeUnitHP"        : { "Zerg" : 8 },
"RegroupRadius"             : 600,
"UnitNearEnemyRadius"       : 600,
"ScoutDefenseRadius"        : { "Terran" : 400 }

You can specify an integer to get the same value for all races, or break out different values for each race. If you leave out a race, it defaults to 0. Here I want terran to pull an SCV to defend itself against the enemy scout (within a radius of 400 pixels) to protect its SCVs while they are building; the other races don’t have that problem and keep mining as much as possible (ScoutDefenseRadius 0 means “don’t bother”).

Flash versus Soulkey

If you watch only one pro game this month, I recommend Flash versus Soulkey (the last game in the cast, starting at about 2:07:20 into the video), played today. It is a TvZ with continuous excellent play on both sides, continuing into the late game with all bases taken.

The game is the deciding ace game of the grand final match of the Afreeca Team Battle tournament, a team tournament. The player who wins the game brings tournament victory to his team, so they gave it their all.

Flash is the world #1 TvZ player, and Soulkey has become a top ZvT expert; the game was on a higher level than an ordinary pro game. It is interesting partly because it is standard: Both players followed mainline strategy, Flash going 5 barracks +1 into late game mech switch and Soulkey going 3 hatchery mutalisk into lurkers, then defilers and finally ultralisks. The mainline strategy represents the best known play, and both players kicked it up a notch with sharp attacks and precise defenses.

It was a great game that fans will remember, and there’s a lot to learn from it. From the point of view of a bot author, it is depressing how much game knowledge and skill are visible.

Update: It amused me to collect some quotes from the Teamliquid thread. The first 4 quotes are from before the game finished.

“Really impressive to see Soulkey hold his own against Flash in a macro game.” — ZiggyPG
“This is a GREAT game.” — [[Starlight]]
“hes matching Flash move for move and looking ...good” — Probemicro
“That was some sick moves” — psd
“GG that was an amazing game” — CakeSauc3
“WORTH THE SORE THROAT” — LittLeLives
“I suggest you watch the game, it was incredible” — Piste
“Just incredible level of play in my opinion.” — Netto.
“That ace match was the most beautiful game I’ve seen in a very very long time” — jamesuh