alkurbatov / suvorov-bot

Starcraft 2 rule-based bot capable to play for all races.
MIT License
18 stars 6 forks source link

Estimate current income rate #55

Open ImpulseCloud opened 3 years ago

ImpulseCloud commented 3 years ago

In order to fix #37, we need to be able to accurately estimate our immediate income rate, future income rate, and predict how long it will be before we can afford future build order items (while training military units/tech). This also affects other issues related to macro economy planning, resource mix (mineral/gas ratio), and expansion timings.

Use as example: https://github.com/BurnySc2/sc2-planner/blob/master/src/game_logic/income.ts

ImpulseCloud commented 3 years ago

Problem: If workers deliver 100 minerals and built a Supply Depot in the same frame, it will look like nothing has been mined. Even if we keep track of money spent, if a building fails to build because of a burrowed zergling, etc, it will look like we gained minerals when it's auto-canceled. It's complex to keep track of all possible issues with canceling build/research, and that's best done in a separate module. (API shows NET income, not GROSS. Also, shows non-mined income)

Solution: Track income per worker via checking when unit.IsCarryingMinerals/Vespene turns from =True to =False, so we can directly count resources delivered. (can check BuffId for HighYieldMinerals)

sc2_client.h - helper function

bool IsCarryingMinerals(const Unit& unit) {
    auto is_mineral = [](const BuffID& buff){
        return buff == BUFF_ID::CARRYMINERALFIELDMINERALS
            || buff == BUFF_ID::CARRYHIGHYIELDMINERALFIELDMINERALS;
    };
    return std::find_if(unit.buffs.begin(), unit.buffs.end(), is_mineral) != unit.buffs.end();
}

bool IsCarryingVespene(const Unit& unit) {
    auto is_vespene = [](const BuffID& buff){
        return buff == BUFF_ID::CARRYHARVESTABLEVESPENEGEYSERGAS
            || buff == BUFF_ID::CARRYHARVESTABLEVESPENEGEYSERGASPROTOSS
            || buff == BUFF_ID::CARRYHARVESTABLEVESPENEGEYSERGASZERG;
    };
    return std::find_if(unit.buffs.begin(), unit.buffs.end(), is_vespene) != unit.buffs.end();
}

Note: Should also check that worker did not just switch between mineral/vespene, and is_alive and seen in current game_loop (in case BuffId is not visible as a passenger or something)

Current Rate: Can record mined income over last 120 frames (roughly 5 seconds, ~2 mineral trips to far minerals) and average over last 4 (or more) records (of 120 frame set). Most supply-decisions and training-times have a duration of 20 seconds, so we should seek to be able to predict available resources after next 20 seconds.

ImpulseCloud commented 3 years ago

Have a preliminary commit for this: https://github.com/ImpulseCloud/suvorov-Impulse/commit/244d35c5cfc306cd44c050e756303bb8e3e463c4

Not yet done the average of 4 records or hooked it up to anything, but have tested it and it accurately tracks mined-income every 120 frames, and inserts into a vector of records.

You can tell when a miner's cycle is off a bit, and the income varies a bit (also due to workers stopping to build buildings).

alkurbatov commented 3 years ago

“ If workers deliver 100 minerals and built a Supply Depot in the same frame, it will look like nothing has been mined.”

I think the game works in a different way. There is a Score class which contains income rate. If I not mistaken, it doesn’t change when we spend minerals.

“ API shows NET income, not GROSS. Also, shows non-mined income” But why do we need such precise income measurement?

ImpulseCloud commented 3 years ago

Ah, I see it now in Score.proto , accessible from gAPI->observation().GetScore().ScoreDetails . Awesome, thanks!

I think it should be as precise as is easily possible / available. Why should it be less precise than possible? It can be averaged/smoothed for less precision.

I think the ScoreDetails.collection_rate_minerals/vespene may be good enough, but I will do a little testing to see how well. I agree it is likely not good time spent now to get super-precise (such as predicting what frame a worker will return their current resource carried, based on speed/distance)