archive by month
Skip to content

bizarre bug

I was making changes to squad membership updating and tactical targeting, which happens once every 8 frames. And I hit a bug which had these symptoms: Steamhammer appeared to run normally, but extremely slowly. Each frame took a second or more. Not each eighth frame, each single frame. Also Steamhammer was unable to recognize its slowness; its timer said that the mean frame took 0.1 milliseconds, normal for the very early game which was as far as I let it run. What could cause such bizarre behavior?

It took a long time to debug. I finally traced it to a misplaced } character which included too much in an if, with the result that an important value was never initialized. It was not easy to see in the code. How this caused a slowdown and how the slowdown stayed outside the timed code (or otherwise broke the timer's behavior) I do not know, and don't expect to find out....

It is worth it to use a prickly language like C++? One wrong touch and you get glochids in your skin for who knows how long. Some days I think about recoding Steamhammer in a pleasant language like Haskell, where new code more often than not works the first time. But that also comes with big disadvantages.

tricky point with MapPartitions

If you use the MapPartitions code that I posted, here’s a tricky point that I ran into.

Units can’t walk on a geyser, so MapPartitions puts the walk tiles of the geyser in partition 0, the unwalkable partition. It stays unwalkable if there is a refinery building on it. So if you check reachability like so, the.partitions.connected(this unit’s position, that geyser’s position) (to build on it), or the.partitions.connected(this unit’s position, that refinery’s position) (perhaps to mine from it, or to attack it), then the answer will always come back—no, you can’t walk there. It’s correct, you can’t walk on top of the refinery!

The correct check is: Can the unit reach any position adjacent to the geyser? The geyser blocks paths, so in principle it might be reachable from more than one partition. Maybe you can get to it from your side and the enemy from theirs, but there is no path between your bases because the geyser is in the way. Weird maps happen. To always get the right answer, you have to check each adjacent tile.

There’s no issue with checking reachability between 2 units, or between a unit and any building other than a refinery building. The ground underneath is walkable, and that is what counts. Only refineries cause extra complexity.

how to specify island openings

By the way, I decided to implement this opening requires that map feature without defaults, like this: If the opening specifies a feature, like "island": "yes", then the feature value is required. If it doesn’t specify, then there is no default value; the map may have the feature or not, the opening doesn’t care. So far it’s as described before, and now I add one more idea: Island openings are playable on non-island maps. They are unlikely to be a good idea, but it will happen against some opponents (I proved it by test). Simply give them a low probability of being played. On an island map, everything else is disallowed, and you get an island opening. On a non-island map, you usually get a usual opening, but occasionally Steamhammer will try out an island opening to see if the opponent is weak to it.

Steamhammer and Sparkle

I’m still preparing for AIST, so expect Steamhammer to participate. The bot is going to struggle, though. There is not enough time to prepare it fully for the maps. Steamhammer will do well only if the other competitors are equally unprepared.

Sparkle is a genuinely difficult map. It requires specialized builds that are not right for any other map, which means that in the proper course of events I ought to delay work on it until Steamhammer can develop its own openings, which won’t be for a time yet. And the map has unique features that require special code. No matter how general I make the code, in practice it will apply to this map alone.

The high ground expansion on the island has zerg buildings that create creep. The creep is an issue for map analysis if you’re not zerg, because BWAPI doesn’t provide a way to find static creep on the map; you have to see it to know where it is, or figure out for yourself how it spreads. The high ground base also has a large crystal on top of the geyser. Any race can built a refinery of its type on the geyser, but the crystal blocks mining from anything other than a zerg extractor (well, you can mine if you glitch each worker through; a bot might be able to do it regularly, but it’s definitely not intended by the map design). Steamhammer is confused by the confusing features and isn’t able to take the base, even as zerg. The low ground expansion has a psi disruptor building on top of the geyser. To my surprise, on the AIST map version it’s also possible to build a refinery underneath the psi disruptor (I tested it by hand with the 3 races). I haven’t seen that in pro games, and assumed the building had to be torn down first. Steamhammer can build an extractor there. But no race can mine from the geyser until the psi disruptor is destroyed.

Yesterday I started on the skill of clearing the neutral buildings that clog up the second and third bases on each starting island. As matters stand, Steamhammer can mine only 1 of the 3 geysers that it should have access to, which leaves it ridiculously weak. The map analysis creates for each base a list of “blockers” that ought to be destroyed, which it outlines on the map in red if map info is turned on. Almost all maps have none. Fortunately the coding and testing only took a couple hours.

Only then did I think through the rest of the steps to implement the skill. There is updating the list of blockers as they are destroyed, of course. Before that is sending a squad to do the work... but hmm, the usual tactical getAttackLocation() only returns a location, not a unit to attack. In fact, Steamhammer doesn’t have any way to order a squad to destroy a unit! I have to create a new kind of squad order, implement it in the squad, and add the tactical analysis to decide when to use the order. It’s likely a day’s work to write and test and finish, more if I hit a snag.

Well, that’s too much effort to support a single map. On the other hand, most of the code is reusable for destroying buildings that block paths, a more common skill though low on my list. I’m still thinking about it. In the meantime, I’ll work on other skills. There’s not exactly a shortage.

ground connectivity code to share

Here is Steamhammer's new C++ MapPartitions class, which calculates ground connectivity. This is a donation to the community. Feel free to borrow it for your own bot, if you like. The download is a zip which contains 4 files: MapPartitions.cpp, MapPartitions.h, and 2 license files. The class contains some code derived from UAlbertaBot, so there is a UAlbertaBot license plus Steamhammer’s license. They are both MIT style licenses, which basically make no requirement beyond including the license file. (It’s irritating that the licensing considerations are as complex as the code, but that’s the world we live in.)

I’ll quote the comment describing it:

// Partition the map into connected walkable areas.
// The grain size is the walk tile, 8x8 pixels, the granularity the map provides.
// Unwalkable walk tiles are partition ID 0.
// Walkable partitions get partition IDs 1 and up.

// This class provides two features:
// 1. Walkability for all walk tiles, taking into account the immobile neutral
//    units at the start of the game.
// 2. Ground connectivity: What points are reachable by ground?

// If two walk tiles are in the same partition, it MIGHT be possible for a unit
// to walk between them. To know for sure, you have to find a path and verify
// that it is wide enough at every point for the unit to pass.
// If two walk tiles are not in the same partition, no unit can walk between them.

Concretely, the interface lets you directly test the walkability of a position, find the partition ID of a position, or check whether 2 positions are connected by ground. You can find out the total number of partitions (some maps have hundreds due to small holes), and you can draw partitions on the screen for debugging. There’s not that much to it.

A few notes:

  • There is a separate initialize() method that is not called by the constructor. You have to call it yourself, in or after your main onStart(). Depending on your use case, this is sometimes necessary to avoid making BWAPI calls before BWAPI is initialized itself.
  • Maps often have little side bits attached by a single walk tile. No unit is small enough to walk there, but this class doesn’t know it.
  • The little side bits are not important, at least not in Steamhammer’s play. Units don’t want to go there.
  • The class does not update when neutral units are destroyed, opening paths that used to be closed. But the walkability data structure is designed to be easy to update in that case.
  • This version is slightly different from Steamhammer’s code. I changed the error handling so that I could remove one last dependency and make it completely standalone (except for BWAPI, of course). Only a handful of lines are different.

I’ve suggested before that authors should package up reusable code as libraries. BWEM, BWEB, and FAP are great examples. MapPartitions is more modest. When I’ve looked at breaking out Steamhammer code, until now I’ve always felt that the parts were too integrated with each other, with too many dependencies. MapPartitions is the first exception. I hope some people find it useful.

3 brief Steamhammer notes

1. Today I removed a bunch of unneeded header includes from various source files. In some cases, I moved an include from the .h to the .cpp file, and dropped in a forward declaration instead. I’m hoping it will speed up compilation.

3. Also today I made several small changes to improve play on island maps like Sparkle, and maps with large inaccessible areas like Third World. I gave Steamhammer the skill to avoid sending a squad to a place the squad can’t reach, basic stuff like that. There’s more to do. The fundamental skills need adjustments to fit situations that Steamhammer has never been tuned for.

3. Steamhammer today played a hell-for-leather test game on Third World. Its mutalisks left the terran main in tatters while its own bases were under marine attack, then the flyers returned home barely in time to save the last zerg building, the spawning pool. Steamhammer won the game with a single bleeding building. Unfortunately the replay seems to be corrupt, so I can’t show it.

Steamhammer’s opening selection surprised me

Steamhammer lost a game to XIMP by Tomas Vajda, for the first time since December. When I saw the result listing I thought “That has to be due to a new bug.” But no, it is actually an unplanned effect of a deliberate feature.

After Steamhammer finishes the exploration phase of opening learning (the first 5 games), it does one extra check that I didn’t mention in my post on the topic (you can insert this as step 1.5 in that post’s list). If all games so far are wins, or if all games with the current expected enemy plan are wins and the enemy is believed to always follow the same plan (even if we don’t always recognize it), then Steamhammer takes a “business as usual” shortcut and bypasses the rest of its analysis. “So far I’ve been playing openings for this matchup, or openings to counter the enemy plan,” it thinks. “And it worked perfectly. All I have to do is more of the same.”

But since I carried over the old game record files, it wasn’t playing its standard openings against XIMP. Before this version, Steamhammer was hand-configured to play a specific counter that always wins. That opening is the only one to appear in the game records before this. Now the hand configuration is gone, and the business-as-usual shortcut applied, so Steamhammer chose a different opening to counter XIMP’s cannons, a 3 hatch ling bust. But XIMP makes too many cannons for that to work, and zerg lost.

Hmm... I have 3 different thoughts. First, this should be the only loss. In the next game, Steamhammer will notice “Hey, I know an opening that always wins. Play that!”

Second, retaining the old game records has been an interesting test. And I think I want to keep it up for a while, because I’m still learning from it. But I also need to delete the old game records and do a blank slate test, because that is how the system was designed to work. It will behave differently when learning from scratch. For one thing, it will likely take several tries to hit on its XIMP-beating opening, doing worse at first. For another, it should discover quickly that it knows a way to put up a fight against Iron, and start doing better in that matchup. I was originally planning to do a blank slate test on SAIL, but SAIL remains down.

Third, I think the opening selection has a lot of room for improvement. It barely takes the predicted plan into account, it is not skilled at taking the map into account, the 5 game exploration phase is sometimes too long and sometimes too short, the exploration that happens after the exploration phase is not tuned correctly, and the whole system is rather ad hoc. I should drop in a proper machine learning algorithm and figure out a correct exploration policy. Those steps will make the rest of the opening selection apparatus structurally simpler.

Still a lot do to!

this opening requires that map feature

Before the 1.4.x series is finished, Steamhammer will adapt its opening choices to the maps by experience, even when it faces an unknown opponent. In a tournament, it may also want to adapt immediately to an unfamiliar map, which means it will have to analyze the map for features that influence play, and it will need prior knowledge of which openings work for a map with those features. At first, hand coding will be the way to connect map features to opening choices. That will also make it reasonable to compete in AIST. It’s no fun to play on an island map like Sparkle without specialized island openings.

The topic came up in the comments to the post chokes and regions from February. Antiga proposed 3 map features he wanted to be able to take into account.

Here’s what I’m considering for the design.

  • Code the map feature preferences in the configuration file, where they are easy to change.
  • Attach preferences to the openings directly, not to the strategy mixes. The island openings will be marked good for islands on the opening, along with Race and OpeningGroup. Then they are included in the strategy mixes without further marks.
  • It needs to be kept simple. Something like “Require” : { “feature” : “value” }, where you can put any number of features between the braces. Each gets only one value, though, unless I make it possible for the value to be a list. Maybe “Avoid” instead of or in addition to “Require”.
  • Require would mean that all the given features are required. Avoid would mean that all the given features are to be avoided.
  • There should be at least one default value: Most openings are not suitable to play on an island map, and should be silently marked for no islands. Maybe there should be more defaults.

I may start with only a map feature, or only an island feature. Here are possible feature names and values for some features, including Antiga’s 3 features.

featurevaluesmeaning
map[map name]this string is in the map name
islandyes, nono 2 starting positions are connected by ground
main rampup, down, levelentrance to each main base
main choke width[number]main entrance must be no wider than this, in tiles
natural choke width[number]natural entrance must be no wider than this, in tiles

A lot more feature values might make sense. To cover all island maps, there should be features for the number of bases and the number of gases at each starting positition, and probably more features beyond that. There are a bunch of different kinds of semi-island maps, and maybe each kind should have its own value. Some are specialized: For example, Iron Curtain (designed as a 2v2 map) has 4 bases with a divider that (initially) separates it into 2 islands, each with 2 starting locations—in 1v1, you might start on the same island as your opponent, or you might not. The main ramp feature might have a value “multiple” for maps which have more than one entrance to the main. And of course there are always irregular Blizzard maps where each starting base may have entirely different features. These things are complications that should be minimized on a first pass.

To be concrete, you might declare an island opening like this. (Beware: I edited this up in less than a minute and didn’t test it. It is not a tuned opening.)

    "Island 2HatchMuta" : { "Race" : "Zerg", "Require" : { "island" : "yes" },
        "OpeningBuildOrder" : ["4 x drone", "overlord", "4 x drone", "hatchery", "spawning pool", "extractor", "7 x drone", "Lair", "drone", "metabolic boost", "2 x drone", "spire", "3 x drone", "hatchery", "drone", "extractor", "2 x overlord", "12 x mutalisk"] },

The 80-20 rule applies. Does this plan meet 80% of needs? What do you think?

Microwave beats Iron

Locutus is doing great. What I find more interesting is that Microwave is also doing great since its update yesterday. In particular, Microwave seems to have improved enough to beat Iron consistently. I thought the most interesting game was the most recent one.

Furthermore, Microwave played more than one opening against Iron in its recent winning streak. Its favorite, though, seems to be 9 pool with a fast 2nd hatchery. The game plan has not changed much, only the execution is improved. Early zerglings hammer on the terran wall, occasionally breaking it down but usually only sapping the terran income. The lings don’t care if the wall is open, or if they come under fire, they only want to hit those supply depots. Microwave defends against the vulture runby with sunkens up front plus a sunken in the main. Iron usually appears surprised by the sunken in the main and fails to cope with it. Microwave follows up with hydralisks and later mutalisks, switching flexibly between hydras and mutas and preventing Iron from gathering its forces—especially targeting tanks so that they don’t build up to large numbers. Eventually Microwave reaches a critical mass of hydras and wins.

Even if you are as good as Iron, that is what happens if you always play the same and don’t update for a long time.

Locutus and Microwave are both Steamhammer forks. I’m pleased that they’re doing well. I see it as partly my success, too.

a cosmetic code improvement

Steamhammer inherits UAlbertaBot’s architecture: It is a collection of singletons which communicate by directly calling each other. Each provides a method Instance() which defines its instance as a static and returns a reference. It’s a standard C++ trick. The cross-calls look like this:

    return BuildingManager::Instance().isBeingBuilt(unitType);

It works, but I find it wordy and ugly. It repeats implementation details that are irrelevant to the caller. I plan to change the syntax to this:

    return the.building.isBeingBuilt(unitType);

With a local instance variable the, a reference to the The singleton which will centralize access to the other singletons. So far I have done this only for the newly-written MapPartition class which does connectivity analysis. It was a test to make sure I could see in the dark, because of the C++ Needlessly Obscure Initialization Rules (NOIR).

The code changes are not too much, and when I feel less time pressure I intend to implement the idea throughout. I think it’s an improvement. More concise and readable code will make the codebase more fun to work with.

Steamhammer priorities in light of AIST

Should Steamhammer compete in the AIST competition? I’m still thinking about it. It’s not an easy decision. On the one hand, I love the idea behind the competition. It places demands on bots that I have always intended to meet eventually. On the other hand, “eventually” is a key word. The skills a bot needs to compete in AIST are not needed in any other competition, and working on them now will delay other things that I want to do.

Progress so far: I fixed the crash on Transistor. Crashes first! At the top center expansion, some mineral patches overlap the terrain in a way that fooled Steamhammer’s map analysis into believing that they are inaccessible, which indirectly caused a “should never happen” condition because of how base placement works, which caused the fatal bug, a use-after-free error. It’s good now, and Steamhammer can play games on Transistor. I fixed the endless stream of exceptions on Sparkle and Third World caused by the bot’s inability to figure out how to move a scout around the enemy base. I diagnosed Steamhammer’s inability to expand on Third World. The bot believes that the narrow ramp from the main is not passable, because the path analysis is simplified. I’m testing the fix now. It is a feature that will also be necessary on Sparkle, a partition map at walk tile resolution which says which ground areas are connected.

The topic is priorities. So:

What will improve Steamhammer the most in the short run? Tactics and micro. Steamhammer is not well-rounded. I’ve been concentrating on strategy and macro, and by now Steamhammer’s strategy and macro skills are relatively much stronger than its tactics and micro. There are weaknesses everywhere, but tactical blunders and awkward micro lose a lot more games. Throwing away overlords like they’re free, ignoring cloaked units, running an army into the enemy forces without fighting—there’s all kinds of horrible stuff.

What does my plan call for? More work on the opponent model, for multiple reasons. One, I feel the need to make at least basic progress on the machine learning part. It helps keep the goal in sight. Two, it will give Steamhammer a kind of adaptivity that bots desperately need. Three, it fits with my overall top-down development plan: Strategy first, then tactics, then micro.

What needs doing for the AIST? Mostly map skills.

  • Sparkle: Basic island skills. Choose an island build order, take island bases, defend them, transfer workers, scout for enemy islands. Many regular skills need to work differently on islands.
  • Sparkle: Fix the bug that prevented Steamhammer from expanding on its own starting island.
  • Sparkle: Destroy the neutral building on the low-ground expansion geyser. This is the only map I know with that feature, so it’s a very specialized skill (though simple enough).
  • Third World: Pathing. Understand the narrow ramp from the main. Understand the gate between the “first world” and the “third world,” which workers can mineral-walk through though it is impassible to other ground units.
  • Third World: Semi-island skills. Not quite the same as island skills, though maybe I can find a way to treat them as the same.
  • Transistor: Place defenses at the twin ramps, not at the natural hatchery.

I think I can probably get everything working in time, at least at a basic level. If I spend the time to do that, Steamhammer will definitely perform worse in AIIDE. Also, some of these features I was planning to delay until more infrastructure was in place, and there won’t be time to add that infrastructure for AIST. There is a risk of having to redo work later, slowing down overall progress.

So far I’m proceeding on the assumption that Steamhammer will compete. But I haven’t decided firmly.

Steamhammer finally tries its AntiFactory opening

Steamhammer-Iron is the first game in which Steamhammer played its AntiFactory opening, which counters Iron’s vulture runby skill (as well as other stuff that opponents could try with a mech opening, and some opponents do try). If Steamhammer started with no history, it would counter Iron on the second game. In fact it started with all its past game records on SSCAIT since version 1.4, so it thought AntiFactory was just another opening to explore, more important than the average opening because it counters the predicted enemy plan, but still just another opening and not worth special attention. Since it lost, it probably won’t try the opening again for a while (though there is a lot of randomness in the decision).

If you’re familiar other Steamhammer-Iron games, like that one, you’ll notice that in this game the AntiFactory opening stops Iron’s early vulture pressure more or less neatly and gets mutalisks in time to break up the next attack, the followup that adds marines and 1 tank. Then Steamhammer starts to flounder. The mutalisks neither defend efficiently nor attack the enemy base efficiently, and zerg desperately wants a 3rd base and sends drone after drone to die. Still, it was a tough fight and Iron only gradually took the upper hand. With this opening, Steamhammer has a moderate chance to beat Iron.

Steamhammer needs more skills to beat Iron regularly.

  • Place the initial sunken correctly. It makes a surprisingly big difference.
  • Don’t send drones directly into enemy forces. If you can’t expand safely, make a macro hatchery in your base instead.
  • Once another base is up, defend it. Steamhammer tends to lose all the drones there.
  • Play more incisively with the mutalisks. Don’t hesitate in front of defenses, immediately switch to another target. Be more eager to pick off tanks and less willing to chase fleeing vultures and SCVs.

I think those 4 skills would be enough to win most games against Iron, and only 1 of them is a complex skill. Likely even 2 of them, any 2, would put the win rate over 50%. Tilt the game a tiny bit, and it rolls your way over time. More skills would help more. For example:

  • Don’t lose overlords like an idiot from Idiot City, even if you are one. Steamhammer regularly flies into turrets.
  • When it’s time to expand, coordinate actions: Clear any blocking spider mine, push attackers away, if necessary escort the drone to the expansion site.
  • Use scourge properly.
  • Clear spider mines systematically. Steamhammer has the infrastructure to track spider mines that it has seen once, but doesn’t use it; units ignore mines that are not detected at that moment.

Am I going to actually work on any of these skills soon? Maybe. Overlord safety is high on my list, because it is a critical weakness in all matchups. For the more complex skills, I am waiting for the revamp of squad structure, which won’t start for months yet. Priorities are hard.

Next: Priorities in light of the AIST competition.

Steamhammer 1.4.2 opening selection by the opponent model

The previous Steamhammer version chose its openings solely based on the enemy’s predicted plan, if there was one, or based on the matchup default openings if no plan was predicted. The new version 1.4.2 can still do that, but once it has played a given opponent enough times it prefers to choose openings that have won in past games. It has always stored the data in its game records; now it is using it.

At heart, the selection algorithm is epsilon-greedy, but there are a few wrinkles.

1. The first 5 games against each opponent, in each matchup, are the initial exploration phase. Choose openings the old way, according to the matchup or the enemy plan.

2. If any opening has a 100% win rate, play it again. This bypasses the exploration phase. If any opening has a 100% win rate on this map, play it again. It does this even if the opening has only been tried once before, so that we can’t much trust in another win. If more than one opening has a 100% win rate on the map, choose among them randomly without regard to how many times each has been played. The idea is to encourage map specialization. Also it usually ensures that a surprise win opening is tried at least a couple more times—once because it always wins, and once because it always wins on this map—to find out whether it was a fluke.

3. Decide randomly whether to explore for new openings, or try to play a known good opening. The exploration rate (“epsilon” in “epsilon-greedy”) varies from 5% if we always win to 15% if we always lose. (I don’t have any reason to think that those are good numbers, it’s just a try. Likely it should grow toward 100% if we keep losing, because there are so many openings to explore. I think I should do some math....) The form of exploration varies according to the total number of games played against this opponent. If there are few games, Steamhammer prefers to try an opening that responds to the enemy plan. With more, it increasingly chooses from the wider variety of openings for the matchup, ignoring the plan. If there are many games, over 30, it starts to choose openings at random from its entire universe of known openings. (Steamhammer doesn’t have game records for that many games against any opponent on SSCAIT yet, but it will happen in long tournaments.)

4. Try to choose the best known opening, the “greedy” in “epsilon-greedy.” Steamhammer combines the win numbers of each opening on the current map with the win numbers across all maps (a much larger number of games, but each is less informative because the map is different) in an ad hoc way to get a “weighted” win rate. It takes that as an estimate of the chance of winning this game if it chooses that opening, and picks the biggest number. In case of ties, it chooses randomly among the tied openings.

The weighted win rates are a crude attempt to adapt to the map without restricting the input to data about that map only. In some future version, I’ll switch to a more general context-aware algorithm that can take more information into account. Steps 2, 3, and 4 should collapse into one.

On the SSCAIT server, I left the existing learning files in place to see how Steamhammer would make use of the old information. So far, the data doesn’t seem too helpful. Some of the files have systematic mispredictions that the new Steamhammer version doesn’t make, and yet still believes (“there it is in writing!”). In the case of Iron, new Steamhammer can recognize and respond to the terran plan, but old Steamhammer recorded a lot of data in which it could not. Steamhammer is choosing openings against Iron according to its old data, when it would do better to ignore it. Well, I know what to do to fix that, and it’s on the list.

Randomhammer needs a lot of data to get anywhere. Even with the old data included, there is not enough. If it has played games against an opponent as protoss, that tells it nothing about what openings it should choose as terran.

AIST competition

Two big posts today! Antiga announced the AI Starcraft Tournament (AIST), Season 1, to be held next month. The tournament is to be played on the same professional maps as the ongoing Afreeca Starleague season 5, aka ASL5: The old map Gladiator and the new maps Sparkle, Third World, and Transistor.

I’m in favor of tournaments like this. The new maps present difficult challenges that Brood War bots have not faced before, and confronting them should force bots to become more robust and capable. The competition is labeled Season 1, so the intention is to hold more competitions in the same vein, with further tough maps. Over time, that should also impel bots to become more general and flexible. It’s good.

The deadline is soon! The submission deadline is 10 June, less than a month away. The new maps are ambitious targets for current bots, and I believe it will not be possible for authors to fully adapt their bots in time. I expect that participants will be taking shortcuts or accepting poor play.

trying out the maps

Should Steamhammer participate? The first part of the question is, can Steamhammer participate? Can it be adapted to the demanding professional maps before the deadline, or is the amount of work infeasible? I imagine I could do it if I rely on map-specific hacks (“if the map is named Sparkle, use this build order, kill this neutral building at this time, ...”), but I see hacks as wasted work that will have to be redone properly later. I want more general techniques to cope naturally with particular map features wherever they come up.

I tried the maps in test games to see what happens. In alphabetical order:

Gladiator was fine. Steamhammer played normally. It’s a classic map from 2010, and that is what I expected.

Sparkle, the island map, caused less trouble than I expected. Well, I expected a lot. I gave Steamhammer a game against the built-in AI (which can cope with islands), and made it ZvZ to be sure that it would go air. Drone scouting failed, obviously. The bot was unable to expand, even on its own island, and played the game with 1 base. It made a ton of zerglings (“it’s ZvZ, I know how to play this”) which had little use since they could not get off the island. The Recon squad is ground-only, so Steamhammer didn’t find enemy island expansions until after it reduced the enemy main. Steamhammer did finally win, though; the broken stuff was not absolutely essential. The problems are likely solvable by adding basic island skills, plus some map feature skills so it can recognize and take its own expansions.

Third World caused more trouble than I expected. I thought that play would ignore the “third world” upper left part of the map, since it is reachable only by drone mineral-walking tricks, and proceed normally on the resource-poor “first world” remainder. But no, again Steamhammer was unable to expand beyond its starting base. I don’t know why. Drone scouting again failed, apparently because BWTA failed to calculate regions, or did it in some way differently than Steamhammer expects (I didn’t dig into the cause). It’s hard to say how long it might take to debug and fix or work around the issues. Every use of BWTA has to be removed eventually, but this one was going to wait....

Transistor crashed Steamhammer’s new map analysis. I’ve tested the code on dozens of maps, from irregular Blizzard maps to intricate pro maps, and this is the first crash. The map doesn’t look technically difficult except for the weird crystals at the starting locations, and I had expected it would be OK. Likely it will turn out fine once I fix the crash.

My preliminary conclusion is that Steamhammer can play, but it will not be well adapted to the maps. I will have to spend time surviving tricky map features and won’t have much for thriving with the important play features like mineral-walking drones into the third world, or linking up island bases with nydus canals. The play features are what make it worthwhile, so I’m feeling hesitant. I’ll look into it more, though. This is only a first look, and there is more to find out.

How many participants will there be? How ready will they be? If people are saying “I suffer on these maps too, but it is still worth it” then it becomes self-fulfilling, it is still worth it.

Steamhammer 1.4.2 change list

Steamhammer’s elo is plummeting, a clear sign that the voters have taken an interest in the new release and are wielding their monkey wrench. At this rate, they’ll lose interest again pretty soon....

Here is the change list. Opening selection in the opponent model should help all races choose stronger openings against each opponent, when the bot has gathered enough experience. The map analysis changes have no effect on play (yet), but are part of the effort to jettison the terrain analysis library BWTA2 and will make a variety of analyses easier in the future. Protoss should no longer break the time limit after its base fills up with buildings. Terran tanks don’t siege and unsiege so insanely often. Terran building addons are built more quickly and reliably. Many of the other changes help all races play better. Macro for terran and protoss is much improved. Micro is a trifle crisper, especially for melee units.

It’s a complicated update, and there is a higher than usual risk of new bugs.

The most important changes are in bold type.

opponent model

Select openings which have been successful against this opponent before. Take the map into account; with experience, Steamhammer will play different openings on different maps (though with 14 maps to learn about, map adaptation is slow as a snail). If past opening choices are not fully successful, try out different openings. With an increasing number of games, this increasingly includes openings that aren’t tagged as suitable for the opponent’s recognized plan, or for the opponent’s race, or even openings that Steamhammer otherwise won’t play at all. We will occasionally see openings that Steamhammer has never played in public before, because they were written in but not configured to be played. This is more or less as described in my earlier plans. I’ll post a more detailed description separately.

• When recording the opponent’s game record to a file at the end of each game, rewrite the entire file instead of appending to it. Appending works on SSCAIT, but most other competitions require the file to be rewritten.

The plan recognizer recognizes a new Factory plan that can be followed only by terran. It means terran is starting with a mech opening. Zerg is configured to counter the Factory plan with an AntiFactory opening. In tests against different opponents, the plan is accurately recognized, and the change produces much stronger zerg play against terran mech openings. Terran and protoss are not configured with anti-mech openings, but could be.

• The plan recognizer is smarter in one way: If the opponent plays a fast opening plan, the recognizer doesn't change its mind later when it sees signs of a slow opening plan; it wants to recognize the first thing the opponent does. The plan recognizer was doing things like correctly recognizing a 5 pool as Fast rush, then later seeing the 5 pooler add a second hatchery, and deciding that it was a macro opening after all. In the next game, Steamhammer could react incorrectly.

• There was a typo in the UCB code! Fixed. This affected the auto gas steal, if it was turned on.

• Separately, the auto gas steal is a little more conservative. It wants to see more evidence that it is useful before it is repeated.

• Try to detect whether the opponent always plays the same strategy. If so, the game info display notes it as (say) “surely Fast rush” instead of “expect Fast rush”. The opponent model uses the information behind the scenes (a little bit).

map analysis

• New code calculates base locations and stores related information. It works, and is used for some purposes, but most of Steamhammer still relies on BWTA. The information manager indexes bases by their BWTA::BaseLocation, and it turns out to be something that should be redone in one sweep. Later I’ll move the information manager functions into the new Bases class.

openings

• The opponent model is now in charge of choosing openings, so the CounterStrategies section of the configuration file is now used only when the opponent model wants to try out openings that respond to the enemy's expected plan, rather than whenever the enemy has an expected plan. I made the probabilities more even, so that stronger counters are still preferred but weaker counters will come up more often than they used to. That will help the opponent model collect the data it needs.

• I haven’t documented it before, but Steamhammer for a time has had some openings which are included in the configuration file but not configured to be played. Bot authors might choose them for their own bots, or take them as starting points for developing new openings. The opponent model is now able to select these openings. It will occasionally choose one at random and try it out against an opponent which it has played many games against, and if the opening is successful it will keep playing it against that opponent. For this release, I added more unconfigured openings.

• Included in the openings provided but not configured to be played are a bunch of new protoss openings from Antiga. They deserve attention, but I ran out of time.

OverpoolSpeed was accidentally replaced with a bad opening in the previous version. I restored the good version.

• The classic Over10Hatch opening was restored under that name. It makes 2 sunkens at earlier timing than the newer Over10Hatch2Sunk, and builds fewer drones. The idea is that restoring it should allow old learned data at SSCAIT to remain useful—it’s useless when it refers to an opening name that no longer exists. Plus there are times when the low-economy version works better.

New zergling rushes 5PoolHard2Player which hits harder on 2 player maps, and 6PoolSpeed which hits differently.

• Zerg has a new AntiFactory opening to combat early vultures and their followup. It works great. Terran has a corresponding VultureWraith opening for TvZ. I wrote it as one of the tests of AntiFactory, and found that it is effective against zerg opponents which aren’t ready. And the opponent model should now be able to tell which opponents are not ready.

• New zerg openings Over10HatchBust, Over10HatchSlowLings, and Over10Hatch2SunkHard are variations of the anti-zealot rush build and may be suitable for different opponents. They are intended as food for the opponent model. Overpool+1 is a weaker version of the ZvZ opening played by Marian Devecka’s Killerbot. Because of a limitation of the strategy boss, it gets carapace +1 instead of melee attack +1, which reduces its effectiveness. None of these openings is particularly good, but I expect they will have value against some opponents.

production

No more queue deadlocks. Steamhammer avoids most production jams by recognizing and dropping jammed items. This affects terran and protoss; zerg already had virtually no production jams.

Effective queue reordering. The previous version occasionally reordered queue items for more efficient production. This version generalizes the technique and benefits from it much, much more often. If the next queue item can’t be started immediately, and a later item up to 4 places ahead can be, and other conditions are satisfied, the later item is moved to the front of the queue and produced immediately. This affects all races, terran the most, protoss somewhat less, zerg the least.

With production unjamming and queue reordering working together, terran and protoss macro is way smoother. Production unjamming is aggressive; it drops any item which will cause a long delay. BOSS will often order (say) a cyber core and then dragoon range immediately afterward. It caused a long delay, though not a deadlock. Now when that happens, Steamhammer usually finds when it gets to dragoon range that the cyber core is still in the building manager’s queue and hasn’t been started yet, so it drops the research. BOSS will order the research again on its next cycle; not ideal but a big improvement overall. Meanwhile, the next cycle comes sooner because queue reordering makes production of this cycle faster. If the cyber core was started and hasn’t finished, other queue items will be pulled in front of dragoon range and produced first. Unfortunately, BOSS is as happy as ever to order all buildings and no units. Queue reordering can only mitigate the flaw to an extent.

• It’s possible in principle for queue reordering to be too thorough and break an opening’s intended build order. To cope with that, I added a new command that can be included in a build order, "go queue barrier". Nothing after "go queue barrier" will be reordered to happen before it. It was just a tiny little bit trivial to implement, because it’s a command that does nothing; queue items are never reordered across any command. No openings use "go queue barrier" currently; I tested the likely candidates and found that they worked fine with queue reordering.

Protoss should not overstep the time limit when its base fills up with buildings. This is a major improvement for protoss. There are 2 contributors to the slowness: Failing building placement in the building manager, and failing building placement in the production manager when it tries to predict when a building can be started (it needs to know where, so it can figure out how far ahead of time to send a worker). The change is applied in both places; both are fixed. The production manager’s problem was already mitigated by a workaround which retries building placement less often, and I kept the mitigation since placement can fail for other reasons. The problem won’t be 100% solved and shelved until building placement is fast and reliable.

If the debug option Config::Debug::DrawBuildingInfo is turned on, then buildings which are currently stalled and cannot be built are in red. After a pylon finishes and the buildings unstall, they turn back to the normal yellow color.

• Earlier versions tried to work around the protoss building placement problem in part by allowing certain types of protoss buildings to touch each other vertically. This made building layout more compact, but had the side effect of sometimes trapping units in enclosures. This version drops the workaround. Protoss base layout is loose to the point that it is awkward and causes problems, but that is not as bad as trapping units.

Terran addons are produced via production goals, which construct them more reliably and earlier. It is a significant improvement for terran. Some terran openings are improved to take advantage; there is more room to improve them. I removed a workaround for a BOSS comsat bug, since production goals work around the bug automatically.

tactics and micro

• Deciding which base to attack now takes into account some of the enemy units seen near that base. Earlier versions took into account static defense only: Attack the base with the least static defense. This version also takes into account certain slow-moving units that are good for defense: Tanks, high templar, reavers, lurkers, guardians. The aim is still to attack the least-defended base. It’s smart enough that an air squad with no ground units only worries about defenses which can attack air, and a ground squad with no air units doesn’t worry about turrets or spore colonies.

Deciding where to drop is more flexible. Drop navigation—moving the transport around the edge of the map—is rewritten, and its code is far simpler. The main practical effect of this work is that the dropship or shuttle always takes the shortest path around the map to its target. It used to be right only half the time, despite an attempt to figure it out. The protoss dark templar drop will land earlier up to half the time. With help from the addon production improvement (see above), the terran vulture drop always lands earlier.

I did not finish my plans to improve drop decisions. It’s a little smarter about where to drop, not as smart as I wanted. If no enemy base is known to drop on (which only happens in rare circumstances when other things have gone wrong), the transport is sent to drop on an unscouted base—and if the base is empty, it still drops there. Furthermore, the dropped units then sit around and do nothing. The issue is so rare that I didn’t fix it.

Some rework of micro targeting. Zealots and zerglings are particularly improved. Melee units are allowed (as ranged units always have been) to ignore bad targets, which makes them less distractible. All units ignore targets outside of a maximum range limit, to make them less distractible. The method of prodding units to tend to move toward the squad goal is changed; this is responsible for most of the improvement in melee units. And there are various minor changes in detail.

• Units that can’t attack under dark swarm, don’t try to. This may help occasionally against those few opponent bots that use dark swarm. Mainly, it is preparation for when Steamhammer knows how to use defilers itself.

• Enemy nydus canals were usually given priority as important targets. Now they are always given yet higher priority. I think it is likely that they are still not high enough in priority, but Steamhammer has yet to need the knowledge, so it’s hard to be sure.

configuration file

• The debug option Config::Debug::DrawQueueFixInfo prints information to the screen when the queue is reordered or a production jam is averted. For terran and protoss, this happens a lot. If you only watch the queue on screen and don’t turn on this option, it is hard to visually catch all of the changes.

• The IO option Config::IO::MaxGameRecords limits the maximum number of game records that are stored for each opponent. When over the limit, the oldest records are dropped. The bot can be left running indefinitely on a server like SSCAIT without risk of using up its allotted space or growing slow because the opponent model files are too big.

• If you don’t want to use the plan recognizer, set Config::Strategy::UsePlanRecognizer to false. The enemy plan will always be Unknown. I expect this may be useful for some authors of forks.

• The configuration option Config::Micro::KiteLongerRangedUnits is removed. Not only was it a confusing name, I decided it wasn’t something that bot authors needed to be able to reconfigure. You can still change the behavior in the code at Micro::AlwaysKite().

• The command "go queue barrier" can be used in build orders to prevent queue reordering. It was explained above.

code

CombatCommander::getMainAttackLocation() renamed to getAttackLocation(). If I keep this up, eventually all the names will make sense.

terran and protoss

Tank siege and unsiege are much improved. There’s less “Siege! Unsiege! Siege! Maybe fire a shot? No, unsiege!” Terran tank play is considerably stronger—it is like putting dirt in your eye, instead of blinding yourself with a red-hot poker. There are other minor changes to tank behavior.

• Wraiths now use the same kiting code as mutalisks.

• Goliaths, wraiths, and scouts prefer air targets over ground targets. Suddenly they realize that their air weapons are much stronger. This change and the previous one were made first of all to improve terran wraith play, now that there is a VultureWraith opening. Overlords beware.

• Protoss carriers do not target enemy interceptors. In carrier-on-carrier battles, Steamhammer’s carriers were ineffective because they tried to hit the swooping enemy interceptors, and failed—“I’ll hit that one... no. That one... no. That one...”.

zerg

If we need a macro hatchery and adding it won’t delay important production, just add it! There was a bug where Steamhammer thought that adding a macro hatchery would prevent it from getting critical tech units in time, even when it had plenty of resources and adding the hatchery could only help. This fixes what I think is the one biggest cause of breakdowns in zerg macro. (There are more.)

• If we have a big mineral excess, be more eager to add extractors. This should improve gas-starved late game play, which is especially noticeable versus XIMP by Tomas Vajda.

• If we scout the enemy doing a fast rush, and we’re doing a fast rush ourselves—don’t react. Steamhammer’s current reaction is not appropriate for the situation; it’s better to stay the course.

• The gas steal sometimes failed because the strategy boss incorrectly dropped the extractor from the queue. Fixed.

• Fixed a bug that prevented 4 pool from working. I think this one has been around for a while; I have occasionally noticed odd games that were probably caused by this bug, but never diagnosed it because 4 pool is played so rarely.

• I made a minor change to the details of unburrowing a severely hurt unit after it heals up some.

• A minor change to defense against proxy buildings: Steamhammer no longer feels a need to defend against a terran proxy which lifts off and floats out of sight.