From 7abf8c26bcff47258e0b10223487f8f89e8232cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Bergstr=C3=B6m?= <davbe125@student.liu.se> Date: Thu, 5 Jul 2018 14:18:29 +0200 Subject: [PATCH] Separate baseline agent from API --- python-api-src/library.cpp | 1 - python-api-src/library.h | 8 - src/IDABot.cpp | 463 ------------------------------------ src/IDABot.h | 70 ------ src/MyAgent.cpp | 470 +++++++++++++++++++++++++++++++++++++ src/MyAgent.h | 77 ++++++ src/main.cpp | 4 +- 7 files changed, 550 insertions(+), 543 deletions(-) create mode 100644 src/MyAgent.cpp create mode 100644 src/MyAgent.h diff --git a/python-api-src/library.cpp b/python-api-src/library.cpp index 1856427..1baf98a 100644 --- a/python-api-src/library.cpp +++ b/python-api-src/library.cpp @@ -60,7 +60,6 @@ PYBIND11_MODULE(library, m) // the name used for inheritance in Pyhon stays the same. Weird. .def("OnGameStart", &IDABot::OnGameStart) .def("OnStep", &IDABot::OnStep) - .def("OnStep_UpdateIDABot", &IDABot::OnStep_UpdateIDABot) .def("get_all_units", &IDABot::GetAllUnits) .def("get_my_units", &IDABot::GetMyUnits) .def("get_player_race", &IDABot::GetPlayerRace) diff --git a/python-api-src/library.h b/python-api-src/library.h index ebfbd65..8c960ce 100644 --- a/python-api-src/library.h +++ b/python-api-src/library.h @@ -43,14 +43,6 @@ public: OnStep ); } - void OnStep_UpdateIDABot() override - { - PYBIND11_OVERLOAD( - void, - IDABot, - OnStep_UpdateIDABot - ); - } }; // The functions below are all defined in different .cpp files, in order diff --git a/src/IDABot.cpp b/src/IDABot.cpp index edd114b..afd0902 100644 --- a/src/IDABot.cpp +++ b/src/IDABot.cpp @@ -1,463 +1,6 @@ #include "IDABot.h" #include "Util.h" -void IDABot::OnStep_UpdateIDABot() -{ - // This is the entry point of the bot. - // This function is called every time the game loop is run. - - //if (!Bases().getPlayerStartingBaseLocation(Players::Enemy)->isExplored()) - //{ - // assignScout(); - //} - - std::vector<UnitType> build_plan = CreateBuildPlan(); - manageWorkers(build_plan); - manageBuilding(build_plan); -} - -void IDABot::manageBuilding(std::vector<UnitType> & build_plan) -{ - std::map<UnitType, int> currently_being_built = numberOfTypeBeingBuilt(); - - for (auto & pair : currently_being_built) - std::cout << "Building " << pair.second << " of " << pair.first.getName() << std::endl; - - int minerals = GetMinerals(); - int gas = GetGas(); - // TODO: Supply - - for (UnitType type : build_plan) - { - if (currently_being_built[type] > 0) - { - currently_being_built[type]--; - std::cout << "Already building " << type.getName() << std::endl; - continue; - } - - // If we cannot afford the next thing, we don't want to build at all - if (type.mineralPrice() > minerals || type.gasPrice() > gas) - { - break; - } - - std::vector<Unit> producers = getProducer(MetaType(type, *this)); - - // Reserve some resources - // TODO: Only reserve resources if the corresponding worker is in BuildingWalking - minerals -= type.mineralPrice(); - gas -= type.gasPrice(); - - if (!producers.empty()) - { - if (type.isBuilding()) - { - for (Unit & worker : getWorkers()) - { - if (isFree(worker)) - { - CCTilePosition position = getBuildPosition(type); - BuildStatus status{ type, position }; - currently_building[worker] = status; - // Reserve the location - m_buildingPlacer.reserveTiles(position.x, position.y, type.tileWidth(), type.tileHeight()); - - // Update economy book-keeping - if (type.isRefinery() || type.isResourceDepot()) - { - economy_spending.minerals += type.mineralPrice(); - economy_spending.gas += type.gasPrice(); - } - else if (type.supplyProvided() == 0) - { - military_spending.minerals += type.mineralPrice(); - military_spending.gas += type.gasPrice(); - } - assignWork(worker, Assignment::BuildWalking); - return; - } - } - } - else if (type.isCombatUnit() || type.isWorker()) - { - // TODO: Remove code-duplication - // Update economy book-keeping - if (type.isWorker()) - { - economy_spending.minerals += type.mineralPrice(); - economy_spending.gas += type.gasPrice(); - } - else - { - military_spending.minerals += type.mineralPrice(); - military_spending.gas += type.gasPrice(); - } - std::cout << "Training unit " << type.getName() << std::endl; - producers.front().train(type); - return; - } - } - } -} - -bool IDABot::isFree(Unit & worker) -{ - if (workerAssignment.count(worker) > 0) - { - return workerAssignment[worker] == Assignment::Mineral; - } - else - { - return true; - } -} - -std::vector<UnitType> IDABot::CreateBuildPlan() -{ - // Count the total number of minerals, including all bases - size_t number_of_minerals{ 0 }; - for (const BaseLocation * base : Bases().getOccupiedBaseLocations(Players::Self)) - { - number_of_minerals += base->getMinerals().size(); - } - size_t number_of_workers = getWorkers().size(); - size_t available_food = GetMaxSupply() - GetCurrentSupply(); - - std::vector<UnitType> build_plan; - - std::cout << "Military spending (minerals) " << military_spending.minerals << std::endl; - std::cout << "Economy spending (minerals) " << economy_spending.minerals << std::endl; - - // TODO: Make a better decision here - if ((500 + military_spending.minerals) < economy_spending.minerals) - { - CreateMaximizeMilitaryBuildPlan(build_plan, available_food); - CreateMaximizeEconomyBuildPlan(number_of_minerals, number_of_workers, available_food, build_plan); - } - else - { - CreateMaximizeEconomyBuildPlan(number_of_minerals, number_of_workers, available_food, build_plan); - CreateMaximizeMilitaryBuildPlan(build_plan, available_food); - } - - for (size_t i{}; i < build_plan.size() && i < 10; ++i) - { - std::cout << build_plan[i].getName() << ", "; - } - std::cout << std::endl; - - return build_plan; -} - -void IDABot::CreateMaximizeMilitaryBuildPlan(std::vector<UnitType> &build_plan, size_t &available_food) -{ - for (auto & pair : military_goal) - { - // How many do we already have? - size_t units_of_type = GetUnits(pair.first).size(); - - // First, do we meet the military goal for this UnitType? - if (units_of_type >= pair.second) { - continue; - } - - // Second, can we produce anything of UnitType? - std::vector<Unit> producers = getProducer(MetaType(pair.first, *this), true); - // TODO: Don't build too many Barracks if we don't need to - if (producers.empty()) - { - // TODO: Calculate what we need to build using the TechTree - // For now, let's build a barrack - build_plan.push_back(UnitType(sc2::UNIT_TYPEID::TERRAN_BARRACKS, *this)); - } - else - { - // TODO: This is never run, or is it?? - int units_needed = pair.second - units_of_type; - int food_required = pair.first.supplyRequired(); - for (int i{}; i < units_needed; ++i) - { - if (available_food >= food_required) - { - return; - } - else - { - available_food -= food_required; - build_plan.push_back(pair.first); - - } - } - } - } -} - -void IDABot::CreateMaximizeEconomyBuildPlan(size_t &number_of_minerals, size_t &number_of_workers, size_t &available_food, std::vector<UnitType> &build_plan) -{ - const int WORKER_PER_MINERAL_DEPOSIT = 3; - const int FOOD_THRESHOLD_BUILD_DEPOT = 1; - - // Plan for the two next base expansions - for (int i = 0; i < 2; i++) - { - while (WORKER_PER_MINERAL_DEPOSIT * number_of_minerals > number_of_workers) - { - if (available_food == FOOD_THRESHOLD_BUILD_DEPOT) - { - UnitType type{ sc2::UNIT_TYPEID::TERRAN_SUPPLYDEPOT, *this }; - build_plan.push_back(type); - - available_food += type.supplyProvided(); // TODO: Maybe 16 sometimes? (+16 with Calldown: Supplies) - } - else - { - build_plan.push_back(UnitType(sc2::UNIT_TYPEID::TERRAN_SCV, *this)); - available_food--; - number_of_workers++; - } - } - - UnitType type{ sc2::UNIT_TYPEID::TERRAN_COMMANDCENTER, *this }; - build_plan.push_back(type); - available_food += type.supplyProvided(); - - const BaseLocation * next_expansion = Bases().getNextExpansion(Players::Self); - if (next_expansion) - { - number_of_minerals += next_expansion->getMinerals().size(); - } - } -} - -void IDABot::assignScout() -{ - // Assumes that at least one worker is unassigned - for (Unit worker : getWorkers()) - { - if (workerAssignment.count(worker) == 0) - { - assignWork(worker, Assignment::Scout); - std::cout << "Assigned worker to Scout" << std::endl; - break; - } - } -} - -void IDABot::manageWorkers(std::vector<UnitType> & build_plan) -{ - std::vector<Unit> workers = getWorkers(); - for (Unit worker : workers) - { - // If the worker has does not have an assignment - if (workerAssignment.count(worker) == 0) - { - assignWork(worker, Assignment::Mineral); - } - else - { - CCTilePosition position; - switch (workerAssignment[worker]) { - case Assignment::Mineral: - case Assignment::Gas: - // Never change let the gas/mineral workers rest - break; - case Assignment::Scout: - if (Bases().getPlayerStartingBaseLocation(Players::Enemy)->isExplored()) { - assignWork(worker, Assignment::Mineral); - } - break; - case Assignment::BuildWalking: - position = currently_building[worker].position; - if (position == Util::GetTilePosition(worker.getPosition())) - { - assignWork(worker, Assignment::BuildBuilding); - } - break; - case Assignment::BuildBuilding: - // Probably done, - if (worker.isIdle()) - { - BuildStatus status = currently_building[worker]; - m_buildingPlacer.freeTiles(status.position.x, status.position.y, status.type.tileWidth(), status.type.tileHeight()); - currently_building.erase(worker); - assignWork(worker, Assignment::Mineral); - } - break; - } - } - } -} - -void IDABot::assignWork(Unit & worker, Assignment assignment) -{ - // Assigns worker to assignment - workerAssignment[worker] = assignment; - Unit mineral; - const BaseLocation * enemyBaseLocation; - CCTilePosition position; - AssignmentData data; - const BaseLocation * assigned_base; - - switch (assignment) { - case Assignment::Mineral: - // Select which base to mine for - assigned_base = AssignBase(worker); - mineral = getClosestMineral(assigned_base->getPosition()); - worker.rightClick(mineral); - break; - case Assignment::Scout: - enemyBaseLocation = Bases().getPlayerStartingBaseLocation(Players::Enemy); - - if (enemyBaseLocation) - { - std::cout << "Enemy base location known!" << std::endl; - worker.move(enemyBaseLocation->getPosition()); - } - else - { - std::cout << "Enemy base location unknown!" << std::endl; - - for (const BaseLocation * location : Bases().getStartingBaseLocations()) - { - if (!Map().isExplored(location->getPosition())) - { - worker.move(location->getPosition()); - break; - } - } - } - break; - case Assignment::BuildWalking: - worker.move(currently_building[worker].position); - break; - case Assignment::BuildBuilding: - worker.build(currently_building[worker].type, currently_building[worker].position); - break; - } - -} - -std::map<UnitType, int> IDABot::numberOfTypeBeingBuilt() const -{ - std::map<UnitType, int> being_built; - for (auto & pair : currently_building) - { - being_built[pair.second.type]++; - } - return being_built; -} - -const BaseLocation * IDABot::AssignBase(Unit & worker) -{ - std::set<const BaseLocation *> base_locations = Bases().getOccupiedBaseLocations(Players::Self); - int most_needed_workers_value{std::numeric_limits<int>().min()}; - const BaseLocation * most_needed_workers_pointer{}; - - for (const BaseLocation * base_location : base_locations) - { - int workers_needed = 3 * base_location->getMinerals().size(); - int number_of_workers; - if (base_assignment.count(base_location) > 0) - { - std::vector<Unit> & workers = base_assignment[base_location]; - number_of_workers = workers.size(); - } - else - { - number_of_workers = 0; - std::vector<Unit> workers; - base_assignment[base_location] = workers; - } - int needed_workers = workers_needed - number_of_workers; - if (needed_workers > most_needed_workers_value) - { - most_needed_workers_value = needed_workers; - most_needed_workers_pointer = base_location; - } - } - std::vector<Unit> & workers = base_assignment[most_needed_workers_pointer]; - workers.push_back(worker); - return most_needed_workers_pointer; -} - -CCTilePosition IDABot::getBuildPosition(UnitType & building) -{ - CCTilePosition tile_position; - if (Util::GetTownHall(GetPlayerRace((int) Players::Self), *this) == building) - { - tile_position = Bases().getNextExpansion((int)Players::Self)->getDepotPosition(); - } - else - { - CCPosition position = Bases().getPlayerStartingBaseLocation(Players::Self)->getPosition(); - tile_position = m_buildingPlacer.getBuildLocationNear(Util::GetTilePosition(position), building, 1); - } - return tile_position; -} - -Unit IDABot::getClosestMineral(const CCPosition & position) const -{ - Unit bestMineral; - double bestDist = 100000; - - for (auto & mineral : GetAllUnits()) - { - if (!mineral.getType().isMineral()) continue; - - double dist = Util::Dist(mineral, position); - - if (dist < bestDist) - { - bestMineral = mineral; - bestDist = dist; - } - } - - return bestMineral; -} - -std::vector<Unit> IDABot::getWorkers() -{ - std::vector<Unit> workers; - - for (auto & unit : GetMyUnits()) - { - if (unit.getType().isWorker()) - { - workers.push_back(unit); - } - } - - return workers; -} - -std::vector<Unit> IDABot::getProducer(const MetaType & type, bool includeBusy, bool includeIncomplete) -{ - // get all the types of units that cna build this type - auto & producerTypes = Data(type).whatBuilds; - - // make a set of all candidate producers - std::vector<Unit> candidateProducers; - for (auto unit : UnitInfo().getUnits(Players::Self)) - { - // reasons a unit can not train the desired type - if (std::find(producerTypes.begin(), producerTypes.end(), unit.getType()) == producerTypes.end()) { continue; } - if (!includeIncomplete && !unit.isCompleted()) { continue; } - if (!includeBusy && Data(unit).isBuilding && unit.isTraining()) { continue; } - if (unit.isFlying()) { continue; } - - // TODO: if unit is not powered continue - // TODO: if the type is an addon, some special cases - // TODO: if the type requires an addon and the producer doesn't have one - - // if we haven't cut it, add it to the set of candidates - candidateProducers.push_back(unit); - } - - return candidateProducers; -} - IDABot::IDABot() : m_map(*this) , m_bases(*this) @@ -465,7 +8,6 @@ IDABot::IDABot() , m_techTree(*this) , m_buildingPlacer(*this) { - military_goal[UnitType(sc2::UNIT_TYPEID::TERRAN_MARINE, *this)] = 30; } void IDABot::OnGameStart() @@ -530,11 +72,6 @@ void IDABot::OnStep() m_unitInfo.onFrame(); m_bases.onFrame(); - // ----------------------------------------------------------------- - // Run the actual bot. - // ----------------------------------------------------------------- - OnStep_UpdateIDABot(); - // ----------------------------------------------------------------- // Draw debug interface, and send debug interface to the Sc2 client. // ----------------------------------------------------------------- diff --git a/src/IDABot.h b/src/IDABot.h index 09ac90e..809e8d2 100644 --- a/src/IDABot.h +++ b/src/IDABot.h @@ -19,7 +19,6 @@ class IDABot : public sc2::Agent BaseLocationManager m_bases; UnitInfoManager m_unitInfo; TechTree m_techTree; - // TODO: This should not be exported for student use BuildingPlacer m_buildingPlacer; std::vector<Unit> m_allUnits; @@ -34,75 +33,6 @@ public: void OnGameStart() override; void OnStep() override; - virtual void OnStep_UpdateIDABot(); - - /* - My stuff - */ - - // Worker management - enum Assignment { - Scout, - Mineral, - Gas, - BuildWalking, - BuildBuilding - }; - - struct AssignmentData { - // When assigned to building, this corresponds to the location for the building - CCTilePosition buildGoal; - }; - - // Building management - - // Worker assignment book-keeping - std::map<Unit, Assignment> workerAssignment; - - // When workers are assigned to either minerals or gas, they get assigned a base - std::map<const BaseLocation *, std::vector<Unit>> base_assignment; - - std::vector<UnitType> build_order; // TODO: Not used - - std::vector<UnitType> CreateBuildPlan(); - void CreateMaximizeMilitaryBuildPlan(std::vector<UnitType> &build_plan, size_t &available_food); - void CreateMaximizeEconomyBuildPlan(size_t &number_of_minerals, size_t &number_of_workers, size_t &available_food, std::vector<UnitType> &build_plan); - void assignScout(); - void manageWorkers(std::vector<UnitType> & build_plan); - void manageBuilding(std::vector<UnitType> & build_plan); - bool isFree(Unit & worker); - void assignWork(Unit & worker, Assignment assignment); - std::map<UnitType, int> numberOfTypeBeingBuilt() const; - const BaseLocation * AssignBase(Unit & worker); - CCTilePosition getBuildPosition(UnitType & building); - Unit getClosestMineral(const CCPosition & unit) const; - - struct BuildStatus - { - UnitType type; - CCTilePosition position; - int idle; - }; - std::map<Unit, BuildStatus> currently_building; - - // Military management - std::map<UnitType, int> military_goal; - - // Economy - struct Resources - { - int minerals; - int gas; - }; - - Resources military_spending{}; - Resources economy_spending{}; - - // Getters - std::vector<Unit> getWorkers(); - - // Maybe - std::vector<Unit> getProducer(const MetaType & type, bool includeBusy = false, bool include_incomplete = false); /* API for students diff --git a/src/MyAgent.cpp b/src/MyAgent.cpp new file mode 100644 index 0000000..087ca33 --- /dev/null +++ b/src/MyAgent.cpp @@ -0,0 +1,470 @@ +#include "MyAgent.h" + +MyAgent::MyAgent() +{ + military_goal[UnitType(sc2::UNIT_TYPEID::TERRAN_MARINE, *this)] = 30; +} + +void MyAgent::OnGameStart() +{ + IDABot::OnGameStart(); +} + +void MyAgent::OnStep() +{ + IDABot::OnStep(); + // This is the entry point of the bot. + // This function is called every time the game loop is run. + + //if (!Bases().getPlayerStartingBaseLocation(Players::Enemy)->isExplored()) + //{ + // assignScout(); + //} + + std::vector<UnitType> build_plan = CreateBuildPlan(); + manageWorkers(build_plan); + manageBuilding(build_plan); +} + +void MyAgent::manageBuilding(std::vector<UnitType> & build_plan) +{ + std::map<UnitType, int> currently_being_built = numberOfTypeBeingBuilt(); + + for (auto & pair : currently_being_built) + std::cout << "Building " << pair.second << " of " << pair.first.getName() << std::endl; + + int minerals = GetMinerals(); + int gas = GetGas(); + // TODO: Supply + + for (UnitType type : build_plan) + { + if (currently_being_built[type] > 0) + { + currently_being_built[type]--; + std::cout << "Already building " << type.getName() << std::endl; + continue; + } + + // If we cannot afford the next thing, we don't want to build at all + if (type.mineralPrice() > minerals || type.gasPrice() > gas) + { + break; + } + + std::vector<Unit> producers = getProducer(MetaType(type, *this)); + + // Reserve some resources + // TODO: Only reserve resources if the corresponding worker is in BuildingWalking + minerals -= type.mineralPrice(); + gas -= type.gasPrice(); + + if (!producers.empty()) + { + if (type.isBuilding()) + { + for (Unit & worker : getWorkers()) + { + if (isFree(worker)) + { + CCTilePosition position = getBuildPosition(type); + BuildStatus status{ type, position }; + currently_building[worker] = status; + // Reserve the location + BuildingPlacer().reserveTiles(position.x, position.y, type.tileWidth(), type.tileHeight()); + + // Update economy book-keeping + if (type.isRefinery() || type.isResourceDepot()) + { + economy_spending.minerals += type.mineralPrice(); + economy_spending.gas += type.gasPrice(); + } + else if (type.supplyProvided() == 0) + { + military_spending.minerals += type.mineralPrice(); + military_spending.gas += type.gasPrice(); + } + assignWork(worker, Assignment::BuildWalking); + return; + } + } + } + else if (type.isCombatUnit() || type.isWorker()) + { + // TODO: Remove code-duplication + // Update economy book-keeping + if (type.isWorker()) + { + economy_spending.minerals += type.mineralPrice(); + economy_spending.gas += type.gasPrice(); + } + else + { + military_spending.minerals += type.mineralPrice(); + military_spending.gas += type.gasPrice(); + } + std::cout << "Training unit " << type.getName() << std::endl; + producers.front().train(type); + return; + } + } + } +} + +bool MyAgent::isFree(Unit & worker) +{ + if (workerAssignment.count(worker) > 0) + { + return workerAssignment[worker] == Assignment::Mineral; + } + else + { + return true; + } +} + +std::vector<UnitType> MyAgent::CreateBuildPlan() +{ + // Count the total number of minerals, including all bases + size_t number_of_minerals{ 0 }; + for (const BaseLocation * base : Bases().getOccupiedBaseLocations(Players::Self)) + { + number_of_minerals += base->getMinerals().size(); + } + size_t number_of_workers = getWorkers().size(); + size_t available_food = GetMaxSupply() - GetCurrentSupply(); + + std::vector<UnitType> build_plan; + + std::cout << "Military spending (minerals) " << military_spending.minerals << std::endl; + std::cout << "Economy spending (minerals) " << economy_spending.minerals << std::endl; + + // TODO: Make a better decision here + if ((500 + military_spending.minerals) < economy_spending.minerals) + { + CreateMaximizeMilitaryBuildPlan(build_plan, available_food); + CreateMaximizeEconomyBuildPlan(number_of_minerals, number_of_workers, available_food, build_plan); + } + else + { + CreateMaximizeEconomyBuildPlan(number_of_minerals, number_of_workers, available_food, build_plan); + CreateMaximizeMilitaryBuildPlan(build_plan, available_food); + } + + for (size_t i{}; i < build_plan.size() && i < 10; ++i) + { + std::cout << build_plan[i].getName() << ", "; + } + std::cout << std::endl; + + return build_plan; +} + +void MyAgent::CreateMaximizeMilitaryBuildPlan(std::vector<UnitType> &build_plan, size_t &available_food) +{ + for (auto & pair : military_goal) + { + // How many do we already have? + size_t units_of_type = GetUnits(pair.first).size(); + + // First, do we meet the military goal for this UnitType? + if (units_of_type >= pair.second) { + continue; + } + + // Second, can we produce anything of UnitType? + std::vector<Unit> producers = getProducer(MetaType(pair.first, *this), true); + // TODO: Don't build too many Barracks if we don't need to + if (producers.empty()) + { + // TODO: Calculate what we need to build using the TechTree + // For now, let's build a barrack + build_plan.push_back(UnitType(sc2::UNIT_TYPEID::TERRAN_BARRACKS, *this)); + } + else + { + // TODO: This is never run, or is it?? + int units_needed = pair.second - units_of_type; + int food_required = pair.first.supplyRequired(); + for (int i{}; i < units_needed; ++i) + { + if (available_food >= food_required) + { + return; + } + else + { + available_food -= food_required; + build_plan.push_back(pair.first); + + } + } + } + } +} + +void MyAgent::CreateMaximizeEconomyBuildPlan(size_t &number_of_minerals, size_t &number_of_workers, size_t &available_food, std::vector<UnitType> &build_plan) +{ + const int WORKER_PER_MINERAL_DEPOSIT = 3; + const int FOOD_THRESHOLD_BUILD_DEPOT = 1; + + // Plan for the two next base expansions + for (int i = 0; i < 2; i++) + { + while (WORKER_PER_MINERAL_DEPOSIT * number_of_minerals > number_of_workers) + { + if (available_food == FOOD_THRESHOLD_BUILD_DEPOT) + { + UnitType type{ sc2::UNIT_TYPEID::TERRAN_SUPPLYDEPOT, *this }; + build_plan.push_back(type); + + available_food += type.supplyProvided(); // TODO: Maybe 16 sometimes? (+16 with Calldown: Supplies) + } + else + { + build_plan.push_back(UnitType(sc2::UNIT_TYPEID::TERRAN_SCV, *this)); + available_food--; + number_of_workers++; + } + } + + UnitType type{ sc2::UNIT_TYPEID::TERRAN_COMMANDCENTER, *this }; + build_plan.push_back(type); + available_food += type.supplyProvided(); + + const BaseLocation * next_expansion = Bases().getNextExpansion(Players::Self); + if (next_expansion) + { + number_of_minerals += next_expansion->getMinerals().size(); + } + } +} + +void MyAgent::assignScout() +{ + // Assumes that at least one worker is unassigned + for (Unit worker : getWorkers()) + { + if (workerAssignment.count(worker) == 0) + { + assignWork(worker, Assignment::Scout); + std::cout << "Assigned worker to Scout" << std::endl; + break; + } + } +} + +void MyAgent::manageWorkers(std::vector<UnitType> & build_plan) +{ + std::vector<Unit> workers = getWorkers(); + for (Unit worker : workers) + { + // If the worker has does not have an assignment + if (workerAssignment.count(worker) == 0) + { + assignWork(worker, Assignment::Mineral); + } + else + { + CCTilePosition position; + switch (workerAssignment[worker]) { + case Assignment::Mineral: + case Assignment::Gas: + // Never change let the gas/mineral workers rest + break; + case Assignment::Scout: + if (Bases().getPlayerStartingBaseLocation(Players::Enemy)->isExplored()) { + assignWork(worker, Assignment::Mineral); + } + break; + case Assignment::BuildWalking: + position = currently_building[worker].position; + if (position == Util::GetTilePosition(worker.getPosition())) + { + assignWork(worker, Assignment::BuildBuilding); + } + break; + case Assignment::BuildBuilding: + // Probably done, + if (worker.isIdle()) + { + BuildStatus status = currently_building[worker]; + BuildingPlacer().freeTiles(status.position.x, status.position.y, status.type.tileWidth(), status.type.tileHeight()); + currently_building.erase(worker); + assignWork(worker, Assignment::Mineral); + } + break; + } + } + } +} + +void MyAgent::assignWork(Unit & worker, Assignment assignment) +{ + // Assigns worker to assignment + workerAssignment[worker] = assignment; + Unit mineral; + const BaseLocation * enemyBaseLocation; + CCTilePosition position; + AssignmentData data; + const BaseLocation * assigned_base; + + switch (assignment) { + case Assignment::Mineral: + // Select which base to mine for + assigned_base = AssignBase(worker); + mineral = getClosestMineral(assigned_base->getPosition()); + worker.rightClick(mineral); + break; + case Assignment::Scout: + enemyBaseLocation = Bases().getPlayerStartingBaseLocation(Players::Enemy); + + if (enemyBaseLocation) + { + std::cout << "Enemy base location known!" << std::endl; + worker.move(enemyBaseLocation->getPosition()); + } + else + { + std::cout << "Enemy base location unknown!" << std::endl; + + for (const BaseLocation * location : Bases().getStartingBaseLocations()) + { + if (!Map().isExplored(location->getPosition())) + { + worker.move(location->getPosition()); + break; + } + } + } + break; + case Assignment::BuildWalking: + worker.move(currently_building[worker].position); + break; + case Assignment::BuildBuilding: + worker.build(currently_building[worker].type, currently_building[worker].position); + break; + } + +} + +std::map<UnitType, int> MyAgent::numberOfTypeBeingBuilt() const +{ + std::map<UnitType, int> being_built; + for (auto & pair : currently_building) + { + being_built[pair.second.type]++; + } + return being_built; +} + +const BaseLocation * MyAgent::AssignBase(Unit & worker) +{ + std::set<const BaseLocation *> base_locations = Bases().getOccupiedBaseLocations(Players::Self); + int most_needed_workers_value{std::numeric_limits<int>().min()}; + const BaseLocation * most_needed_workers_pointer{}; + + for (const BaseLocation * base_location : base_locations) + { + int workers_needed = 3 * base_location->getMinerals().size(); + int number_of_workers; + if (base_assignment.count(base_location) > 0) + { + std::vector<Unit> & workers = base_assignment[base_location]; + number_of_workers = workers.size(); + } + else + { + number_of_workers = 0; + std::vector<Unit> workers; + base_assignment[base_location] = workers; + } + int needed_workers = workers_needed - number_of_workers; + if (needed_workers > most_needed_workers_value) + { + most_needed_workers_value = needed_workers; + most_needed_workers_pointer = base_location; + } + } + std::vector<Unit> & workers = base_assignment[most_needed_workers_pointer]; + workers.push_back(worker); + return most_needed_workers_pointer; +} + +CCTilePosition MyAgent::getBuildPosition(UnitType & building) +{ + CCTilePosition tile_position; + if (Util::GetTownHall(GetPlayerRace((int) Players::Self), *this) == building) + { + tile_position = Bases().getNextExpansion((int)Players::Self)->getDepotPosition(); + } + else + { + CCPosition position = Bases().getPlayerStartingBaseLocation(Players::Self)->getPosition(); + tile_position = BuildingPlacer().getBuildLocationNear(Util::GetTilePosition(position), building, 1); + } + return tile_position; +} + +Unit MyAgent::getClosestMineral(const CCPosition & position) const +{ + Unit bestMineral; + double bestDist = 100000; + + for (auto & mineral : GetAllUnits()) + { + if (!mineral.getType().isMineral()) continue; + + double dist = Util::Dist(mineral, position); + + if (dist < bestDist) + { + bestMineral = mineral; + bestDist = dist; + } + } + + return bestMineral; +} + +std::vector<Unit> MyAgent::getWorkers() +{ + std::vector<Unit> workers; + + for (auto & unit : GetMyUnits()) + { + if (unit.getType().isWorker()) + { + workers.push_back(unit); + } + } + + return workers; +} + +std::vector<Unit> MyAgent::getProducer(const MetaType & type, bool includeBusy, bool includeIncomplete) +{ + // get all the types of units that cna build this type + auto & producerTypes = Data(type).whatBuilds; + + // make a set of all candidate producers + std::vector<Unit> candidateProducers; + for (auto unit : UnitInfo().getUnits(Players::Self)) + { + // reasons a unit can not train the desired type + if (std::find(producerTypes.begin(), producerTypes.end(), unit.getType()) == producerTypes.end()) { continue; } + if (!includeIncomplete && !unit.isCompleted()) { continue; } + if (!includeBusy && Data(unit).isBuilding && unit.isTraining()) { continue; } + if (unit.isFlying()) { continue; } + + // TODO: if unit is not powered continue + // TODO: if the type is an addon, some special cases + // TODO: if the type requires an addon and the producer doesn't have one + + // if we haven't cut it, add it to the set of candidates + candidateProducers.push_back(unit); + } + + return candidateProducers; +} + diff --git a/src/MyAgent.h b/src/MyAgent.h new file mode 100644 index 0000000..83e527e --- /dev/null +++ b/src/MyAgent.h @@ -0,0 +1,77 @@ +#pragma once + +#include "IDABot.h" + +class MyAgent : public IDABot +{ + // Worker management + enum Assignment { + Scout, + Mineral, + Gas, + BuildWalking, + BuildBuilding + }; + + struct AssignmentData { + // When assigned to building, this corresponds to the location for the building + CCTilePosition buildGoal; + }; + + // Building management + + // Worker assignment book-keeping + std::map<Unit, Assignment> workerAssignment; + + // When workers are assigned to either minerals or gas, they get assigned a base + std::map<const BaseLocation *, std::vector<Unit>> base_assignment; + + std::vector<UnitType> build_order; // TODO: Not used + + std::vector<UnitType> CreateBuildPlan(); + void CreateMaximizeMilitaryBuildPlan(std::vector<UnitType> &build_plan, size_t &available_food); + void CreateMaximizeEconomyBuildPlan(size_t &number_of_minerals, size_t &number_of_workers, size_t &available_food, std::vector<UnitType> &build_plan); + void assignScout(); + void manageWorkers(std::vector<UnitType> & build_plan); + void manageBuilding(std::vector<UnitType> & build_plan); + bool isFree(Unit & worker); + void assignWork(Unit & worker, Assignment assignment); + std::map<UnitType, int> numberOfTypeBeingBuilt() const; + const BaseLocation * AssignBase(Unit & worker); + CCTilePosition getBuildPosition(UnitType & building); + Unit getClosestMineral(const CCPosition & unit) const; + + struct BuildStatus + { + UnitType type; + CCTilePosition position; + int idle; + }; + std::map<Unit, BuildStatus> currently_building; + + // Military management + std::map<UnitType, int> military_goal; + + // Economy + struct Resources + { + int minerals; + int gas; + }; + + Resources military_spending{}; + Resources economy_spending{}; + + // Getters + std::vector<Unit> getWorkers(); + + // Maybe + std::vector<Unit> getProducer(const MetaType & type, bool includeBusy = false, bool include_incomplete = false); + +public: + + MyAgent(); + + void OnGameStart() override; + void OnStep() override; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d9ee0a3..ce02c35 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include "Common.h" #include "IDABot.h" +#include "MyAgent.h" #include "Util.h" #include "sc2utils/sc2_manage_process.h" @@ -22,7 +23,8 @@ int main(int argc, char* argv[]) sc2::Difficulty enemyDifficulty = sc2::Difficulty::Easy; // Add the custom bot, it will control the players. - IDABot bot1; + MyAgent bot1; + //IDABot bot1; //IDABot bot2; // WARNING: Bot logic has not been thorougly tested on step sizes > 1 -- GitLab