unit tracking and inferences
I’m thinking about unit tracking, and the many many issues it is connected with.
Steamhammer keeps a UnitData data structure to remember enemy units when they are out of sight. It’s only modestly modified from the one inherited from UAlbertaBot. The information manager keeps the information updated for both enemy and friendly units. But should it keep data on friendly units? On the one hand, it might be convenient to have a fixed format for all uses. And Steamhammer does use UnitData for friendly units in certain cases. On the other, it’s duplicating information that is already available directly through BWAPI. What’s the right design?
Many bots place a wrapper around BWAPI::Unit to keep their extra information. It seems like a good design, but I’ve been reluctant to switch to it, partly because there was no need for the extra work, and partly because I don’t understand all the ramifications. Now I’m thinking of doing the work anyway, and when do I ever understand all the ramifications? Well, I don’t need to decide yet.
Then there is the issue of inferred information. If I see a vulture, I know there is a factory, even if I’ve never seen the factory. It’s a known enemy building with almost no other information available; its location might be anywhere that I haven’t seen in the time it takes to build a factory and a vulture. If I see a tank, I know there is a factory with addon. If I see a dark templar, or if I see fast zealots, I know there is a citadel of Adun. I want Steamhammer to be able to draw all these inferences. Even with the current plan recognizer, it would make the recognition rules simpler. In the future, with a more powerful enemy strategy predictor, it will be valuable. I haven’t decided how to store the information. It could be fitted into a unit wrapper.
More inferences: If I see a spider mine, I know there’s a vulture and a machine shop. If I see a broodling, or a parasited neutral unit, I know there’s a queen. If I see something of the enemy’s on an island, or notice that the blocking minerals on an island have been mined out, I know there is transport. There may be an exception for units which were accidentally pushed through unwalkable ground, but that should be exceedingly rare. Also, some cases have an exception for mind control. If I dropped a worker on an island on Python, and it wandered near the edge, it could be mind controlled from a neighboring base, and the enemy could mine out the minerals and take the base without transport.
The idea of inferences with exceptions brings up the issue of estimates and uncertain inferences. As part of understanding the enemy strategy, you want to estimate the enemy’s economy—what count of resources mined so far, what rate of current mining? And the enemy’s production capacity—how many barracks, how many factories, how many starports? Sometimes, especially early in the game, you can get an accurate estimate by seeing the enemy’s army size and comparing it with what is possible. I saw when the first barracks was under construction, and by now it could have trained x marines, but I see x+n marines, so there are 2 barracks or more. More complex estimates are possible, based on income limits. On top of that, you can combine technical estimates made using the rules of the game with learned data (here’s what this enemy has done in the past). Fancy estimates like this do not fit into a simple unit tracking framework, but they interact with it; you have to take into account the intersection.
I’m not off the topic of unit tracking yet. When you get to the level of inferring the enemy’s plans based on all available information, there is one more input you want: A model of what the enemy knows about you. You can’t be sure what the enemy has seen, because the enemy may see you without being spotted in turn. There could be an overlord over that cliff watching, or the enemy units may have a longer sight range (for example, a probe has a longer sight range than an SCV or drone, thanks to protoss high tech). But when the early game enemy worker scout is cruising around your base, there is no problem (beyond technical details of visibility) keeping a unit tracking record of what stuff of yours it saw when. Top bots use their scouting data to get ready for what you are planning, and if you know what they know and can guess what they are getting ready for, you can lay better plans yourself (or try a trick like placing a hydra den in the main and a spire in a far corner of the natural).
These things will matter as I make progress on Steamhammer’s strategy adaptation, so I should start getting my ideas in order now.
Comments
Dan on :
Some of PurpleWave's strategy fingerprints work the way you describe, by estimating the required number of production facilities to explain the units the enemy has shown. Currently it uses this formulaically to predict 2/3-Factory openings, and manually to predict 4-Gate Dragoon or 2-Gate Zealot. I find it hard to tune given that in practice you will not see all their units, and often not enough to rule out other builds.
I find proxy classes for BWAPI types -- and a common interface for both friendly and enemy units -- to be absolute must-haves for rapid and sane development. If you want common math functions -- for example, to measure the damage one unit can do to another including weird units like Carriers and Bunkers or where one unit should stand to attack another -- you'll go nuts if you have to write distinct implementations for unrelated types. Those calculations tend to stack on one another too, and having to convert from BWAPI units to more useful types all over a codebase adds a lot of friction. I find the abstractions in https://github.com/dgant/PurpleWave/blob/master/src/ProxyBwapi/UnitInfo/UnitInfo.scala critical to implementing complex micro techniques and getting them right the first time.
Marian on :
For units it's UnitInfo and also I use CustomOrder and derived classes for every order I issue(from scout, attack to morph and research). Furthermore I have a lot of utilities in UnitUtils for doing common stuff like distance between units, getting weapon range or even checking if unit is friend or foe.
And it is the right choice even from the point of app design.
Ideally you would want to wrap everything you want from BWAPI into some GameState class and use only GameState for everything. I would like to try to (re-)design bot like this and see if it not too hard on performance(I think C++ should still be fine).
Advantages from this approach:
- if you make all your classes(including GameState) serializable, you can create an environment where bugs are super easy to detect and solve
- it could be possible to recreate GameState even from replay
- bugs/inconsistencies in BWAPI are solvable in your own wrapper functions
- custom orders manage resource and frame-timing inconsistencies so I don't have to worry about them
- UnitInfo class should ideally provide a lot of useful information: like tracking unit position in time(is it going away or closing in?, which base is it going to?, is unit stuck? ...), when did the unit last time attack or get hit? ...
- you could place non-existant shadow Units if your prediction module thinks there should be an expansion or additional units
Dan on :
Jay Scott on :