archive by month
Skip to content

Steamhammer’s learning results

When I uploaded Steamhammer 1.4.3 to SSCAIT on 11 June, I erased its learned data from the server. Its elo immediately plunged, partly because the voters wanted to put it through its paces against strong opponents, and partly because it needs its learning data to cope. Most rushbots, and many others, won their first or first few games against Steamhammer. That didn’t change when I uploaded 1.4.4 a week later—the improvements weren’t many.

Finally, only in the past several days, I’ve started to feel that Steamhammer has learned enough that it is closing in on its equilibrium elo. It has been wavering around the high 2100’s, not able to break above 2200 but not falling far either. It seems about right, at least for SSCAIT conditions.

The findings. As I’ve mentioned, clearing the learning data was a deliberate test to see how well the learning system works when learning from scratch. I’m fairly pleased. I see weaknesses, but only weaknesses I expected. Against XIMP, Steamhammer has settled on an opening that has won every game so far, but is not as strong as the 3 hatch before pool opening that I hand-chose for it in the old days. Steamhammer only sees that it wins. It can’t tell the difference between openings that win nearly all games because it sees only the winning rate; it needs an evaluation function that can tell it “this one wins more convincingly.” Against Proxy, it won one game with its unusual 6 pool opening. Then it played another game and recorded another win—because Proxy crashed. Steamhammer thought it had found a winner, and had to lose some games before it realized that the 6 pool was not a reliable counter. (It would be, if not for Proxy’s powerful worker defense.) Possibly 5 pool or 4 pool would succeed, but Steamhammer does not know that some openings are related to others. When I teach it that, it will be able to realize that if one opening shows promise but is not quite successful, it should try related openings.

In some cases, Steamhammer hit on surprising counters. The most striking example is against TyrProtoss, which had been winning every game with its cannon turtle into timing attack strategy. Steamhammer tried its 2 hatch lurker all-in attack, which did not make sense to me—Steamhammer’s lurkers suck when cannons are around, it has little idea how to break the cannons and no idea how to bypass them. But it won a game. We’ll see if it keeps winning!

I expected the weaknesses, and I expected the surprising counters. I feel as though I understood the learning system and its limitations fairly well. It gives me confidence that my planned improvements, when I finally get around to them, will be real improvements.

Steamhammer’s resource box

I continue to prepare Steamhammer for CIG as best I can in the short time since I relaid my plans. I had to abandon 2 more ambitious changes when they busted their time boxes, but my log shows about 20 minor improvements. Each one helps in some situations—probably. Nothing is as well tested as it should be. Steamhammer does do better against some test opponents. I think that overall the improvement is definite though not great, and I have a little time left.

MicroDK mentioned somewhere (maybe it was in Microwave’s update note?) that Arrakhammer fixed a problem in the resource box used to avoid placing buildings in the mineral line. Steamhammer loves to manner its own minerals and gas (that means putting buildings in the way so they interfere with mining), but fixing it had never come to the top of my queue. Today I decided it was the issue with the best effort/payoff ratio.

Oh wow, this code inherited from UAlbertaBot is not at all good for Steamhammer! It calculates a single rectangle “avoid placing buildings here” for the main base minerals. It pays no attention to the gas geyser or to other bases. It was good enough for UAlbertaBot, which barely expands and doesn’t build much, but not so for Steamhammer.

My fix is similar in idea to Arrak’s, though my implementation looks completely different. I did away with the single-purpose resource box and relied on the more general building tile reservation map. I marked certain tiles near the minerals and geyser as reserved, so that nothing can be built there. For the geyser, I was careful to reserve the adjacent clockwise tiles which cause gas mining workers to take long paths around, and I tried to leave other tiles alone. For example, if the gas is straight up from the hatchery, then a building in front of the extractor on the left looks as though it should be in the way, but doesn’t interfere with mining. A building touching on the right, even at the corner, can cause bizarre paths for a drone leaving the extractor. There are some tricky cases of diagonally placed geysers where Steamhammer reserves too many tiles, but it’s not harmful.

Looking at the pattern of reserved tiles for different bases, I was able to spot one case where theoretically Steamhammer might be able to manner its minerals. But I couldn’t get it to happen in practice. This should completely fix an irritating problem.

I’ll release Steamhammer 1.4.5 after CIG submission closes. It’s a change of plans; I decided there was no reason not to.

tactical analysis

Today there was a short string of comments about retreating. Steamhammer makes mistakes in retreating: It retreats to poor positions, and the purpose of retreating is to disengage so it issues a move command—but it doesn’t check whether it is moving to a safe place. It often loses by retreating into or through an enemy force without fighting. It’s such a severe weakness that I think almost every Steamhammer fork has implemented some improvement to the behavior.

I am planning a simple improvement for CIG. For the following AIIDE tournament, I should have time to implement actual factual honest-to-goodness tactical analysis and make maneuver decisions properly. So here are my thoughts on what has to go into tactical analysis.

1. Distinguish different kinds of tactics. Maneuvering is one kind: Deciding when and where to move, when and where to attack, what locations need static defense or mobile defenders or scouting units, all that stuff. In maneuvering tactics, you do planning to decide on goals, and then carrying out the goals requires reacting to the situation but doesn’t require planning as such. The planning is all up front; if the bot hits a problem, the solution is to replan and set new goals. There are also what I think of as sequencing tactics, like clearing a map block or carrying out a drop or performing a coordinated attack from different directions. They require a sequence of steps. The bot has to recognize a need—clear the neutral buildings to open the path to expand to that base; select and collect resources—use lurkers because the buildings are stacked, otherwise it will take too long; plan paths; and execute steps in order—first clear the neutral buildings, then send the drone to expand. The response to problems seems more complicated than “just replan from scratch;” minor problems can be solved by assigning more resources. I don’t think there’s necessarily a sharp line between maneuvering tactics and sequencing tactics, but they do seem to call for different planning skills.

2. I am increasingly thinking that the 3 levels of analysis strategy, tactics, micro are not enough. I think I want 4 levels, which I will call strategy, operations, tactics, and micro. To a certain extent, this already shows in Steamhammer, where decisions made at the squad level sometimes look like tactics and sometimes look like micro. Retreating is a perfect example: It is a tactical decision which is made by the squad based on local tactical analysis of the situation of that squad only. Roughly speaking, high level tactics are in the combat commander, low level tactics in the squad, and micro in the squad’s micro managers. I want to reorganize it somewhat so that it is clear what level each decision is made at.

Strategy is about resources. It decides on the drone count, how much gas to collect, when to expand, and what units to make. The unit count it achieves and the unit mix it chooses have big effects on the lower levels. Strategy also includes the highest-level understanding of the game situation, like “I need to stay defensive until I’ve built up a big enough force” or “I’m ahead in economy, so an even trade is good.”

Operations is about choosing high-level tactical goals and assigning units to achieve them. Raiding squad, go hit that enemy base; blocking squad, delay any enemies from arriving there. I think that attack and retreat should be decided at this level. Sometimes it is right to retreat the raiding squad when strong enemies arrive; sometimes it is right to sacrifice the raiding squad to do as much damage as possible. The decision is best made based on the whole situation. You want to be able to reason, “sacrificing the raiders to delay the enemy a little longer means the upcoming drop is more likely to work.”

Tactics tries to achieve the operational goals. It chooses roles for each unit in a squad, or perhaps subdivides the squad into subsquads with different subgoals: Hydras engage the cannons, lurkers run past and reach the mineral line as fast as possible.

Micro is individual unit control. Where should the lurkers aim to kill as much stuff as possible?

As always, the different levels of analysis should ideally feed information to each other. See levels of abstraction.

3. How to analyze maneuver tactics efficiently? MaasCraft showed the way: Cluster units together and analyze at the level of clusters. At the operational level, it doesn’t matter exactly where individual units are. The analysis has to remain valid for at least a few seconds, and units will move around in that time. Draw a bounding box or a circle around each cluster and represent the contents of the cluster as summary measures of unit count, total hit points, speed or range of speeds, or whatever other values are useful. I believe many bots do this kind of analysis, but Steamhammer doesn’t.

With that information in hand, you can make quick estimates of when an army can arrive, how much force can be brought to bear on a given point, who wins the fight, and so on. The estimates may not be accurate, but combat simulators aren’t accurate either. You just have to be good enough to improve your decisions.

4. What should tactical analysis look like? MaasCraft uses a form of MCTS, a logical choice of search algorithm. A simpler search would already improve Steamhammer’s tactics. I expect that, with a smart enough evaluation function, a brief search that looks ahead a short time would produce strong tactical play. Surely machine learning should be able to produce an evaluator that smart. But I have ideas about how to write one by hand, and I may try that first.

Still next: Oops, somehow Proxy slipped down a slot.

on the CIG wheel

Still here. I’ve been hamstering on the codemill and not leaving myself enough time for anything else. It seems like the right thing to do, but based on history it probably isn’t. I usually go wrong when I rush for a deadline.

I want to make as much progress as possible by the CIG deadline on 10 July. And I have made several clear improvements. But my latest feature may be an overreach. After several days of work, the development version of Steamhammer lost every test game against the current release version. It will be an improvement if I can fix the problems....

Next: Proxy has improved fast.

a latent bug in squad updating

CombatCommander::updateIdleSquad() does this:

    for (const auto unit : _combatUnits)
    {
        // if it hasn't been assigned to a squad yet, put it in the low priority idle squad
        if (_squadData.canAssignUnitToSquad(unit, idleSquad))
        {
            idleSquad.addUnit(unit);
        }
    }

The code is correct as it stands. It cannot assign 1 unit to 2 squads. But I consider it a latent bug: Only the lowest-priority squad can be updated this way. It would be easy and natural to make changes to squad priorities and cause an error, or to copy and paste this code under the assumption that it would be correct in a different case. You would get an irritating bug with no obvious cause. In the spirit of defensive programming, I recommend changing it to this:

    for (const auto unit : _combatUnits)
    {
        // if it hasn't been assigned to a squad yet, put it in the low priority idle squad
        if (_squadData.canAssignUnitToSquad(unit, idleSquad))
        {
            _squadData.assignUnitToSquad(unit, idleSquad);
        }
    }

assignUnitToSquad() ensures that the unit is removed from any previous squad before it is added to the new squad. Any squad other than the lowest-priority squad might steal a unit from another squad, and you can’t steal it without taking it away. Assigning 1 unit to 2 squads would cause problems, and Steamhammer checks later and will throw.

At a minimum, I recommend commenting the code so you don’t copy-paste it heedlessly when adding a new squad type.

Randomhammer-ZurZurZur game with vulture-wraith opening

Randomhammer terran played its first public game with the vulture-wraith opening against zerg ZurZurZur. As I have mentioned, I wrote vulture-wraith originally as an extra test of Steamhammer’s anti-factory zerg opening. I found that the vultures and wraiths bring terror to unprepared zergs, so I made it a permanent part of the terran arsenal.

ZurZurZur, it turned out, was not prepared at all. It could not get a sunken colony up, and it did not get hydralisks into the fight until its natural was destroyed. By then it had lost all overlords so it could not reinforce and the hydras were quickly defeated. I can see how Tscmoo terran does so well with a similar strategy.

ZurZurZur loses its natural

I’m working on zerg stuff now for the upcoming tournaments, but I have a note to revisit this opening. It shows its legacy as a test opening; the execution and followup could be much stronger.

Next: A latent bug.

Steamhammer 1.4.4 change list

Steamhammer bugfix version 1.4.4 is uploaded to SSCAIT. It has only been a week since the last version was uploaded, so the changes are not many. The bug fixes probably mean slightly less inexplicable erratic behavior. Zerg is slightly smarter because of a few small tweaks, but it may not be easy to tell.

After rechecking the CIG publication dates, the next public release may not be until October, after AIIDE submission closes. We’ll see; there is plenty of time before then for me to change my mind.

the Bruce bugs

The bugs found by Bruce Nielsen (Locutus) and related fixes.

BuildingManager::validateWorkersAndBuildings() copied building objects unnecessarily.

BuildingManager::cancelQueuedBuildings() and BuildingManager::cancelBuildingType() unsafely deleted buildings from the _buildings vector while looping through it.

Squad::releaseWorkers() had a similar deletion bug.

MapTools::nextExpansion() had a typo that caused it to do a check with the wrong coordinates.

other code changes

UnitUtil::GetAttackRange() and UnitUtil::GetAttackRangeAssumingUpgrades() mistakenly returned the weapons range of a reaver or a carrier as 8 pixels, rather than 8 tiles (8 * 32 pixels). “We’re perfectly safe at this distance, folks!”

• I am reworking DistanceMap as the foundation of a range of classes that keep a number for each tile of the map. A new base class Grid provides the basic data structure, initialization to a constant value, and lookup. DistanceMap is renamed to GridDistances. I wrote a new class GridAttacks as a start on a kind of influence map; some code is there, but it is not yet tested or used, so exercise care.

• Removed the unused BuildingData class, and the unused method MicroManager::calcCenter().

• Removed more unnecessary includes.

zerg

• If an expansion hatchery fails to build, the building manager turns it into a macro hatchery instead. It prevents Steamhammer from sending drone after drone to expand and losing them all. It’s a low-level trick instead of the high-level tactical safety analysis that should be done, but it’s simple and it works moderately well.

• Versus protoss, count only corsairs, scouts, and carriers as reason to make devourers. Steamhammer played a game in which it made devourers to combat the enemy’s large number of observers. “I know how to beat that plan!”

• On 2 player maps, increase the chance of playing rush-safe ZvZ openings. Also rejiggered fast pool chances (suggestion by Antiga). A number of other probabilities are adjusted, mostly to make more diverse choices so that learning has data to work with.

new Steamhammer plan: win

In AIST S1, Steamhammer got to play only 5 games and lost 4 of them. It was a sad showing, though the field was strong. CIG is coming soon, and Steamhammer is registered. Then AIIDE is after that. Also, as I have mentioned, Steamhammer is unbalanced in its play skills, much stronger in strategy and macro than in tactics and micro. Looking at results in the past few months, I’ve concluded recently that it is so unbalanced that improving strategy no longer helps at all. When I fix a strategy weakness, even one which demonstrably loses games, the benefit is so small that there is no detectable change in skill, either objectively in elo or subjectively in my judgment. The imbalance is so extreme that nearly all losses, even in games affected by strategy problems, are due to mistakes in tactics and micro.

That means that if I work on the opponent model and opening selection as I planned, I won’t be able to tell if am doing a good job. No matter what I try, it will hardly affect results. It also means Steamhammer is at risk of getting whomped in CIG and AIIDE.

I have made a bunch of bug fixes recently (thanks Bruce!), so here’s my new plan. I’ll release 1.4.4 shortly with source, and to save time I’ll skip releasing the source of 1.4.3. (It takes more effort than you might imagine.) 1.4.4 includes bug fixes (one of which hasn’t been mentioned here), plus a start on an influence map for tactics, plus a zerg strategy fix that may save 1 game in 200 and a zerg tactics change that is more likely to save 1 game in 15 or 20. I think it’s all stuff that people should have.

Then the next public release will be after the CIG deadline next month (CIG releases source itself), and the following one may be after the AIIDE submission deadline in October. I will buckle down and make the changes that improve play the most in that time.

Steamhammer bug fixes

Yesterday was a productive day for fixing bugs, thanks to info from Bruce Nielsen, author of Locutus. See comments to Steamhammer 1.4.3 change list. Because tournaments are coming up, it may be a while before I release another public version of Steamhammer, but bugfixes are important for everyone with a fork, short of the devil.

After fixing these bugs, I can no longer reproduce the “wrong reserves” error messages that the building manager used to kick out several times per game. Other mysterious misbehavior is probably fixed too, though I’m not sure what.

BuildingManager shouldn’t copy buildings

The methods BuildingManager::validateWorkersAndBuildings() and BuildingManager::checkForCompletedBuildings() loop through the vector of Building data structures and delete those that are no longer needed. To avoid deleting from the vector of Building objects while in the midst of looping through it, it stashes the buildings to delete in another vector toRemove and passes toRemove to a deletion method.

    std::vector<Building> toRemove;

Well, it’s copying the building data. I haven’t uncovered any bug that this causes, but it is wasteful at best and at worst an invitation to errors. If code updates the copies, or updates the real buildings before the copies are sent in for deletion, no real building or the wrong real building might be deleted because of how equality is defined in the Building class. undoBuildings() has to undo data structure dependencies and could easily make such a mistake.

There are several steps to the fix, but the idea is to keep a vector of references instead of a vector of copies. To keep references in a Standard Template Library container, we have to uglify the type signature with a wrapper.

    std::vector< std::reference_wrapper<Building> > toRemove;

The rest of the code doesn’t change, only the declaration. Then we also have to update the declarations of the deletion routines that undo data structure dependencies and perform the deletion.

    void undoBuildings(const std::vector< std::reference_wrapper<Building> > & toRemove);
    void removeBuildings(const std::vector< std::reference_wrapper<Building> > & toRemove);

canceling buildings is incorrect

BuildingManager::cancelQueuedBuildings() and BuildingManager::cancelBuildingType() also delete unwanted buildings, and do it while looping through the vector of buildings. It is unsafe and can cause errors. It’s my fault, I wrote these. I rewrote them like this:

// It's an emergency. Cancel all buildings which are not yet started.
void BuildingManager::cancelQueuedBuildings()
{
	std::vector< std::reference_wrapper<Building> > toCancel;

	for (Building & b : _buildings)
	{
		if (b.status == BuildingStatus::Unassigned || b.status == BuildingStatus::Assigned)
		{
			toCancel.push_back(b);
		}
	}

	for (Building & b : toCancel)
	{
		cancelBuilding(b);
	}
}

and

// It's an emergency. Cancel all buildings of a given type.
void BuildingManager::cancelBuildingType(BWAPI::UnitType t)
{
	std::vector< std::reference_wrapper<Building> > toCancel;

	for (Building & b : _buildings)
	{
		if (b.type == t)
		{
			toCancel.push_back(b);
		}
	}

	for (Building & b : toCancel)
	{
		cancelBuilding(b);
	}
}

releasing workers from a squad

I realized I was not sensitive enough to this category of bug, and surveyed the codebase to see whether there were any more. Almost all risky loops were correct, whether written by Dave Churchill or by me, but I found 2 more. One was inconsequential because it was in the BuildingData class, which is unused (so I deleted it). The other was in Squad::releaseWorkers() and should be fixed.

// Remove all workers from the squad, releasing them back to WorkerManager.
void Squad::releaseWorkers()
{
	for (auto it = _units.begin(); it != _units.end(); )
	{
		if (_combatSquad && (*it)->getType().isWorker())
		{
			WorkerManager::Instance().finishedWithWorker(*it);
			it = _units.erase(it);
		}
		else
		{
			++it;
		}
	}
}

finding the next expansion to take

Bruce also pointed out an unrelated typo in MapTools::nextExpansion(). It's a serious bug. Here is the Locutus commit fixing it.

Change this:

        for (int x = 0; x < player->getRace().getCenter().tileWidth(); ++x)
        {
			for (int y = 0; y < player->getRace().getCenter().tileHeight(); ++y)
            {
				if (BuildingPlacer::Instance().isReserved(x,y))
				{
					// This happens if we were already planning to expand here. Try somewhere else.
					buildingInTheWay = true;
					break;
				}

To this:

        for (int x = 0; x < player->getRace().getCenter().tileWidth(); ++x)
        {
			for (int y = 0; y < player->getRace().getCenter().tileHeight(); ++y)
            {
				if (BuildingPlacer::Instance().isReserved(tile.x + x, tile.y + y))
				{
					// This happens if we were already planning to expand here. Try somewhere else.
					buildingInTheWay = true;
					break;
				}

x and y were treated as absolute map coordinates instead of relative to the building location, so the check gave completely wrong results. The next check, immediately below, is correct; the bug is a typo. But the social status of the bug does not matter. What matters is that it broke stuff.

the ordeal of Sparkle

Mark Twain advised writers that every time you want to write “very” you should put “damn” instead: “Supporting Sparkle is damn hard.” Your editor, he said, would remove the word “damn” and your writing would be as it should. Well, I don’t have an editor. Supporting Sparkle is damn hard, and my writing is as it should be.

When I first looked at the AIST competition, I posted “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.” I was right. Steamhammer can survive on Sparkle and Third World, but it doesn’t have the skills to play the maps as they were meant to be played. I did not have the time to implement them. That’s the theme of this post: Not enough time.

the Require: feature

Since Sparkle requires specialized openings, I wanted to implement at least Require: { “map” : “Sparkle” } for openings in the configuration file. See yesterday for the many ways in which my plan was flawed. I invested too much time before I realized that it would not pay off, and I had to put in a hack and move on to other stuff. It was not fun. I’ll rethink this completely for the next version.

the opponent model

You can’t get 5 pooled on an island. You don’t have to do anything special if the enemy makes a fast factory—you expect that on an island map. The plans that Steamhammer knows how to recognize don’t matter on island maps, and I had neither time nor test opponents to work on recognizing island plans, much less to counter the plans. It made more sense to short-circuit the plan recognizer, or you might say, to correctly recognize that none of the known plans mattered.

Still, Steamhammer does know 3 Sparkle openings, and it would have been nice to rely on the opponent model’s opening selection to pick the ones that worked better. But that was problematic too, because of how the opponent model tries to generalize across maps. Most maps you can generalize across: If this opening worked well against this opponent on Circuit Breaker, it has a good chance to work on Roadrunner too. But island maps, and concept maps in general, call for specialized choices; you can’t generalize from a concept map to any other map, or vice versa. I’ll remember that when I work on opening statistics, one of the next planned features. I learned that Steamhammer should be able to measure how much a map is like other maps, and generalize appropriately.

island transport

When I surveyed the AIST S1 map pool, I thought: Hmm, Sparkle is an island map and Third World is a kind of semi-island map where workers can pass between the map halves by mineral walking. The important skills are mineral walking, drop, and placing nydus canals. What combination of skills should I aim for first? Drop is the most flexible and the most useful overall, but it will take a ton of work to implement in Steamhammer because the existing infrastructure is weak. Also drop is slow and awkward for moving units around. Mineral walking drones is comparatively easy, and it is useful on a modest variety of maps. Also mineral walking doesn’t require research; when you can use it at all, you can use it from the start of the game. Nydus canals are the best way to move units between islands in the late game, they’re useful on almost all maps, and they’re related to pathfinding which I want to work on, so now is a good time to work on nydus. Plus Sparkle provides neutral zerg buildings on islands, so zerg can create nydus connections without needing drop. Mineral walking and nydus were my choice; drop is not required for zerg to play either map.

As it turned out, I didn’t have time to implement either feature. Steamhammer is restricted to its starting island and cannot move land units to another island. Zerg will win with air power, or it will not win. On Third World, Steamhammer is restricted to the starting half of the map, which has only 5 bases including the enemy main and natural. It’s disappointing.

island strategy

Ideally, I wanted to provide an air strategy with an optional switch into a late game ground strategy of securing islands with hydralisks by nydus. As the deadline approached, I foresaw that I would not have time for nydus, so I spent a few days getting the air strategy tuned up. I optimized the 3 Sparkle openings (1 hatchery muta, 2 hatchery muta, and 3 hatchery muta) until improvements became small. Once I realized that I could not finish the Require: feature and that the opponent model had to be more or less turned off, I simply set each opening to be played 1/3 of the time. It’s as good a guess as I can make.

At first, the mutalisks were uselessly weak. To get anywhere, I had to make changes to the strategy boss to give it some understanding of island maps. First, correct the unit mix so Steamhammer sticks with air and doesn’t decide to switch to lurkers or ultralisks, which without drop or nydus have no offensive value. Even after that, Steamhammer often lost against the weak built-in AI. I continued: Reduce ground upgrades and eliminate unnecessary research to save gas for mutalisks, and get air armor upgrades. Now Steamhammer can defeat weak or unprepared opponents. Improving mutalisk and scourge micro would help more, but that would have taken way too long.

In Candide’s best of all possible worlds, meaning several versions from now, Steamhammer should support drop and have the option of following a ground strategy on an island map. Ideally.

ground units

Steamhammer still tends to make many zerglings with its leftover minerals, which it accumulates because it has too many drones for what it is doing, which is because it doesn’t understand that it is stuck on one island. Well, its economy control skills are lacking overall; it only wants to make drones up to a limit depending on the number of mineral patches and geysers it has. The zerglings are at least useful for keeping an eye on the island and stopping drop attempts.

At first, the zerglings tried to reach the enemy base, piling up at the island edge. I expected that; it is part of what MapPartitions is for. I updated the tactical analyzer (primitive as it is) to only send ground squads to places they can reach. Now the zerglings understand that they can’t get to any enemy base, so they “explore the map,” running back and forth between corners of the starting island to keep an eye on things. Incursions should be spotted right away.

There is a bug with assigning air units to a ground squad. Despite a fix in the previous version, it still happens sometimes; it must be a second bug. I’ve seen guardians assigned to the ground squad when they should not be, which on Sparkle causes this weird behavior: The squad is sent as usual to explore the map, but because it includes air units it knows it can explore the entire map. The zerglings again gather at the edge of the island, while the guardians slowly drift from empty point to empty point. The bug doesn’t cause much trouble on land maps, but on islands it’s serious.

Steamhammer still makes defensive sunkens as usual when it sees a scary enemy ground army. It doesn’t know whether the army might appear at its base.

At some point in the 1.5.x series I will make squads more dynamic. I plan to make it illegal for a squad to have ground units in more than one map partition; if a ground unit moves from one partition to another, it will be put into a different squad which belongs to that partition. Ground units in transports will belong to a different squad yet. Squads won’t have to worry about their units being stuck in separate places, and should have an easier time making decisions.

island scouting

Steamhammer’s original Recon squad is a small ground squad that scours the map for enemy bases, and when it finds one, may attack if the base is poorly defended. On Sparkle, Steamhammer only found a new enemy island base when it happened to stumble across it while doing something else. I changed the squad assignment so that on an island map, the Recon squad consists of 1 overlord. Before overlord speed finishes, the overlord moves so slowly that its recon target often times out before it gets there, switching the overlord to a new destination, but the overlord still crisscrosses the map and sees stuff that wouldn’t otherwise be seen. Once speed is done, the overlord scouts nicely. Steamhammer has no overlord survival skills, so the overlord will happily fly over turrets and generally has no fear. I’m hoping that the scouting info makes up for the vulnerability. But in test games, Steamhammer loses overlords at a high rate.

destructible blocking buildings

The low ground natural base on each starting island has a psi disruptor building on top of the geyser. Every race can build a refinery building under the psi disruptor, but none can mine the gas until the psi disruptor is destroyed. I found by experience that building an extractor before destroying the psi disruptor was a poor idea. First, the extractor apparently sticks out beyond the edges of the psi disruptor at some points, so that zerglings attacking the psi disruptor have to choose their target carefully and it takes longer to finish off the neutral building. Second, when the psi disruptor is destroyed, usually—though not always—the extractor is destroyed at the same time. I’m not sure how that happens, but it explains why I’ve always seen pros destroy the building first and take the gas after.

Yesterday I described Steamhammer’s new multi-part system to destroy the psi disruptor, and how the openings are adjusted to get the right number of zerglings at the right time to take the gas when needed. It’s complicated and it is needed only on occasional concept maps like Sparkle.

the geyser with a crystal on it

The high ground natural has a geyser with a big crystal on it. Only zerg can mine from that geyser. Steamhammer does not believe that it can take this gas, and never tries. I have a theory about why, but I haven’t looked into it; I chose to deal with the destructible buildings first, and ran out of time. It may have been the wrong decision. Maybe I should have skipped the destructible psi disruptor and figured out how to take the crystal gas.

As zerg, Steamhammer should be able to mine 3 geysers on its starting island. It is only smart enough to get 2.

taking the high ground natural

BWTA believes that the high ground natural base does not exist; it does not locate a base there for any race. Maybe it understands that the neutral zerg lair spreads creep which blocks the base placement. If so, it is a mistake: Zerg can build on the creep and mine the gas, and terran and protoss can either destroy the neutral lair and wait for the creep to dissipate, or (since they can’t mine the gas) locate a base on the opposite side of the minerals. Pros commonly take the opposite side of the minerals. To figure all this out needs a sophisticated understanding of the situation.

Steamhammer’s map analysis doesn’t understand the base either. It doesn’t realize that the neutral lair spreads creep, and tells all races they can build a base in the normal spot. Or, in some starting positions, near the normal spot; it sometimes displaces the base (maybe there’s an off-by-one bug). Since Steamhammer will be playing zerg in AIST and no other map has the weird features, it was good enough and I left it alone.

Steamhammer recognizes the neutral lair as a “blocker” that should be destroyed, but does not destroy it. I think the bug happens because of a use of BWTA that I didn’t remove. It does no harm in practice, so I left this one alone too.

I decided to do all this stuff and I’m not convinced that it was worth it. I could have concentrated on Third World instead. I would now have the mineral walking feature at a minimum, and it could share some pathfinding code with nydus canals. On the downside, I would have done less to improve Steamhammer’s play in AIST, because moving Sparkle from can’t-win to can-win is a bigger jump than moving Third World from can-win to plays-nicely. On the upside, I would have had a feature that is more widely useful. I feel some regret. I could have chosen to play somewhat better on a variety of maps, and instead chose to play substantially better on one map, Sparkle, that may never matter again. And that substantially better play is still painfully weak.

Steamhammer 1.4.3 uploaded to SSCAIT

Steamhammer 1.4.3 is uploaded. I cleared its learning data, so it is starting from zero and has to figure out on its own how each opponent plays. This is the AIST version. As I have mentioned, the changes affect play on SSCAIT so little that it will be hard to notice any difference.

Note: Because this is the same file I submitted to AIST, debug drawing is turned off. The stream will not show Steamhammer’s familiar game info and production displays.

Steamhammer 1.4.3 change list

AIST S1 submission is closed, so here is the change list for the new Steamhammer 1.4.3 which I submitted. I’ll upload this to SSCAIT tomorrow when I have energy again; I had an exhausting weekend. The SSCAIT upload will be the same file I submitted to AIST, and this would also be a good opportunity to clear Steamhammer’s learned data and start the blank slate test that I mentioned in this post.

Most of the work for this version was preparation to compete in AIST with its demanding maps. Play on other maps is barely different. This post is about what I did, and tomorrow I’ll go into the overall plan and what I didn’t do.

map support

• MapPartitions provides information about walkability and ground connectivity per walk tile. It is used in various places, mainly for figuring out what places a worker or a ground squad can reach, as is needed to support island (Sparkle) and semi-island (Third World) maps. It also helps on land maps that have islands or plateaus, which are common. squad.mapPartition() tells which map partition a squad is on (a squad might have units in more than one partition—it picks any one unit arbitrarily and goes with that unit’s partition). On the map Third World, the narrow ramps from the main bases seem impassible to Steamhammer at build tile resolution (32x32 pixels), but MapPartitions at walk tile resolution (8x8 pixels) understands that units can pass through. It fixes the inability to expand on Third World.

• A bug in map analysis caused a crash on Transistor. Fixed.

• On the map Third World, BWTA doesn’t provide a region for the enemy base. Steamhammer uses BWTA regions for directing the worker scout to and around the perimeter of the enemy base. The missing region caused an endless stream of exceptions and left the scouting worker idle. At some point I will have to provide another way to direct the worker scout (Locutus shows one idea), but for now I worked around it by releasing the scout if there is no region when there should be. Third World is a 2 player map, so it is relatively less harmful to skip worker scouting.

• I wrote 3 openings for Sparkle, a map which requires specialized openings—seriously specialized openings for this map only. Zerg has to make the right number of zerglings at the right time, as dictated by the map design, to get the next gas geyser its strategy calls for.

• I wanted to provide the Require: feature to configure openings appropriate to given map features, but my design was crummy. First, as described it cannot work, it’s not correct for all cases; second, because of how opening selection is implemented, it seems to require a substantial rewrite; and third, it interacts with opponent model opening selection in ways that I didn’t account for. Major ouch. For now, I hardcoded “if the map is Sparkle, select from the Sparkle strategy mix” of the 3 Sparkle openings. Hold your nose, it’s a disgusting hack.

• Sparkle places a psi disruptor on top of the geyser at each starting island’s low ground natural base. You have to destroy the psi disruptor to mine the gas. To support Sparkle (and a small number of other maps, such as Arkanoid), I assembled a complex feature that destroys neutral buildings which obstruct the operation of bases. Actually I kept it simple: The feature destroys neutral buildings which are very close to bases; good enough. Each base, during the bot’s initialization when its data structure is created, finds any neutral buildings that it thinks are in the way and remembers them as blockers. The tactical analyzer, choosing targets for squads to attack, looks to see if any of our bases have blockers, and may assign a squad to destroy the blockers. That involved adding a new squad order DestroyNeutrals and new code in the squad to implement the order (this part at least can be reused when I get around to destroying neutral buildings that block paths). When a blocker is destroyed, InformationManager (responsible for unit tracking) notices and informs Bases, which keeps a reverse index for efficiency and tells the affected base that its blocker is gone. Whew. Not simple, but necessary to play an acceptable game on Sparkle. The feature usually works but fails occasionally, apparently because InformationManager prematurely decides that a blocker has been destroyed; I’m not sure how that happens.

• Island support: Steamhammer normally sends an overlord scout, unless the opponent is known to be terran. It has zero overlord survival skills, so it doesn’t send an overlord to look at a terran base. On island maps, it can’t scout by ground, so I changed it to send the overlord anyway (and lose it to marines) if Steamhammer starts on an island.

• Island support: The Recon squad was designed to perform reconnaissance in force with a small ground squad. On an island map, instead it assigns 1 overlord to be the Recon squad. Obviously that’s no longer reconnaissance in force. It works adequately, but the lone overlord is vulnerable and easily lost.

• Island support: Zerg strategy is adapted to islands. Mainly, zerg goes air only until nydus canals are established. Since zerg doesn’t know how to establish nydus canals, that’s the entire game. It does make plenty of zerglings with its excess minerals, which scout around the island they are on and pounce on any drops.

• Island support: Zerg delays ground upgrades to save gas for its air strategy. It turned out to be a necessary adaptation. Steamhammer is normally aggressive about getting evo chamber upgrades, and it ate up so much gas that the mutalisk fleet was stunted.

• Zerg gets air upgrades when appropriate. Well, it gets air armor up to +2 before greater spire. Steamhammer hasn’t had that feature since early days; I finally restored it. This isn’t technically an island support feature, since it could happen in any game, but it is especially important with the island air strategy.

• Island support: The plan recognizer and plan predictor do not try to recognize or predict island plans; the enemy plan is always Unknown. For one thing, I would need to add new and different island plans; the existing plans don’t fit. For another, Steamhammer doesn’t understand that island and non-island maps are fundamentally different, and that what it learns about an opponent on an island map is probably wrong on a land map, and vice versa. To handle this right, the map adaptation in the opponent model needs more smarts. Anyway, the upshot is that the opponent model is effectively turned off on an island map.

•  I removed more uses of BWTA from various places in the code. There are still a lot more to go; I mainly removed the uses that got in the way for AIST.

buildings

• Fixed a nonfatal exception: A fresh Building object was initialized wrong in one case, so that canceling an expansion which could not be placed might throw. An expansion can almost always be placed (that is, can almost always have its final location set), so it was rare.

• Only print the “reserves wrong” debug message if the DrawBuildingInfo debug option is turned on. The “reserves wrong” message was supposed to shame me into fixing the underlying bugs, but it failed; the bugs are still there.

• In removing BWTA from the code which chooses where to place the next expansion, I removed a feature: Steamhammer will now happily expand to a base in a region where the enemy has buildings. I didn’t want to do extra work to keep using the BWTA region when I intend to drop BWTA. I may have to add the feature back if it causes problems.

tactics

• Squad status strings are a little more informative. Turn on DrawSquadInfo to see them. I should rewrite the squad display, because it’s tough to understand and messages from different squads can overlap on the screen.

• Tactical targeting: A lifted building will be assigned to a squad that can attack air. Clever, no? Oops, but a squad with zerglings and scourge qualifies... well, one step at a time.

• Micr0 targeting: A higher priority for air units to hit tanks. Picking off tanks can be critical, but Steamhammer didn’t think so.

• Micr0 targeting: Scourge are slightly more eager to hit carriers, as opposed to other targets.

code changes

• Changed instances of BWAPI::Position(0,0) to BWAPI::Positions::Origin.

• In the configuration file, I moved the debug log location from the Debug section to the IO section. Keeping the file stuff together seemed more important than keeping the debug stuff together, since the other debug options are unrelated to the debug log.

• Added the missing manual commands /set drawscoutinfo (forgotten long ago) and /set drawqueuefixinfo (forgotten recently).

• Formerly, if you asked for the center point of a squad with no units, you got (0,0) and (if it was turned on) a message on the screen. Not so useful. Now you get the location of the starting base—the spot where future squad members are most likely to appear—and no message.

openings

• The Sparkle openings are Sparkle 1HatchMuta, Sparkle 2HatchMuta, and Sparkle 3HatchMuta. Simple. The 1 hatchery opening starts with 9 gas 9 pool to get mutalisks as fast as possible, and the others start with 12 hatchery. The followups are different from the land-based parent openings, though: They have few zerglings at specific timings, more drones, and earlier hatcheries and second gas. Details matter—it’s not so simple after all.

• New anti-factory variations to offer choices in defeating terran vulture-first play: AntiFact_13Pool and AntiFact_2Hatch.

zerg fixes

• Fixed a problem that could make drones way over the limit. It was rare—except on island maps. I had been puzzling over this one for months.

• Fixed a typo in calculating gas reserves after ordering a carapace upgrade. It was a bug, but the effect is barely noticeable even if you know what to look for—it might delay a few gas units from this production round to the next round seconds later.

Next: The ordeal of supporting Sparkle.

Steamhammer and AIST S1

Steamhammer is registered for AIST S1. It is almost done; I am working on final testing and fixes. After submission closes, I’ll release the new version as 1.4.3 and upload it on SSCAIT. Features that I formerly planned for 1.4.3 will be pushed to 1.4.4, and so on down the line. Expect the usual change list et cetera, plus discussions of the map features and how I chose to cope with them.

On SSCAIT, it will probably be hard to notice a difference from version 1.4.2, because most of the changes are to support the difficult maps of AIST. There are about 7 bug fixes and improvements that affect play on all maps, but in such subtle ways that neither stream viewers nor Steamhammer’s elo should see a change. Even so, there are a lot of changes, including entirely new features like MapPartitions. Judging the change bars by eye, I’d say I touched about 15% of the lines of StrategyBossZerg. There are also changes to tactics, micro, map exploration, the Recon squad, and scouting.

Steamhammer is not remotely ready for the tournament. The impossible is impossible. I took shortcuts to get as far as I did. But it can play games on the AIST maps without looking like a moth flying into a windowpane (“I see the light, it’s that way.” Wham! “Oh... I see the light again, it’s that way.” Wham!), and it can defeat some opponents. It’s a huge step up from crashing, or throwing endless exceptions, or playing a mass zergling opening on an island map. And I’ve made more progress toward removing BWTA, though it’s haphazard—I removed the parts that were in the way.

Next: The new bot Proxy.

confused game Steamhammer-Tyr

Steamhammer and Tyr terran by Simon Prins played a hilarious game on Moon Glaive. I think that Steamhammer decided on a 3 hatchery before spawning pool build (it’s not entirely clear), but the zerg build became confused because Tyr’s scouting SCV delayed the natural hatchery, and because Steamhammer has an issue on this map where it sometimes sets its bases down unexpectedly far away. The build as executed was hatchery-pool-hatchery, with the third hatchery in Tyr’s natural because zerg hadn’t located the enemy yet. Something similar happened almost exactly a year ago in a game versus Krasi0.

Tyr reacted differently than Krasi0: Tyr ignored the zerg base in its natural!

Tyr is unworried by the zerg base

The marines fired at the zerglings, but the hatchery and drones were in plain sight, and they just didn’t care. Their job, I gather, was to guard the natural against zerg attack, and the functioning zerg base was not an attack. They took their orders literally! Steamhammer was also unworried, and mined minerals contentedly.

Tyr was bright enough to figure out that it had to expand somewhere else, and planted a command center at what would normally be its 3rd base position. The zerg play was disorganized, but the terran play was more disorganized, and Steamhammer won effortlessly.