├── libvoxelbot ├── utilities │ ├── python_utils.h │ ├── python_utils.cpp │ ├── pathfinding.h │ ├── cereal_json.h │ ├── profiler.cpp │ ├── profiler.h │ ├── unit_data_caching.h │ ├── renderer.h │ ├── predicates.h │ ├── stdutils.h │ ├── bump_allocator.h │ ├── influence.h │ ├── build_state_serialization.h │ ├── ladder_interface.h │ ├── pathfinding.cpp │ ├── mappings.h │ ├── predicates.cpp │ ├── sc2_serialization.h │ ├── unit_data_caching.cpp │ ├── renderer.cpp │ └── influence.cpp ├── generated │ ├── abilities.bin │ ├── upgrades.bin │ └── abilities.h ├── caching │ ├── dependency_analyzer.h │ ├── dependency_analyzer.cpp │ └── caching.cpp ├── buildorder │ ├── build_time_estimator.h │ ├── tracker.h │ ├── optimizer.test.cpp │ ├── build_time_estimator.cpp │ ├── optimizer.h │ ├── build_order.h │ ├── tracker.cpp │ ├── build_order.cpp │ └── build_state.h ├── combat │ ├── combat_upgrades.cpp │ ├── combat_upgrades.h │ ├── combat_environment.h │ ├── simulator.h │ ├── simulator.test.cpp │ └── combat_environment.cpp └── common │ └── unit_lists.h ├── docs └── images │ └── combat.png ├── .gitmodules ├── examples ├── combat_simulator.cpp ├── build_optimizer.cpp └── combat_simulator2.cpp ├── bin2sh.cmake.txt ├── CMakeLists.txt └── readme.md /libvoxelbot/utilities/python_utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern std::mutex python_thread_mutex; -------------------------------------------------------------------------------- /docs/images/combat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalfVoxel/sc2-libvoxelbot/HEAD/docs/images/combat.png -------------------------------------------------------------------------------- /libvoxelbot/generated/abilities.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalfVoxel/sc2-libvoxelbot/HEAD/libvoxelbot/generated/abilities.bin -------------------------------------------------------------------------------- /libvoxelbot/generated/upgrades.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalfVoxel/sc2-libvoxelbot/HEAD/libvoxelbot/generated/upgrades.bin -------------------------------------------------------------------------------- /libvoxelbot/utilities/python_utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | mutex python_thread_mutex; -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cereal"] 2 | path = cereal 3 | url = git@github.com:USCiLab/cereal.git 4 | [submodule "s2client-api"] 5 | path = s2client-api 6 | url = git@github.com:Blizzard/s2client-api.git 7 | -------------------------------------------------------------------------------- /libvoxelbot/caching/dependency_analyzer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sc2api/sc2_api.h" 3 | #include 4 | 5 | struct DependencyAnalyzer { 6 | std::vector> allUnitDependencies; 7 | void analyze(); 8 | }; 9 | -------------------------------------------------------------------------------- /libvoxelbot/generated/abilities.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | extern std::vector> unit_type_has_ability; 4 | extern std::vector> unit_type_initial_health; 5 | extern std::vector unit_type_is_flying; 6 | extern std::vector unit_type_radius; 7 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/pathfinding.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "sc2api/sc2_api.h" 4 | #include 5 | 6 | std::vector getPath (const sc2::Point2DI from, const sc2::Point2DI to, const InfluenceMap& costs); 7 | InfluenceMap getDistances (const InfluenceMap& startingPoints, const InfluenceMap& costs); -------------------------------------------------------------------------------- /libvoxelbot/utilities/cereal_json.h: -------------------------------------------------------------------------------- 1 | // Identical to including some cereal files, but without the annoying warnings 2 | 3 | #pragma once 4 | #pragma GCC diagnostic ignored "-Wunused-private-field" 5 | // Fix annoying noexcept warnings 6 | #include 7 | #define CEREAL_RAPIDJSON_ASSERT(x) assert(x) 8 | #include 9 | #include 10 | #pragma GCC diagnostic warning "-Wunused-private-field" -------------------------------------------------------------------------------- /libvoxelbot/utilities/profiler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using Clock = std::chrono::high_resolution_clock; 3 | 4 | void Stopwatch::start() { 5 | startTime = Clock::now(); 6 | } 7 | 8 | void Stopwatch::stop() { 9 | endTime = Clock::now(); 10 | } 11 | 12 | double Stopwatch::millis() { 13 | std::chrono::duration duration = endTime - startTime; 14 | return duration.count(); 15 | } 16 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/profiler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct Stopwatch { 5 | private: 6 | std::chrono::time_point startTime; 7 | std::chrono::time_point endTime; 8 | public: 9 | Stopwatch() : startTime(), endTime() { 10 | start(); 11 | } 12 | Stopwatch(bool started) : startTime(), endTime() { 13 | if (started) start(); 14 | } 15 | double millis(); 16 | void stop(); 17 | void start(); 18 | }; 19 | -------------------------------------------------------------------------------- /libvoxelbot/buildorder/build_time_estimator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #if LIBVOXELBOT_ENABLE_PYTHON 5 | #include 6 | #endif 7 | 8 | struct BuildResources; 9 | 10 | struct BuildOptimizerNN { 11 | #if LIBVOXELBOT_ENABLE_PYTHON 12 | pybind11::object predictFunction; 13 | #endif 14 | 15 | void init(); 16 | 17 | std::vector> predictTimeToBuild(const std::vector>& startingState, const BuildResources& startingResources, const std::vector < std::vector>>& targets) const; 18 | }; 19 | -------------------------------------------------------------------------------- /examples/combat_simulator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | using namespace sc2; 6 | 7 | int main() { 8 | initMappings(); 9 | CombatPredictor simulator; 10 | simulator.init(); 11 | CombatState state = {{ 12 | makeUnit(1, UNIT_TYPEID::TERRAN_MARINE), 13 | makeUnit(1, UNIT_TYPEID::TERRAN_MEDIVAC), 14 | makeUnit(1, UNIT_TYPEID::TERRAN_MARINE), 15 | 16 | makeUnit(2, UNIT_TYPEID::ZERG_ROACH), 17 | makeUnit(2, UNIT_TYPEID::ZERG_ROACH), 18 | makeUnit(2, UNIT_TYPEID::ZERG_ZERGLING), 19 | }}; 20 | CombatResult outcome = simulator.predict_engage(state); 21 | cout << outcome.state.toString() << endl; 22 | cout << "Winner player is " << outcome.state.owner_with_best_outcome() << endl; 23 | 24 | return 0; 25 | } -------------------------------------------------------------------------------- /libvoxelbot/utilities/unit_data_caching.h: -------------------------------------------------------------------------------- 1 | #include "sc2api/sc2_api.h" 2 | #include 3 | #include 4 | #include 5 | 6 | const std::string UNIT_DATA_CACHE_PATH = "sc2-libvoxelbot/libvoxelbot/generated/units.data"; 7 | const std::string UPGRADE_DATA_CACHE_PATH = "sc2-libvoxelbot/libvoxelbot/generated/upgrades.bin"; 8 | const std::string ABILITY_DATA_CACHE_PATH = "sc2-libvoxelbot/libvoxelbot/generated/abilities.bin"; 9 | 10 | void save_unit_data(const std::vector& unit_types, std::string path=UNIT_DATA_CACHE_PATH); 11 | std::vector load_unit_data(); 12 | void save_ability_data(std::vector abilities); 13 | void save_upgrade_data(std::vector upgrades); 14 | std::vector load_ability_data(); 15 | std::vector load_upgrade_data(); -------------------------------------------------------------------------------- /libvoxelbot/buildorder/tracker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | struct BuildOrderTracker { 6 | private: 7 | std::map ignoreUnits; 8 | std::vector buildOrderUnits; 9 | int tick; 10 | public: 11 | std::set knownUnits; 12 | BuildOrder buildOrder; 13 | 14 | BuildOrderTracker () {} 15 | BuildOrderTracker (BuildOrder buildOrder); 16 | 17 | void setBuildOrder (BuildOrder buildOrder); 18 | void tweakBuildOrder (std::vector keepMask, BuildOrder buildOrder); 19 | void addExistingUnit(const sc2::Unit* unit); 20 | 21 | void ignoreUnit (sc2::UNIT_TYPEID type, int count); 22 | std::vector update(const sc2::ObservationInterface* observation, const std::vector& ourUnits); 23 | }; 24 | -------------------------------------------------------------------------------- /libvoxelbot/combat/combat_upgrades.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | CombatUpgrades::CombatUpgrades() { 5 | // It's a bit tricky to set the template argument to the upgrades array based on the availableUpgrades list 6 | // so instead we just validate it at runtime here. 7 | // If new upgrades are introduced the upgrades bitset needs to be expanded. 8 | assert(upgrades.size() >= availableUpgrades.size()); 9 | } 10 | 11 | sc2::UPGRADE_ID CombatUpgrades::iterator::operator*() const { return availableUpgrades.getBuildOrderItem(index).upgradeID(); } 12 | 13 | bool CombatUpgrades::hasUpgrade(sc2::UPGRADE_ID upgrade) const { 14 | return upgrades[availableUpgrades.getIndex(upgrade)]; 15 | } 16 | 17 | void CombatUpgrades::add(sc2::UPGRADE_ID upgrade) { 18 | upgrades[availableUpgrades.getIndex(upgrade)] = true; 19 | } -------------------------------------------------------------------------------- /libvoxelbot/utilities/renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "SDL.h" 3 | 4 | struct InfluenceMap; 5 | 6 | struct MapRenderer { 7 | private: 8 | SDL_Window* window = nullptr; 9 | SDL_Renderer* renderer = nullptr; 10 | public: 11 | 12 | MapRenderer() {} 13 | MapRenderer(const char* title, int x, int y, int w, int h, unsigned int flags = 0); 14 | void shutdown(); 15 | void renderInfluenceMap(const InfluenceMap& influenceMap, int x0, int y0); 16 | void renderInfluenceMapNormalized(const InfluenceMap& influenceMap, int x0, int y0); 17 | void renderMatrix1BPP(const char* bytes, int w_mat, int h_mat, int off_x, int off_y, int px_w, int px_h); 18 | void renderMatrix8BPPHeightMap(const char* bytes, int w_mat, int h_mat, int off_x, int off_y, int px_w, int px_h); 19 | void renderMatrix8BPPPlayers(const char* bytes, int w_mat, int h_mat, int off_x, int off_y, int px_w, int px_h); 20 | void renderImageRGB(const char* bytes, int width, int height, int off_x, int off_y, int scale); 21 | void renderImageGrayscale(const double* values, int width, int height, int off_x, int off_y, int scale, bool normalized); 22 | void present(); 23 | }; 24 | -------------------------------------------------------------------------------- /libvoxelbot/buildorder/optimizer.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | using namespace sc2; 7 | 8 | int main () { 9 | initMappings(); 10 | BuildState state {{ 11 | { UNIT_TYPEID::PROTOSS_NEXUS, 1 }, 12 | { UNIT_TYPEID::PROTOSS_PROBE, 12 }, 13 | }}; 14 | state.resources.minerals = 50; 15 | state.resources.vespene = 0; 16 | 17 | auto buildOrder = findBestBuildOrderGenetic(state, { 18 | { BuildOrderItem(UNIT_TYPEID::PROTOSS_ZEALOT), 1 } 19 | }); 20 | 21 | int numZealots = 0; 22 | int numGateways = 0; 23 | int numPylons = 0; 24 | int numOthers = 0; 25 | 26 | for (const auto item : buildOrder) { 27 | if (item.typeID() == UNIT_TYPEID::PROTOSS_ZEALOT) numZealots++; 28 | if (item.typeID() == UNIT_TYPEID::PROTOSS_GATEWAY) numGateways++; 29 | if (item.typeID() == UNIT_TYPEID::PROTOSS_PYLON) numPylons++; 30 | if (item.typeID() != UNIT_TYPEID::PROTOSS_PROBE) numOthers++; 31 | } 32 | assert(numZealots == 1); 33 | assert(numGateways == 1); 34 | assert(numPylons == 1); 35 | assert(numOthers == 0); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /examples/build_optimizer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | using namespace sc2; 7 | 8 | int main () { 9 | initMappings(); 10 | 11 | // Start with 1 nexus and 12 probes 12 | BuildState state {{ 13 | { UNIT_TYPEID::PROTOSS_NEXUS, 1 }, 14 | { UNIT_TYPEID::PROTOSS_PROBE, 12 }, 15 | }}; 16 | // Start with 50 minerals 17 | state.resources.minerals = 50; 18 | state.resources.vespene = 0; 19 | // Our Nexus has 50 energy at the start of the game 20 | state.chronoInfo.addNexusWithEnergy(state.time, 50); 21 | 22 | // Find a good build order for producing some units 23 | BuildOrder buildOrder = findBestBuildOrderGenetic(state, { 24 | { BuildOrderItem(UNIT_TYPEID::PROTOSS_ZEALOT), 4 }, 25 | { BuildOrderItem(UNIT_TYPEID::PROTOSS_STALKER), 4 }, 26 | { BuildOrderItem(UNIT_TYPEID::PROTOSS_IMMORTAL), 2 }, 27 | { BuildOrderItem(UPGRADE_ID::PROTOSSGROUNDARMORSLEVEL1), 1 }, 28 | }); 29 | 30 | // Print the build order 31 | cout << buildOrder.toString() << endl; 32 | 33 | // Print a more detailed build order output which includes timing information 34 | cout << buildOrder.toString(state, BuildOrderPrintMode::Detailed); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/predicates.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sc2api/sc2_interfaces.h" 3 | 4 | struct IsAttackable { 5 | bool operator()(const sc2::Unit& unit); 6 | }; 7 | struct IsFlying { 8 | bool operator()(const sc2::Unit& unit); 9 | }; 10 | struct IsArmy { 11 | const sc2::ObservationInterface* observation_; 12 | // Ignores Overlords, workers, and structures 13 | IsArmy(const sc2::ObservationInterface* obs) : observation_(obs) {} 14 | bool operator()(const sc2::Unit& unit); 15 | }; 16 | struct IsTownHall { 17 | bool operator()(const sc2::Unit& unit); 18 | }; 19 | struct IsVespeneGeyser { 20 | bool operator()(const sc2::Unit& unit); 21 | }; 22 | struct IsStructure { 23 | const sc2::ObservationInterface* observation_; 24 | IsStructure(const sc2::ObservationInterface* obs) : observation_(obs){}; 25 | bool operator()(const sc2::Unit& unit); 26 | }; 27 | 28 | bool isArmy(sc2::UNIT_TYPEID type); 29 | bool isStructure(const sc2::UnitTypeData& unitType); 30 | 31 | bool carriesResources(const sc2::Unit* unit); 32 | 33 | bool isMelee(sc2::UNIT_TYPEID type); 34 | bool isInfantry(sc2::UNIT_TYPEID type); 35 | bool isChangeling(sc2::UNIT_TYPEID type); 36 | bool hasBuff (const sc2::Unit* unit, sc2::BUFF_ID buff); 37 | bool isUpgradeWithLevels(sc2::UPGRADE_ID upgrade); 38 | bool isTownHall(sc2::UNIT_TYPEID type); 39 | bool isUpgradeDoneOrInProgress(const sc2::ObservationInterface* observation, sc2::UPGRADE_ID upgrade); -------------------------------------------------------------------------------- /libvoxelbot/buildorder/build_time_estimator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #if LIBVOXELBOT_ENABLE_PYTHON 4 | #include 5 | #include 6 | #endif 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | using namespace sc2; 13 | 14 | void BuildOptimizerNN::init() { 15 | #if LIBVOXELBOT_ENABLE_PYTHON 16 | lock_guard lock(python_thread_mutex); 17 | pybind11::exec(R"( 18 | import sys 19 | sys.path.append("bot/optimizer_train") 20 | )"); 21 | pybind11::module trainer = pybind11::module::import("optimizer_train"); 22 | predictFunction = trainer.attr("predict"); 23 | cout << "Finished initializing NN" << endl; 24 | #endif 25 | } 26 | 27 | vector> BuildOptimizerNN::predictTimeToBuild(const vector>& startingState, const BuildResources& startingResources, const vector < vector>>& targets) const { 28 | #if LIBVOXELBOT_ENABLE_PYTHON 29 | lock_guard lock(python_thread_mutex); 30 | auto res = predictFunction(startingState, tuple(startingResources.minerals, startingResources.vespene), targets); 31 | return res.cast>>(); 32 | #else 33 | return vector>(targets.size(), vector(3)); 34 | #endif 35 | } 36 | -------------------------------------------------------------------------------- /libvoxelbot/combat/combat_upgrades.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "sc2api/sc2_interfaces.h" 4 | 5 | struct CombatUpgrades { 6 | std::bitset<90> upgrades; 7 | 8 | struct iterator { 9 | const CombatUpgrades& parent; 10 | size_t index = 0; 11 | iterator(const CombatUpgrades& parent, size_t index): parent(parent), index(index) { 12 | while(this->index < parent.upgrades.size() && !parent.upgrades[this->index]) this->index++; 13 | } 14 | iterator operator++() { 15 | index++; 16 | while(index < parent.upgrades.size() && !parent.upgrades[index]) index++; 17 | return *this; 18 | } 19 | bool operator!=(const iterator & other) const { 20 | return index != other.index; 21 | } 22 | sc2::UPGRADE_ID operator*() const; 23 | }; 24 | 25 | CombatUpgrades(); 26 | 27 | CombatUpgrades(std::initializer_list upgrades) { 28 | for (auto u : upgrades) add(u); 29 | } 30 | 31 | bool hasUpgrade(sc2::UPGRADE_ID upgrade) const; 32 | 33 | uint64_t hash() const { 34 | return std::hash>()(upgrades); 35 | } 36 | 37 | void add(sc2::UPGRADE_ID upgrade); 38 | 39 | void combine(const CombatUpgrades& other) { 40 | upgrades |= other.upgrades; 41 | } 42 | 43 | void remove(const CombatUpgrades& other) { 44 | upgrades &= ~other.upgrades; 45 | } 46 | 47 | iterator begin() const { return iterator(*this, 0 ); } 48 | iterator end() const { return iterator(*this, upgrades.size() ); } 49 | }; 50 | -------------------------------------------------------------------------------- /examples/combat_simulator2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | using namespace sc2; 6 | 7 | int main() { 8 | initMappings(); 9 | CombatPredictor simulator; 10 | simulator.init(); 11 | 12 | CombatState state = {{ 13 | makeUnit(1, UNIT_TYPEID::TERRAN_MARINE), 14 | makeUnit(1, UNIT_TYPEID::TERRAN_MEDIVAC), 15 | makeUnit(1, UNIT_TYPEID::TERRAN_MARINE), 16 | 17 | makeUnit(2, UNIT_TYPEID::ZERG_ROACH), 18 | makeUnit(2, UNIT_TYPEID::ZERG_ROACH), 19 | makeUnit(2, UNIT_TYPEID::ZERG_ZERGLING), 20 | }}; 21 | 22 | CombatUpgrades player1upgrades = { 23 | UPGRADE_ID::TERRANINFANTRYWEAPONSLEVEL1, 24 | UPGRADE_ID::TERRANINFANTRYWEAPONSLEVEL2, 25 | UPGRADE_ID::TERRANINFANTRYWEAPONSLEVEL3, 26 | UPGRADE_ID::TERRANINFANTRYARMORSLEVEL1, 27 | UPGRADE_ID::TERRANINFANTRYARMORSLEVEL2, 28 | }; 29 | 30 | CombatUpgrades player2upgrades = { 31 | UPGRADE_ID::ZERGMISSILEWEAPONSLEVEL1, 32 | }; 33 | 34 | state.environment = &simulator.getCombatEnvironment(player1upgrades, player2upgrades); 35 | 36 | CombatSettings settings; 37 | // Simulate for at most 100 *game* seconds 38 | // Just to show that it can be configured, in this case 100 game seconds is more than enough for the battle to finish. 39 | settings.maxTime = 100; 40 | CombatResult outcome = simulator.predict_engage(state, settings); 41 | cout << outcome.state.toString() << endl; 42 | for (auto& unit : outcome.state.units) { 43 | cout << getUnitData(unit.type).name << " ended up with " << unit.health << " hp" << endl; 44 | } 45 | cout << "Winner player is " << outcome.state.owner_with_best_outcome() << endl; 46 | cout << "Battle concluded after " << outcome.time << " seconds" << endl; 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /libvoxelbot/combat/combat_environment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sc2api/sc2_interfaces.h" 3 | #include 4 | #include 5 | #include 6 | 7 | struct CombatUnit; 8 | 9 | struct WeaponInfo { 10 | private: 11 | mutable std::vector dpsCache; 12 | float baseDPS; 13 | 14 | public: 15 | bool available; 16 | float splash; 17 | const sc2::Weapon* weapon; 18 | 19 | float getDPS() const { 20 | return baseDPS; 21 | } 22 | 23 | float getDPS(sc2::UNIT_TYPEID target, float modifier = 0) const; 24 | 25 | float range() const; 26 | 27 | WeaponInfo() 28 | : baseDPS(0), available(false), splash(0), weapon(nullptr) { 29 | } 30 | 31 | WeaponInfo(const sc2::Weapon* weapon, sc2::UNIT_TYPEID type, const CombatUpgrades& upgrades, const CombatUpgrades& targetUpgrades); 32 | }; 33 | 34 | struct UnitCombatInfo { 35 | WeaponInfo groundWeapon; 36 | WeaponInfo airWeapon; 37 | 38 | // In case the unit has multiple weapons this is the fastest of the two weapons 39 | float attackInterval() const; 40 | 41 | UnitCombatInfo(sc2::UNIT_TYPEID type, const CombatUpgrades& upgrades, const CombatUpgrades& targetUpgrades); 42 | }; 43 | 44 | struct CombatEnvironment { 45 | std::array, 2> combatInfo; 46 | std::array upgrades; 47 | 48 | CombatEnvironment(const CombatUpgrades& upgrades, const CombatUpgrades& targetUpgrades); 49 | 50 | float attackRange(int owner, sc2::UNIT_TYPEID type) const; 51 | float attackRange(const CombatUnit& unit) const; 52 | const UnitCombatInfo& getCombatInfo(const CombatUnit& unit) const; 53 | float calculateDPS(int owner, sc2::UNIT_TYPEID type, bool air) const; 54 | float calculateDPS(const std::vector& units, bool air) const; 55 | float calculateDPS(const CombatUnit& unit, bool air) const; 56 | float calculateDPS(const CombatUnit& unit1, const CombatUnit& unit2) const; 57 | }; 58 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/stdutils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // ------------------------------------------------------------------- 8 | // --- Reversed iterable 9 | 10 | template 11 | struct reversion_wrapper { T& iterable; }; 12 | 13 | template 14 | auto begin (reversion_wrapper w) { return std::rbegin(w.iterable); } 15 | 16 | template 17 | auto end (reversion_wrapper w) { return std::rend(w.iterable); } 18 | 19 | template 20 | reversion_wrapper reverse (T&& iterable) { return { iterable }; } 21 | 22 | template 23 | bool contains(const std::vector& arr, const T& item) { 24 | return find(arr.begin(), arr.end(), item) != arr.end(); 25 | } 26 | 27 | template 28 | int indexOf(const std::vector& arr, const T& item) { 29 | auto c = find(arr.begin(), arr.end(), item); 30 | assert(c != arr.end()); 31 | return (int)(c - arr.begin()); 32 | } 33 | 34 | template 35 | int indexOfMaybe(const std::vector& arr, const T& item) { 36 | auto c = find(arr.begin(), arr.end(), item); 37 | if (c == arr.end()) return -1; 38 | return (int)(c - arr.begin()); 39 | } 40 | 41 | template 42 | void sortByValueAscending (std::vector& arr, std::function value) { 43 | std::sort(arr.begin(), arr.end(), [&](const T& a, const T& b) -> bool { 44 | return value(a) < value(b); 45 | }); 46 | } 47 | 48 | template 49 | void sortByValueDescending (std::vector& arr, std::function value) { 50 | std::sort(arr.begin(), arr.end(), [&](const T& a, const T& b) -> bool { 51 | return value(b) < value(a); 52 | }); 53 | } 54 | 55 | 56 | template 57 | void sortByValueDescendingBubble (std::vector& arr, std::function value) { 58 | bool changed = true; 59 | while(changed) { 60 | changed = false; 61 | for (size_t i = 0; i + 1 < arr.size(); i++) { 62 | if (value(arr[i]) < value(arr[i+1])) { 63 | std::swap(arr[i], arr[i+1]); 64 | changed = true; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/bump_allocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | // A simple bump allocator 5 | // Much faster than new or shared pointers 6 | 7 | template 8 | struct BumpAllocator { 9 | private: 10 | // const size_t BlockSize = 128; 11 | std::vector blocks; 12 | std::vector freeBlocks; 13 | size_t currentBlockCount = 0; 14 | T* currentBlock = nullptr; 15 | 16 | void grow() { 17 | if (currentBlock != nullptr) blocks.push_back(currentBlock); 18 | 19 | currentBlockCount = 0; 20 | if (!freeBlocks.empty()) { 21 | currentBlock = *freeBlocks.rbegin(); 22 | freeBlocks.pop_back(); 23 | } else { 24 | currentBlock = (T*)malloc(sizeof(T) * BlockSize); 25 | assert(currentBlock != nullptr); 26 | } 27 | } 28 | 29 | public: 30 | BumpAllocator() { 31 | grow(); 32 | } 33 | 34 | // Do not allow delete 35 | BumpAllocator(const BumpAllocator& x) = delete; 36 | 37 | // Allow move 38 | BumpAllocator(BumpAllocator&& x) = default; 39 | 40 | ~BumpAllocator() { 41 | for (auto block : blocks) { 42 | for (size_t i = 0; i < BlockSize; i++) (block + i)->~T(); 43 | free(block); 44 | } 45 | for (auto block : freeBlocks) { 46 | free(block); 47 | } 48 | for (size_t i = 0; i < currentBlockCount; i++) (currentBlock + i)->~T(); 49 | free(currentBlock); 50 | currentBlock = nullptr; 51 | } 52 | 53 | template 54 | T* allocate(Args&&... args) { 55 | if (currentBlockCount >= BlockSize) grow(); 56 | T* ret = currentBlock + currentBlockCount; 57 | // *ret = T(std::forward(args)...); 58 | // Placement new. Constructs the new object at the ret pointer's location 59 | new(ret) T(std::forward(args)...); 60 | currentBlockCount++; 61 | return ret; 62 | } 63 | 64 | void clear() { 65 | for (auto block : blocks) { 66 | for (size_t i = 0; i < BlockSize; i++) (block + i)->~T(); 67 | } 68 | 69 | for (auto block : blocks) freeBlocks.push_back(block); 70 | blocks.clear(); 71 | 72 | for (size_t i = 0; i < currentBlockCount; i++) (currentBlock + i)->~T(); 73 | currentBlockCount = 0; 74 | } 75 | 76 | size_t size() { 77 | return blocks.size() * BlockSize + currentBlockCount; 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /libvoxelbot/buildorder/optimizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "sc2api/sc2_interfaces.h" 6 | #include 7 | #include 8 | #include 9 | 10 | struct GeneUnitType { 11 | int type = -1; 12 | bool chronoBoosted = false; 13 | 14 | GeneUnitType() {} 15 | GeneUnitType(int type) : type(type), chronoBoosted(false) {} 16 | GeneUnitType(int type, bool chronoBoosted) : type(type), chronoBoosted(chronoBoosted) {} 17 | 18 | bool operator==(const GeneUnitType& other) const { 19 | return type == other.type && chronoBoosted == other.chronoBoosted; 20 | } 21 | 22 | bool operator!=(const GeneUnitType& other) const { 23 | return type != other.type || chronoBoosted != other.chronoBoosted; 24 | } 25 | }; 26 | 27 | struct BuildOrderFitness { 28 | static const BuildOrderFitness ReallyBad; 29 | 30 | float time; 31 | BuildResources resources; 32 | MiningSpeed miningSpeed; 33 | MiningSpeed miningSpeedPerSecond; 34 | 35 | BuildOrderFitness () : time(0), resources(0,0), miningSpeed({0,0}), miningSpeedPerSecond({0, 0}) {} 36 | BuildOrderFitness (float time, BuildResources resources, MiningSpeed miningSpeed, MiningSpeed miningSpeedPerSecond) : time(time), resources(resources), miningSpeed(miningSpeed), miningSpeedPerSecond(miningSpeedPerSecond) {} 37 | 38 | float score() const; 39 | 40 | bool operator<(const BuildOrderFitness& other) const; 41 | }; 42 | 43 | struct BuildOptimizerParams { 44 | int genePoolSize = 25; 45 | int iterations = 512; 46 | float mutationRateAddRemove = 0.05f; 47 | float mutationRateMove = 0.025f; 48 | float varianceBias = 0; 49 | bool allowChronoBoost = true; 50 | }; 51 | 52 | std::pair> expandBuildOrderWithImplicitSteps (const BuildState& startState, BuildOrder buildOrder); 53 | 54 | BuildOrder findBestBuildOrderGenetic(const std::vector>& startingUnits, const std::vector>& target); 55 | BuildOrder findBestBuildOrderGenetic(const BuildState& startState, const std::vector>& target, const BuildOrder* seed = nullptr, BuildOptimizerParams params = BuildOptimizerParams()); 56 | std::pair findBestBuildOrderGeneticWithFitness(const BuildState& startState, const std::vector>& target, const BuildOrder* seed = nullptr, BuildOptimizerParams params = BuildOptimizerParams()); 57 | BuildOrder findBestBuildOrderGenetic(const BuildState& startState, const std::vector>& target, const BuildOrder* seed = nullptr, BuildOptimizerParams params = BuildOptimizerParams()); 58 | void unitTestBuildOptimizer(); 59 | void printBuildOrderDetailed(const BuildState& startState, const BuildOrder& buildOrder, const std::vector* highlight = nullptr); 60 | void optimizeExistingBuildOrder(const sc2::ObservationInterface* observation, const std::vector& ourUnits, const BuildState& buildOrderStartingState, BuildOrderTracker& buildOrder, bool serialize); 61 | BuildOrderFitness calculateFitness(const BuildState& startState, const BuildOrder& buildOrder); -------------------------------------------------------------------------------- /bin2sh.cmake.txt: -------------------------------------------------------------------------------- 1 | 2 | include(CMakeParseArguments) 3 | 4 | # Function to wrap a given string into multiple lines at the given column position. 5 | # Parameters: 6 | # VARIABLE - The name of the CMake variable holding the string. 7 | # AT_COLUMN - The column position at which string will be wrapped. 8 | function(WRAP_STRING) 9 | set(oneValueArgs VARIABLE AT_COLUMN) 10 | cmake_parse_arguments(WRAP_STRING "${options}" "${oneValueArgs}" "" ${ARGN}) 11 | 12 | string(LENGTH ${${WRAP_STRING_VARIABLE}} stringLength) 13 | math(EXPR offset "0") 14 | 15 | while(stringLength GREATER 0) 16 | 17 | if(stringLength GREATER ${WRAP_STRING_AT_COLUMN}) 18 | math(EXPR length "${WRAP_STRING_AT_COLUMN}") 19 | else() 20 | math(EXPR length "${stringLength}") 21 | endif() 22 | 23 | string(SUBSTRING ${${WRAP_STRING_VARIABLE}} ${offset} ${length} line) 24 | set(lines "${lines}\n${line}") 25 | 26 | math(EXPR stringLength "${stringLength} - ${length}") 27 | math(EXPR offset "${offset} + ${length}") 28 | endwhile() 29 | 30 | set(${WRAP_STRING_VARIABLE} "${lines}" PARENT_SCOPE) 31 | endfunction() 32 | # Function to embed contents of a file as byte array in C/C++ header file(.h). The header file 33 | # will contain a byte array and integer variable holding the size of the array. 34 | # Parameters 35 | # SOURCE_FILE - The path of source file whose contents will be embedded in the header file. 36 | # VARIABLE_NAME - The name of the variable for the byte array. The string "_SIZE" will be append 37 | # to this name and will be used a variable name for size variable. 38 | # HEADER_FILE - The path of header file. 39 | # APPEND - If specified appends to the header file instead of overwriting it 40 | # NULL_TERMINATE - If specified a null byte(zero) will be append to the byte array. This will be 41 | # useful if the source file is a text file and we want to use the file contents 42 | # as string. But the size variable holds size of the byte array without this 43 | # null byte. 44 | # Usage: 45 | # bin2h(SOURCE_FILE "Logo.png" HEADER_FILE "Logo.h" VARIABLE_NAME "LOGO_PNG") 46 | function(BIN2H) 47 | set(options APPEND NULL_TERMINATE) 48 | set(oneValueArgs SOURCE_FILE VARIABLE_NAME HEADER_FILE) 49 | cmake_parse_arguments(BIN2H "${options}" "${oneValueArgs}" "" ${ARGN}) 50 | 51 | # reads source file contents as hex string 52 | file(READ ${BIN2H_SOURCE_FILE} hexString HEX) 53 | string(LENGTH ${hexString} hexStringLength) 54 | 55 | # appends null byte if asked 56 | if(BIN2H_NULL_TERMINATE) 57 | set(hexString "${hexString}00") 58 | endif() 59 | 60 | # wraps the hex string into multiple lines at column 32(i.e. 16 bytes per line) 61 | wrap_string(VARIABLE hexString AT_COLUMN 32) 62 | math(EXPR arraySize "${hexStringLength} / 2") 63 | 64 | # adds '0x' prefix and comma suffix before and after every byte respectively 65 | string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " arrayValues ${hexString}) 66 | # removes trailing comma 67 | string(REGEX REPLACE ", $" "" arrayValues ${arrayValues}) 68 | 69 | # converts the variable name into proper C identifier 70 | string(MAKE_C_IDENTIFIER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME) 71 | string(TOUPPER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME) 72 | 73 | # declares byte array and the length variables 74 | set(arrayDefinition "const unsigned char ${BIN2H_VARIABLE_NAME}[] = { ${arrayValues} };") 75 | set(arraySizeDefinition "const size_t ${BIN2H_VARIABLE_NAME}_SIZE = ${arraySize};") 76 | 77 | set(declarations "${arrayDefinition}\n\n${arraySizeDefinition}\n\n") 78 | if(BIN2H_APPEND) 79 | file(APPEND ${BIN2H_HEADER_FILE} "${declarations}") 80 | else() 81 | file(WRITE ${BIN2H_HEADER_FILE} "${declarations}") 82 | endif() 83 | endfunction() -------------------------------------------------------------------------------- /libvoxelbot/utilities/influence.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sc2api/sc2_api.h" 3 | #include 4 | 5 | struct InfluenceMap { 6 | std::vector weights; 7 | int w, h; 8 | 9 | InfluenceMap() { 10 | } 11 | 12 | InfluenceMap(int width, int height) { 13 | w = width; 14 | h = height; 15 | weights = std::vector(width*height, 0.0); 16 | } 17 | 18 | InfluenceMap(const sc2::ImageData map); 19 | InfluenceMap(const SC2APIProtocol::ImageData map); 20 | 21 | inline double& operator()(int x, int y) { 22 | return weights[y*w + x]; 23 | } 24 | 25 | inline double operator()(int x, int y) const { 26 | return weights[y*w + x]; 27 | } 28 | 29 | inline double& operator()(sc2::Point2DI p) { 30 | return weights[p.y*w + p.x]; 31 | } 32 | 33 | inline double operator()(sc2::Point2DI p) const { 34 | return weights[p.y*w + p.x]; 35 | } 36 | 37 | inline double& operator()(sc2::Point2D p) { 38 | return weights[std::min(h-1, std::max(0, (int)round(p.y)))*w + std::min(w-1, std::max(0, (int)round(p.x)))]; 39 | } 40 | 41 | inline double operator()(sc2::Point2D p) const { 42 | return weights[std::min(h-1, std::max(0, (int)round(p.y)))*w + std::min(w-1, std::max(0, (int)round(p.x)))]; 43 | } 44 | 45 | inline double& operator[](int index) { 46 | return weights[index]; 47 | } 48 | 49 | inline double operator[](int index) const { 50 | return weights[index]; 51 | } 52 | 53 | InfluenceMap& operator+= (const InfluenceMap& other); 54 | 55 | InfluenceMap& operator+= (double other); 56 | 57 | InfluenceMap operator+ (const InfluenceMap& other) const; 58 | 59 | InfluenceMap& operator-= (const InfluenceMap& other); 60 | 61 | InfluenceMap operator- (const InfluenceMap& other) const; 62 | 63 | InfluenceMap& operator*= (const InfluenceMap& other); 64 | 65 | InfluenceMap operator* (const InfluenceMap& other) const; 66 | 67 | InfluenceMap& operator/= (const InfluenceMap& other); 68 | 69 | InfluenceMap operator/ (const InfluenceMap& other) const; 70 | 71 | InfluenceMap operator+ (double factor) const; 72 | 73 | InfluenceMap operator- (double factor) const; 74 | 75 | InfluenceMap operator* (double factor) const; 76 | 77 | void operator*= (double factor); 78 | 79 | void threshold(double value); 80 | 81 | double sum() const; 82 | 83 | double max() const; 84 | void max(const InfluenceMap& other); 85 | double maxFinite() const; 86 | 87 | sc2::Point2DI argmax() const; 88 | 89 | InfluenceMap replace_nonzero(double with) const; 90 | InfluenceMap replace_nan(double with) const; 91 | InfluenceMap replace(double value, double with) const; 92 | 93 | void addInfluence(double influence, sc2::Point2DI pos); 94 | void addInfluence(double influence, sc2::Point2D pos); 95 | void setInfluence(double influence, sc2::Point2D pos); 96 | void addInfluenceInDecayingCircle(double influence, double radius, sc2::Point2D pos); 97 | void setInfluenceInCircle(double influence, double radius, sc2::Point2D pos); 98 | 99 | void addInfluence(const std::vector >& influence, sc2::Point2D); 100 | 101 | void addInfluenceMultiple(const std::vector >& influence, sc2::Point2D, double factor); 102 | 103 | void maxInfluence(const std::vector >& influence, sc2::Point2D); 104 | 105 | void maxInfluenceMultiple(const std::vector >& influence, sc2::Point2D, double factor); 106 | 107 | void propagateMax(double decay, double speed, const InfluenceMap& traversable); 108 | void propagateSum(double decay, double speed, const InfluenceMap& traversable); 109 | sc2::Point2DI samplePointFromProbabilityDistribution() const; 110 | 111 | void print() const; 112 | }; 113 | -------------------------------------------------------------------------------- /libvoxelbot/buildorder/build_order.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "sc2api/sc2_interfaces.h" 4 | 5 | struct BuildState; 6 | 7 | static const int UPGRADE_ID_OFFSET = 1000000; 8 | struct BuildOrderItem { 9 | public: 10 | // union ish. It is assumed that unit type ids are pretty small (which they are) 11 | sc2::UNIT_TYPEID internalType = sc2::UNIT_TYPEID::INVALID; 12 | bool chronoBoosted = false; 13 | 14 | bool isUnitType() const { 15 | return (int)internalType < UPGRADE_ID_OFFSET; 16 | } 17 | 18 | sc2::UNIT_TYPEID rawType() const { 19 | return internalType; 20 | } 21 | 22 | sc2::UNIT_TYPEID typeID() const { 23 | assert((int)internalType < UPGRADE_ID_OFFSET); 24 | return internalType; 25 | } 26 | 27 | sc2::UPGRADE_ID upgradeID() const { 28 | return (sc2::UPGRADE_ID)((int)internalType - UPGRADE_ID_OFFSET); 29 | } 30 | 31 | BuildOrderItem() {} 32 | explicit BuildOrderItem(sc2::UPGRADE_ID upgrade, bool chronoBoosted = false) : internalType((sc2::UNIT_TYPEID)((int)upgrade + UPGRADE_ID_OFFSET)), chronoBoosted(chronoBoosted) {} 33 | explicit BuildOrderItem(sc2::UNIT_TYPEID type) : internalType(type), chronoBoosted(false) {} 34 | explicit BuildOrderItem(sc2::UNIT_TYPEID type, bool chronoBoosted) : internalType(type), chronoBoosted(chronoBoosted) {} 35 | 36 | bool operator==(const BuildOrderItem& other) const { 37 | return internalType == other.internalType && chronoBoosted == other.chronoBoosted; 38 | } 39 | 40 | bool operator!=(const BuildOrderItem& other) const { 41 | return internalType != other.internalType || chronoBoosted != other.chronoBoosted; 42 | } 43 | 44 | std::string name() const { 45 | if (isUnitType()) return sc2::UnitTypeToName(typeID()); 46 | else return sc2::UpgradeIDToName(upgradeID()); 47 | } 48 | }; 49 | 50 | enum class BuildOrderPrintMode { 51 | Brief, 52 | Detailed 53 | }; 54 | 55 | struct BuildOrder { 56 | std::vector items; 57 | 58 | BuildOrder() {} 59 | 60 | BuildOrder(std::initializer_list order) { 61 | for (auto tp : order) { 62 | items.push_back(tp); 63 | } 64 | } 65 | 66 | BuildOrder(std::initializer_list order) { 67 | for (auto tp : order) { 68 | items.push_back(BuildOrderItem(tp)); 69 | } 70 | } 71 | 72 | explicit BuildOrder (const std::vector& order) : items(order.size()) { 73 | for (size_t i = 0; i < order.size(); i++) { 74 | items[i] = BuildOrderItem(order[i]); 75 | } 76 | } 77 | 78 | inline size_t size() const noexcept { 79 | return items.size(); 80 | } 81 | 82 | inline BuildOrderItem& operator[] (int index) { 83 | return items[index]; 84 | } 85 | 86 | inline const BuildOrderItem& operator[] (int index) const { 87 | return items[index]; 88 | } 89 | 90 | std::vector::const_iterator begin() const { return items.begin(); } 91 | std::vector::const_iterator end() const { return items.end(); } 92 | std::vector::iterator begin() { return items.begin(); } 93 | std::vector::iterator end() { return items.end(); } 94 | 95 | std::string toString(); 96 | std::string toString(BuildState initialState, BuildOrderPrintMode mode = BuildOrderPrintMode::Brief); 97 | }; 98 | 99 | 100 | struct BuildOrderState { 101 | std::shared_ptr buildOrder; 102 | int buildIndex = 0; 103 | sc2::UNIT_TYPEID lastChronoUnit = sc2::UNIT_TYPEID::INVALID; 104 | 105 | BuildOrderState (std::shared_ptr buildOrder) : buildOrder(buildOrder) {} 106 | }; 107 | 108 | void printBuildOrder(const std::vector& buildOrder); 109 | void printBuildOrder(const BuildOrder& buildOrder); 110 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/build_state_serialization.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | template 15 | void serialize(Archive& archive, MiningSpeed& data) { 16 | archive( 17 | cereal::make_nvp("mineralsPerSecond", data.mineralsPerSecond), 18 | cereal::make_nvp("vespenePerSecond", data.vespenePerSecond) 19 | ); 20 | } 21 | 22 | template 23 | void serialize(Archive& archive, BuildOrderFitness& data) { 24 | archive( 25 | cereal::make_nvp("time", data.time), 26 | cereal::make_nvp("resources", data.resources), 27 | cereal::make_nvp("miningSpeed", data.miningSpeed), 28 | cereal::make_nvp("miningSpeedPerSecond", data.miningSpeedPerSecond) 29 | ); 30 | } 31 | 32 | template 33 | void serialize(Archive& archive, BuildOrderItem& data) { 34 | archive( 35 | cereal::make_nvp("type", data.internalType), 36 | cereal::make_nvp("chronoBoosted", data.chronoBoosted) 37 | ); 38 | } 39 | 40 | template 41 | void serialize(Archive& archive, BuildOrder& data) { 42 | archive( 43 | cereal::make_nvp("items", data.items) 44 | ); 45 | } 46 | 47 | template 48 | void serialize(Archive& archive, CombatUpgrades& data) { 49 | archive( 50 | cereal::make_nvp("upgrades", data.upgrades) 51 | ); 52 | } 53 | 54 | template 55 | void serialize(Archive& archive, ChronoBoostInfo& data) { 56 | archive( 57 | cereal::make_nvp("energyOffsets", data.energyOffsets), 58 | cereal::make_nvp("chronoEndTimes", data.chronoEndTimes) 59 | ); 60 | } 61 | 62 | template 63 | void serialize(Archive& archive, BaseInfo& data) { 64 | archive( 65 | cereal::make_nvp("remainingMinerals", data.remainingMinerals), 66 | cereal::make_nvp("remainingVespene1", data.remainingVespene1), 67 | cereal::make_nvp("remainingVespene2", data.remainingVespene2) 68 | ); 69 | } 70 | 71 | template 72 | void serialize(Archive& archive, BuildEvent& data) { 73 | archive( 74 | cereal::make_nvp("type", data.type), 75 | cereal::make_nvp("ability", data.ability), 76 | cereal::make_nvp("caster", data.caster), 77 | cereal::make_nvp("casterAddon", data.casterAddon), 78 | cereal::make_nvp("time", data.time), 79 | cereal::make_nvp("chronoEndTime", data.chronoEndTime) 80 | ); 81 | } 82 | 83 | template 84 | void serialize(Archive& archive, BuildResources& data) { 85 | archive( 86 | cereal::make_nvp("minerals", data.minerals), 87 | cereal::make_nvp("vespene", data.vespene) 88 | ); 89 | } 90 | 91 | template 92 | void serialize(Archive& archive, BuildUnitInfo& data) { 93 | archive( 94 | cereal::make_nvp("type", data.type), 95 | cereal::make_nvp("addon", data.addon), 96 | cereal::make_nvp("units", data.units), 97 | cereal::make_nvp("busyUnits", data.busyUnits) 98 | ); 99 | } 100 | 101 | template 102 | void serialize(Archive& archive, BuildState& state) { 103 | archive( 104 | cereal::make_nvp("time", state.time), 105 | cereal::make_nvp("race", state.race), 106 | cereal::make_nvp("units", state.units), 107 | cereal::make_nvp("events", state.events), 108 | cereal::make_nvp("resources", state.resources), 109 | cereal::make_nvp("baseInfos", state.baseInfos), 110 | cereal::make_nvp("chronoInfo", state.chronoInfo), 111 | cereal::make_nvp("upgrades", state.upgrades) 112 | ); 113 | } 114 | -------------------------------------------------------------------------------- /libvoxelbot/common/unit_lists.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sc2api/sc2_interfaces.h" 3 | #include 4 | #include 5 | 6 | struct AvailableUnitTypes { 7 | std::vector index2item; 8 | std::vector type2index; 9 | std::vector upgrade2index; 10 | std::map arbitraryType2index; 11 | private: 12 | std::vector unitTypes; 13 | public: 14 | 15 | size_t size() const { 16 | return index2item.size(); 17 | } 18 | 19 | const std::vector& getUnitTypes() const { 20 | return unitTypes; 21 | } 22 | 23 | 24 | AvailableUnitTypes(std::initializer_list types); 25 | 26 | int getIndex (sc2::UPGRADE_ID upgrade) const { 27 | assert((int)upgrade < (int)upgrade2index.size()); 28 | auto res = upgrade2index[(int)upgrade]; 29 | assert(res != -1); 30 | return res; 31 | } 32 | 33 | int getIndex (sc2::UNIT_TYPEID unit) const { 34 | assert((int)unit < (int)type2index.size()); 35 | auto res = type2index[(int)unit]; 36 | assert(res != -1); 37 | return res; 38 | } 39 | 40 | int getIndexMaybe (sc2::UPGRADE_ID unit) const { 41 | auto it = arbitraryType2index.find((int)unit); 42 | if (it != arbitraryType2index.end()) { 43 | return it->second; 44 | } 45 | return -1; 46 | } 47 | 48 | int getIndexMaybe (sc2::UNIT_TYPEID unit) const { 49 | // Fast path 50 | if ((int)unit < (int)type2index.size()) { 51 | return type2index[(int)unit]; 52 | } 53 | // Slow path 54 | auto it = arbitraryType2index.find((int)unit); 55 | if (it != arbitraryType2index.end()) { 56 | return it->second; 57 | } 58 | return -1; 59 | } 60 | 61 | // Note: does not work for upgrades 62 | bool contains (sc2::UNIT_TYPEID unit) const { 63 | return getIndexMaybe(unit) != -1; 64 | } 65 | 66 | // Reasonable maximum for the unit/upgrade in an army composition. 67 | // This is primarily used to prevent upgrade counts higher than possible 68 | int armyCompositionMaximum(int index) const { 69 | assert(index < (int)index2item.size()); 70 | if (!index2item[index].isUnitType()) { 71 | // This is an upgrade 72 | if (isUpgradeWithLevels(index2item[index].upgradeID())) { 73 | // Enumerable upgrade, there are 3 upgrade levels 74 | // TODO: Build order execution is bugged at the moment, only the first level can be executed 75 | return 1; 76 | } else { 77 | return 1; 78 | } 79 | } 80 | 81 | // Clamp maximum unit counts to something reasonable 82 | // In part to avoid giving the neural network too large input which might cause odd output 83 | return 20; 84 | } 85 | 86 | sc2::UNIT_TYPEID getUnitType(int index) const { 87 | assert(index < (int)index2item.size()); 88 | if (!index2item[index].isUnitType()) return sc2::UNIT_TYPEID::INVALID; 89 | return index2item[index].typeID(); 90 | } 91 | 92 | bool canBeChronoBoosted (int index) const; 93 | 94 | BuildOrderItem getBuildOrderItem (int index) const { 95 | assert(index < (int)index2item.size()); 96 | return index2item[index]; 97 | } 98 | 99 | BuildOrderItem getBuildOrderItem (const GeneUnitType item) const { 100 | auto itm = index2item[item.type]; 101 | itm.chronoBoosted = item.chronoBoosted; 102 | return itm; 103 | } 104 | 105 | GeneUnitType getGeneItem (const BuildOrderItem item) const { 106 | return GeneUnitType(arbitraryType2index.at((int)item.rawType()), item.chronoBoosted); 107 | } 108 | 109 | friend int remapAvailableUnitIndex(int index, const AvailableUnitTypes& from, const AvailableUnitTypes& to) { 110 | assert(index < (int)from.index2item.size()); 111 | return to.arbitraryType2index.at((int)from.index2item[index].rawType()); 112 | } 113 | }; 114 | 115 | 116 | enum class UnitCategory { 117 | Economic, 118 | ArmyCompositionOptions, 119 | BuildOrderOptions, 120 | }; 121 | 122 | const AvailableUnitTypes& getAvailableUnitsForRace (sc2::Race race); 123 | const AvailableUnitTypes& getAvailableUnitsForRace (sc2::Race race, UnitCategory category); 124 | 125 | extern const AvailableUnitTypes availableUpgrades; -------------------------------------------------------------------------------- /libvoxelbot/buildorder/tracker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | using namespace sc2; 8 | 9 | BuildOrderTracker::BuildOrderTracker (BuildOrder buildOrder) : buildOrderUnits(buildOrder.size()), buildOrder(buildOrder) { 10 | } 11 | 12 | void BuildOrderTracker::setBuildOrder (BuildOrder buildOrder) { 13 | this->buildOrder = buildOrder; 14 | buildOrderUnits = vector(buildOrder.size()); 15 | assert(buildOrder.size() == buildOrderUnits.size()); 16 | } 17 | 18 | void BuildOrderTracker::tweakBuildOrder (std::vector keepMask, BuildOrder tweakBuildOrder) { 19 | assert(keepMask.size() == buildOrder.size()); 20 | vector newUnits; 21 | BuildOrder newBO; 22 | 23 | for (size_t i = 0; i < buildOrder.size(); i++) { 24 | if (keepMask[i]) { 25 | newBO.items.push_back(buildOrder[i]); 26 | newUnits.push_back(buildOrderUnits[i]); 27 | } 28 | } 29 | 30 | for (auto item : tweakBuildOrder.items) { 31 | newBO.items.push_back(item); 32 | newUnits.push_back(nullptr); 33 | } 34 | 35 | buildOrder = move(newBO); 36 | buildOrderUnits = move(newUnits); 37 | } 38 | 39 | void BuildOrderTracker::addExistingUnit(const Unit* unit) { 40 | knownUnits.insert(unit); 41 | } 42 | 43 | void BuildOrderTracker::ignoreUnit (UNIT_TYPEID type, int count) { 44 | ignoreUnits[canonicalize(type)] += count; 45 | } 46 | 47 | vector BuildOrderTracker::update(const ObservationInterface* observation, const vector& ourUnits) { 48 | assert(buildOrder.size() == buildOrderUnits.size()); 49 | tick++; 50 | 51 | for (auto*& u : buildOrderUnits) { 52 | if (u != nullptr) { 53 | // If the unit is dead 54 | // Not sure if e.g. workers that die inside of a refinery are marked as dead properly 55 | // or if they just disappear. So just to be on the same side we make sure we have seen the unit somewhat recently. 56 | bool deadOrGone = !u->is_alive || u->last_seen_game_loop - tick > 60; 57 | 58 | // If a structure is destroyed then we should rebuild it to ensure the build order remains valid. 59 | // However if units die that is normal and we should not try to rebuild them. 60 | if (deadOrGone && (isStructure(u->unit_type) || u->unit_type == UNIT_TYPEID::ZERG_OVERLORD)) { 61 | u = nullptr; 62 | } 63 | } 64 | } 65 | 66 | map inProgress; 67 | for (auto* u : ourUnits) { 68 | if (!knownUnits.count(u)) { 69 | knownUnits.insert(u); 70 | auto canonicalType = canonicalize(u->unit_type); 71 | 72 | // New unit 73 | if (ignoreUnits[canonicalType] > 0) { 74 | ignoreUnits[canonicalType]--; 75 | continue; 76 | } 77 | 78 | for (size_t i = 0; i < buildOrder.size(); i++) { 79 | if (buildOrderUnits[i] == nullptr && (buildOrder[i].rawType() == canonicalType)) { 80 | buildOrderUnits[i] = u; 81 | break; 82 | } 83 | } 84 | } 85 | 86 | for (auto order : u->orders) { 87 | auto createdUnit = abilityToUnit(order.ability_id); 88 | if (createdUnit != UNIT_TYPEID::INVALID) { 89 | if (order.target_unit_tag != NullTag) { 90 | auto* unit = observation->GetUnit(order.target_unit_tag); 91 | // This unit is already existing and is being constructed. 92 | // It will already have been associated with the build order item 93 | if (unit != nullptr && unit->unit_type == createdUnit) continue; 94 | } 95 | inProgress[canonicalize(createdUnit)]++; 96 | } 97 | 98 | // Only process the first order (this bot should never have more than one anyway) 99 | break; 100 | } 101 | } 102 | 103 | vector res(buildOrderUnits.size()); 104 | for (size_t i = 0; i < buildOrderUnits.size(); i++) { 105 | res[i] = buildOrderUnits[i] != nullptr; 106 | if (!buildOrder[i].isUnitType()) { 107 | res[i] = isUpgradeDoneOrInProgress(observation, buildOrder[i].upgradeID()); 108 | } 109 | 110 | // No concrete unit may be associated with the item yet, but it may be on its way 111 | if (!res[i] && inProgress[buildOrder[i].rawType()] > 0) { 112 | inProgress[buildOrder[i].rawType()]--; 113 | res[i] = true; 114 | } 115 | } 116 | return res; 117 | } 118 | 119 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/ladder_interface.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "sc2api/sc2_api.h" 4 | #include "sc2utils/sc2_arg_parser.h" 5 | #include 6 | 7 | 8 | static sc2::Difficulty GetDifficultyFromString(std::string InDifficulty) 9 | { 10 | if (InDifficulty == "VeryEasy") 11 | { 12 | return sc2::Difficulty::VeryEasy; 13 | } 14 | if (InDifficulty == "Easy") 15 | { 16 | return sc2::Difficulty::Easy; 17 | } 18 | if (InDifficulty == "Medium") 19 | { 20 | return sc2::Difficulty::Medium; 21 | } 22 | if (InDifficulty == "MediumHard") 23 | { 24 | return sc2::Difficulty::MediumHard; 25 | } 26 | if (InDifficulty == "Hard") 27 | { 28 | return sc2::Difficulty::Hard; 29 | } 30 | if (InDifficulty == "HardVeryHard") 31 | { 32 | return sc2::Difficulty::HardVeryHard; 33 | } 34 | if (InDifficulty == "VeryHard") 35 | { 36 | return sc2::Difficulty::VeryHard; 37 | } 38 | if (InDifficulty == "CheatVision") 39 | { 40 | return sc2::Difficulty::CheatVision; 41 | } 42 | if (InDifficulty == "CheatMoney") 43 | { 44 | return sc2::Difficulty::CheatMoney; 45 | } 46 | if (InDifficulty == "CheatInsane") 47 | { 48 | return sc2::Difficulty::CheatInsane; 49 | } 50 | 51 | return sc2::Difficulty::Easy; 52 | } 53 | 54 | static sc2::Race GetRaceFromString(const std::string & RaceIn) 55 | { 56 | std::string race(RaceIn); 57 | std::transform(race.begin(), race.end(), race.begin(), ::tolower); 58 | 59 | if (race == "terran") 60 | { 61 | return sc2::Race::Terran; 62 | } 63 | else if (race == "protoss") 64 | { 65 | return sc2::Race::Protoss; 66 | } 67 | else if (race == "zerg") 68 | { 69 | return sc2::Race::Zerg; 70 | } 71 | else if (race == "random") 72 | { 73 | return sc2::Race::Random; 74 | } 75 | 76 | return sc2::Race::Random; 77 | } 78 | 79 | struct ConnectionOptions 80 | { 81 | int32_t GamePort; 82 | int32_t StartPort; 83 | std::string ServerAddress; 84 | bool ComputerOpponent; 85 | sc2::Difficulty ComputerDifficulty; 86 | sc2::Race ComputerRace; 87 | bool Bypass = false; 88 | }; 89 | 90 | static void ParseArguments(int argc, char *argv[], ConnectionOptions &connect_options) 91 | { 92 | sc2::ArgParser arg_parser(argv[0]); 93 | 94 | std::vector args = { 95 | { "-g", "--GamePort", "Port of client to connect to", false }, 96 | { "-o", "--StartPort", "Starting server port", false }, 97 | { "-l", "--LadderServer", "Ladder server address", false }, 98 | { "-c", "--ComputerOpponent", "If we set up a computer oppenent", false }, 99 | { "-a", "--ComputerRace", "Race of computer oppent", false }, 100 | { "-d", "--ComputerDifficulty", "Difficulty of computer oppenent", false }, 101 | { "-b", "--Bypass", "Play without using the ladder manager", false }, 102 | }; 103 | arg_parser.AddOptions(args); 104 | 105 | arg_parser.Parse(argc, argv); 106 | std::string GamePortStr; 107 | if (arg_parser.Get("GamePort", GamePortStr)) { 108 | connect_options.GamePort = atoi(GamePortStr.c_str()); 109 | } 110 | std::string StartPortStr; 111 | if (arg_parser.Get("StartPort", StartPortStr)) { 112 | connect_options.StartPort = atoi(StartPortStr.c_str()); 113 | } 114 | std::string bp; 115 | if (arg_parser.Get("Bypass", bp)) { 116 | connect_options.Bypass = true; 117 | } 118 | arg_parser.Get("LadderServer", connect_options.ServerAddress); 119 | std::string CompOpp; 120 | if (arg_parser.Get("ComputerOpponent", CompOpp)) 121 | { 122 | connect_options.ComputerOpponent = true; 123 | std::string CompRace; 124 | if (arg_parser.Get("ComputerRace", CompRace)) 125 | { 126 | connect_options.ComputerRace = GetRaceFromString(CompRace); 127 | } 128 | std::string CompDiff; 129 | if (arg_parser.Get("ComputerDifficulty", CompDiff)) 130 | { 131 | connect_options.ComputerDifficulty = GetDifficultyFromString(CompDiff); 132 | } 133 | 134 | } 135 | else 136 | { 137 | connect_options.ComputerOpponent = false; 138 | } 139 | } 140 | 141 | static bool RunBot(int argc, char *argv[], sc2::Agent *Agent,sc2::Race race) 142 | { 143 | ConnectionOptions Options; 144 | ParseArguments(argc, argv, Options); 145 | 146 | if (Options.Bypass) return false; 147 | 148 | sc2::Coordinator coordinator; 149 | if (!coordinator.LoadSettings(argc, argv)) { 150 | return true; 151 | } 152 | 153 | // Add the custom bot, it will control the players. 154 | int num_agents; 155 | if (Options.ComputerOpponent) 156 | { 157 | num_agents = 1; 158 | coordinator.SetParticipants({ 159 | CreateParticipant(race, Agent), 160 | CreateComputer(Options.ComputerRace, Options.ComputerDifficulty) 161 | }); 162 | } 163 | else 164 | { 165 | num_agents = 2; 166 | coordinator.SetParticipants({ 167 | CreateParticipant(race, Agent), 168 | }); 169 | } 170 | 171 | // Start the game. 172 | 173 | // Step forward the game simulation. 174 | std::cout << "Connecting to port " << Options.GamePort << std::endl; 175 | coordinator.Connect(Options.GamePort); 176 | coordinator.SetupPorts(num_agents, Options.StartPort, false); 177 | // Step forward the game simulation. 178 | coordinator.JoinGame(); 179 | coordinator.SetTimeoutMS(10000); 180 | std::cout << " Successfully joined game" << std::endl; 181 | while (coordinator.Update()) { 182 | } 183 | return true; 184 | } 185 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(libvoxelbot) 4 | 5 | include("${CMAKE_CURRENT_SOURCE_DIR}/bin2sh.cmake.txt") 6 | bin2h(SOURCE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/libvoxelbot/generated/units.data" HEADER_FILE "${CMAKE_CURRENT_SOURCE_DIR}/libvoxelbot/generated/units_data.h" VARIABLE_NAME "LIBVOXELBOT_DATA_UNITS") 7 | bin2h(SOURCE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/libvoxelbot/generated/upgrades.bin" HEADER_FILE "${CMAKE_CURRENT_SOURCE_DIR}/libvoxelbot/generated/upgrades_data.h" VARIABLE_NAME "LIBVOXELBOT_DATA_UPGRADES") 8 | bin2h(SOURCE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/libvoxelbot/generated/abilities.bin" HEADER_FILE "${CMAKE_CURRENT_SOURCE_DIR}/libvoxelbot/generated/abilities_data.h" VARIABLE_NAME "LIBVOXELBOT_DATA_ABILITIES") 9 | 10 | 11 | # add_subdirectory("cereal") 12 | add_subdirectory("s2client-api") 13 | 14 | 15 | # PYTHON_EXECUTABLE 16 | 17 | # Use bin as the directory for all executables. 18 | # This will make protoc easy to find. 19 | # set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) 20 | # set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) 21 | 22 | # set(PYTHON_EXECUTABLE "/Users/arong/anaconda3/bin/python") 23 | 24 | # set(CMAKE_CONFIGURATION_TYPES, ${CMAKE_CONFIGURATION_TYPES} RelWithDebug) 25 | # set(CMAKE_CXX_FLAGS_RELWITHDEBUG "-O2 -g -fPIC") 26 | 27 | # More dependencies 28 | # include_directories(SYSTEM "${PROJECT_BINARY_DIR}/s2client-api/generated") 29 | # Doesn't seem to be required, so it is commented out for now (might be required on Windows or something) 30 | # include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/s2client-api/contrib/SDL-mirror/include") 31 | # include_directories("s2client-api/contrib/SDL-mirror/include") 32 | 33 | set(LIBVOXELBOT_ENABLE_PYTHON 0) 34 | 35 | function (create_executable project_name mainfile) 36 | # TODO: The .h files don't seem to be necessary (think only .cpp files should be included here anyway) 37 | add_executable(${project_name} ${mainfile}) 38 | 39 | # Sets the grouping in IDEs like visual studio (last parameter is the group name) 40 | set_target_properties(${project_name} PROPERTIES FOLDER target) 41 | 42 | if (MSVC) 43 | # set_target_properties(${project_name} PROPERTIES LINK_FLAGS "/NODEFAULTLIB:libcmt;libconcrt") 44 | endif () 45 | 46 | target_link_libraries(${project_name} sc2api sc2lib sc2utils libvoxelbot) 47 | if (LIBVOXELBOT_ENABLE_PYTHON) 48 | target_link_libraries(${project_name} pybind11::embed) 49 | endif() 50 | endfunction () 51 | 52 | # Note: trying to add SDL2-static as the extra_libs parameter here causes pybind11 modules to fail to link (for unknown reasons, maybe PIC related?) 53 | set(libvoxelbot_sources 54 | "libvoxelbot/buildorder/build_order.cpp" 55 | "libvoxelbot/buildorder/build_state.cpp" 56 | "libvoxelbot/buildorder/build_time_estimator.cpp" 57 | "libvoxelbot/buildorder/optimizer.cpp" 58 | "libvoxelbot/buildorder/tracker.cpp" 59 | "libvoxelbot/combat/combat_environment.cpp" 60 | "libvoxelbot/combat/combat_upgrades.cpp" 61 | "libvoxelbot/combat/simulator.cpp" 62 | "libvoxelbot/common/unit_lists.cpp" 63 | "libvoxelbot/generated/abilities.cpp" 64 | "libvoxelbot/utilities/influence.cpp" 65 | "libvoxelbot/utilities/mappings.cpp" 66 | "libvoxelbot/utilities/pathfinding.cpp" 67 | "libvoxelbot/utilities/predicates.cpp" 68 | "libvoxelbot/utilities/profiler.cpp" 69 | "libvoxelbot/utilities/python_utils.cpp" 70 | "libvoxelbot/utilities/renderer.cpp" 71 | "libvoxelbot/utilities/unit_data_caching.cpp" 72 | "libvoxelbot/caching/dependency_analyzer.cpp" 73 | ) 74 | 75 | 76 | 77 | add_library(libvoxelbot ${libvoxelbot_sources}) 78 | 79 | # Sets the grouping in IDEs like visual studio (last parameter is the group name) 80 | set_target_properties(libvoxelbot PROPERTIES FOLDER target) 81 | target_link_libraries(libvoxelbot sc2api sc2lib sc2utils) 82 | # Require C++14 83 | set_property(TARGET libvoxelbot PROPERTY CXX_STANDARD 14) 84 | set_property(TARGET libvoxelbot PROPERTY CXX_STANDARD_REQUIRED ON) 85 | # target_compile_options(libvoxelbot PRIVATE -Wall) 86 | 87 | # Enable pybind11 bindings 88 | target_compile_definitions(libvoxelbot PUBLIC LIBVOXELBOT_ENABLE_PYTHON=${LIBVOXELBOT_ENABLE_PYTHON}) 89 | # target_link_libraries(libvoxelbot pybind11) 90 | 91 | # Multithreaded builds 92 | if (MSVC) 93 | add_compile_options($<$:/MP>) 94 | endif () 95 | 96 | target_include_directories(libvoxelbot 97 | PUBLIC 98 | $ 99 | $ 100 | $ # TODO: Remove? 101 | $ 102 | $ 103 | PRIVATE 104 | src/generated 105 | ) 106 | 107 | target_include_directories(libvoxelbot 108 | SYSTEM 109 | PUBLIC 110 | $ 111 | ) 112 | 113 | 114 | create_executable(test_combat_simulator "libvoxelbot/combat/simulator.test.cpp") 115 | create_executable(test_build_optimizer "libvoxelbot/buildorder/optimizer.test.cpp") 116 | create_executable(cache_mappings "libvoxelbot/caching/caching.cpp") 117 | create_executable(example_combat_simulator "examples/combat_simulator.cpp") 118 | create_executable(example_combat_simulator2 "examples/combat_simulator2.cpp") 119 | create_executable(example_build_optimizer "examples/build_optimizer.cpp") 120 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/pathfinding.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | using namespace sc2; 9 | 10 | struct PathfindingEntry { 11 | double cost; 12 | double h; 13 | Point2DI pos; 14 | 15 | PathfindingEntry(double _cost, Point2DI _pos) 16 | : cost(_cost), h(0), pos(_pos) { 17 | } 18 | 19 | PathfindingEntry(double _cost, double _h, Point2DI _pos) 20 | : cost(_cost), h(_h), pos(_pos) { 21 | } 22 | 23 | bool operator<(const PathfindingEntry& other) const { 24 | return cost + h > other.cost + other.h; 25 | } 26 | }; 27 | 28 | const int MAX_MAP_SIZE = 256; 29 | 30 | int dx[8] = { 1, 1, 1, 0, 0, -1, -1, -1 }; 31 | int dy[8] = { 1, 0, -1, 1, -1, 1, 0, -1 }; 32 | double dc[8] = { 1.41, 1, 1.41, 1, 1, 1.41, 1, 1.41 }; 33 | 34 | /** Returns a map of distances from the starting points. 35 | * A point is considered a starting point if the element in the startingPoints map is non-zero. 36 | * The costs per cell are given by the costs map. 37 | * Diagonal movement costs sqrt(2) times more than axis aligned movement. 38 | */ 39 | InfluenceMap getDistances(const InfluenceMap& startingPoints, const InfluenceMap& costs) { 40 | assert(startingPoints.w == costs.w); 41 | assert(startingPoints.h == costs.h); 42 | 43 | InfluenceMap distances(startingPoints.w, startingPoints.h); 44 | for (auto& w : distances.weights) 45 | w = numeric_limits::infinity(); 46 | 47 | static priority_queue pq; 48 | 49 | int h = startingPoints.h; 50 | int w = startingPoints.w; 51 | 52 | for (int y = 0; y < h; y++) { 53 | for (int x = 0; x < w; x++) { 54 | if (startingPoints(x, y)) { 55 | pq.push(PathfindingEntry(0.0, Point2DI(x, y))); 56 | distances(x, y) = 0; 57 | } 58 | } 59 | } 60 | 61 | while (!pq.empty()) { 62 | auto currentEntry = pq.top(); 63 | auto currentPos = currentEntry.pos; 64 | pq.pop(); 65 | if (currentEntry.cost > distances(currentPos)) { 66 | continue; 67 | } 68 | 69 | for (int i = 0; i < 8; i++) { 70 | int x = currentPos.x + dx[i]; 71 | int y = currentPos.y + dy[i]; 72 | if ((unsigned int)x >= w || (unsigned int)y >= h || isinf(costs(x, y))) { 73 | continue; 74 | } 75 | 76 | double newDistance = currentEntry.cost + costs(x, y) * dc[i]; 77 | if (newDistance < distances(x, y)) { 78 | distances(x, y) = newDistance; 79 | pq.push(PathfindingEntry(newDistance, Point2DI(x, y))); 80 | } 81 | } 82 | } 83 | 84 | // Clear queue (required as it is reused for the next pathfinding call) 85 | while (!pq.empty()) 86 | pq.pop(); 87 | return distances; 88 | } 89 | 90 | /** Returns the shortest path between the start and end point. 91 | * The costs per cell are given by the costs map. 92 | * Diagonal movement costs sqrt(2) times more than axis aligned movement. 93 | */ 94 | vector getPath(const Point2DI from, const Point2DI to, const InfluenceMap& costs) { 95 | static vector > cost(MAX_MAP_SIZE, vector(MAX_MAP_SIZE)); 96 | static vector > version(MAX_MAP_SIZE, vector(MAX_MAP_SIZE)); 97 | static vector > parent(MAX_MAP_SIZE, vector(MAX_MAP_SIZE)); 98 | static priority_queue pq; 99 | static int pathfindingVersion = 0; 100 | 101 | int w = costs.w; 102 | int h = costs.h; 103 | 104 | // Make sure map is sane 105 | assert(w <= MAX_MAP_SIZE); 106 | assert(h <= MAX_MAP_SIZE); 107 | 108 | pathfindingVersion++; 109 | 110 | pq.push(PathfindingEntry(0.0, from)); 111 | cost[from.x][from.y] = 0; 112 | version[from.x][from.y] = pathfindingVersion; 113 | parent[from.x][from.y] = from; 114 | bool reached = false; 115 | 116 | while (!pq.empty()) { 117 | auto currentEntry = pq.top(); 118 | auto currentPos = currentEntry.pos; 119 | pq.pop(); 120 | if (currentEntry.cost > cost[currentPos.x][currentPos.y]) { 121 | continue; 122 | } 123 | 124 | if (currentEntry.pos == to) { 125 | reached = true; 126 | break; 127 | } 128 | 129 | for (int i = 0; i < 8; i++) { 130 | int x = currentPos.x + dx[i]; 131 | int y = currentPos.y + dy[i]; 132 | if (x < 0 || x >= w || y < 0 || y >= h) { 133 | continue; 134 | } 135 | // TODO: sqrt can be optimized 136 | double newCost = currentEntry.cost + costs(x, y) * dc[i]; 137 | if ((newCost < cost[x][y] || version[x][y] != pathfindingVersion) && isfinite(cost[x][y])) { 138 | cost[x][y] = newCost; 139 | parent[x][y] = currentPos; 140 | version[x][y] = pathfindingVersion; 141 | pq.push(PathfindingEntry(newCost, 0 /* abs(x - to.x) + abs(y - to.y) */, Point2DI(x, y))); 142 | } 143 | } 144 | } 145 | 146 | // Clear queue (required as it is reused for the next pathfinding call) 147 | while (!pq.empty()) 148 | pq.pop(); 149 | 150 | if (!reached) 151 | return vector(); 152 | 153 | Point2DI currentPos = to; 154 | vector path = { currentPos }; 155 | while (currentPos != from) { 156 | auto p = parent[currentPos.x][currentPos.y]; 157 | path.push_back(p); 158 | currentPos = p; 159 | } 160 | reverse(path.begin(), path.end()); 161 | return path; 162 | } -------------------------------------------------------------------------------- /libvoxelbot/buildorder/build_order.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | using namespace sc2; 8 | 9 | static string timeStr(float time) { 10 | stringstream ss; 11 | int sec = (int)(fmod(time, 60.0f)); 12 | ss << (int)(time / 60.0f) << ":" << (sec < 10 ? "0" : "") << sec; 13 | return ss.str(); 14 | } 15 | 16 | 17 | void printBuildOrderDetailed(const BuildState& startState, const BuildOrder& buildOrder, const vector* highlight) { 18 | BuildState state = startState; 19 | cout << "Starting units" << endl; 20 | for (auto u : startState.units) { 21 | cout << "\t" << u.units << "x " << UnitTypeToName(u.type); 22 | if (u.addon != UNIT_TYPEID::INVALID) 23 | cout << " + " << UnitTypeToName(u.addon); 24 | cout << endl; 25 | } 26 | cout << "Build order size " << buildOrder.size() << endl; 27 | bool success = state.simulateBuildOrder(buildOrder, [&](int i) { 28 | if (highlight != nullptr && (*highlight)[i]) { 29 | // Color the text 30 | cout << "\x1b[" << 48 << ";2;" << 228 << ";" << 26 << ";" << 28 << "m"; 31 | } 32 | if (buildOrder.items[i].chronoBoosted) { 33 | // Color the text 34 | cout << "\x1b[" << 48 << ";2;" << 36 << ";" << 202 << ";" << 212 << "m"; 35 | } 36 | string name = buildOrder[i].isUnitType() ? UnitTypeToName(buildOrder[i].typeID()) : UpgradeIDToName(buildOrder[i].upgradeID()); 37 | cout << "Step " << i << "\t" << (int)(state.time / 60.0f) << ":" << (int)(fmod(state.time, 60.0f)) << "\t" << name << " " 38 | << "food: " << (state.foodCap() - state.foodAvailable()) << "/" << state.foodCap() << " resources: " << (int)state.resources.minerals << "+" << (int)state.resources.vespene << " " << (state.baseInfos.size() > 0 ? state.baseInfos[0].remainingMinerals : 0); 39 | 40 | // Reset color 41 | cout << "\033[0m"; 42 | cout << endl; 43 | }); 44 | 45 | cout << (success ? "Finished at " : "Failed at "); 46 | cout << (int)(state.time / 60.0f) << ":" << (int)(fmod(state.time, 60.0f)) << " resources: " << state.resources.minerals << "+" << state.resources.vespene << " mining speed: " << (int)round(state.miningSpeed().mineralsPerSecond*60) << "/min + " << (int)round(state.miningSpeed().vespenePerSecond*60) << "/min" << endl; 47 | 48 | // if (success) printMiningSpeedFuture(state); 49 | } 50 | 51 | void printBuildOrder(const vector& buildOrder) { 52 | cout << "Build order size " << buildOrder.size() << endl; 53 | for (size_t i = 0; i < buildOrder.size(); i++) { 54 | cout << "Step " << i << " " << UnitTypeToName(buildOrder[i]) << endl; 55 | } 56 | } 57 | 58 | void printBuildOrder(const BuildOrder& buildOrder) { 59 | cout << "Build order size " << buildOrder.size() << endl; 60 | for (size_t i = 0; i < buildOrder.size(); i++) { 61 | cout << "Step " << i << " "; 62 | if (buildOrder[i].isUnitType()) { 63 | cout << UnitTypeToName(buildOrder[i].typeID()) << endl; 64 | } else { 65 | cout << UpgradeIDToName(buildOrder[i].upgradeID()) << endl; 66 | } 67 | } 68 | } 69 | 70 | std::string BuildOrder::toString() { 71 | stringstream ss; 72 | for (size_t i = 0; i < items.size(); i++) { 73 | ss << setw(3) << i << " "; 74 | if (items[i].isUnitType()) { 75 | ss << UnitTypeToName(items[i].typeID()); 76 | } else { 77 | ss << UpgradeIDToName(items[i].upgradeID()); 78 | } 79 | ss << endl; 80 | } 81 | return ss.str(); 82 | } 83 | 84 | static const string ChronoBoostColor = "\x1b[48;2;0;105;179m"; 85 | 86 | std::string BuildOrder::toString(BuildState initialState, BuildOrderPrintMode mode) { 87 | stringstream ss; 88 | if (mode == BuildOrderPrintMode::Brief) { 89 | bool success = initialState.simulateBuildOrder(*this, [&](int i) { 90 | if (items[i].chronoBoosted) { 91 | // Color the text 92 | ss << ChronoBoostColor; 93 | } 94 | 95 | string name = items[i].isUnitType() ? UnitTypeToName(items[i].typeID()) : UpgradeIDToName(items[i].upgradeID()); 96 | ss << setw(3) << i << " " << timeStr(initialState.time) << " " << name; 97 | 98 | // Reset color 99 | ss << "\033[0m"; 100 | ss << endl; 101 | }); 102 | ss << (success ? "Done at " : "Failed at ") << initialState.time << endl; 103 | } else { 104 | // Detailed 105 | ss << " Time Food Min. Ves. Name" << endl; 106 | bool success = initialState.simulateBuildOrder(*this, [&](int i) { 107 | if (items[i].chronoBoosted) { 108 | // Color the text 109 | ss << ChronoBoostColor; 110 | } 111 | 112 | string name = items[i].isUnitType() ? UnitTypeToName(items[i].typeID()) : UpgradeIDToName(items[i].upgradeID()); 113 | ss << setw(3) << i << " " << timeStr(initialState.time) << " "; 114 | ss << (initialState.foodCap() - initialState.foodAvailable()) << "/" << initialState.foodCap() << " "; 115 | ss << setw(4) << (int)initialState.resources.minerals << " " << setw(4) << (int)initialState.resources.vespene << " "; 116 | // ss << " food: " << (initialState.foodCap() - initialState.foodAvailable()) << "/" << initialState.foodCap() << " resources: " << (int)initialState.resources.minerals << "+" << (int)initialState.resources.vespene; 117 | ss << name; 118 | 119 | // Reset color 120 | ss << "\033[0m"; 121 | ss << endl; 122 | }); 123 | ss << (success ? "Done at " : "Failed at ") << timeStr(initialState.time) << endl; 124 | } 125 | return ss.str(); 126 | } 127 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/mappings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sc2api/sc2_api.h" 3 | 4 | void assertMappingsInitialized(); 5 | void initMappings(const sc2::ObservationInterface* observation); 6 | void initMappings(); 7 | const sc2::UnitTypeData& getUnitData(sc2::UNIT_TYPEID type); 8 | const std::vector& getUnitTypes(); 9 | const sc2::AbilityData& getAbilityData(sc2::ABILITY_ID ability); 10 | const sc2::UpgradeData& getUpgradeData(sc2::UPGRADE_ID upgrade); 11 | sc2::UNIT_TYPEID getUpgradeUnitDependency(sc2::UPGRADE_ID upgrade); 12 | sc2::UPGRADE_ID getUpgradeUpgradeDependency(sc2::UPGRADE_ID upgrade); 13 | sc2::UNIT_TYPEID canonicalize(sc2::UNIT_TYPEID unitType); 14 | const std::vector& abilityToCasterUnit(sc2::ABILITY_ID ability); 15 | sc2::UNIT_TYPEID abilityToUnit(sc2::ABILITY_ID ability); 16 | sc2::UPGRADE_ID abilityToUpgrade(sc2::ABILITY_ID ability); 17 | sc2::UNIT_TYPEID simplifyUnitType(sc2::UNIT_TYPEID type); 18 | const std::vector& hasBeen(sc2::UNIT_TYPEID type); 19 | const std::vector& canBecome(sc2::UNIT_TYPEID type); 20 | sc2::UNIT_TYPEID upgradedFrom(sc2::UNIT_TYPEID type); 21 | float maxHealth(sc2::UNIT_TYPEID type); 22 | float maxShield(sc2::UNIT_TYPEID type); 23 | bool isFlying(sc2::UNIT_TYPEID type); 24 | bool isStationary(sc2::UNIT_TYPEID type); 25 | bool isStructure(sc2::UNIT_TYPEID type); 26 | float unitRadius(sc2::UNIT_TYPEID type); 27 | const std::vector& unitAbilities(sc2::UNIT_TYPEID type); 28 | sc2::UNIT_TYPEID getSpecificAddonType(sc2::UNIT_TYPEID caster, sc2::UNIT_TYPEID addon); 29 | 30 | /** Times in the SC2 API are often defined in ticks, instead of seconds. 31 | * This method assumes the 'Faster' game speed. 32 | */ 33 | inline float ticksToSeconds(float ticks) { 34 | return ticks / 22.4f; 35 | } 36 | 37 | inline bool isBasicHarvester(sc2::UNIT_TYPEID type) { 38 | switch (type) { 39 | case sc2::UNIT_TYPEID::TERRAN_SCV: 40 | case sc2::UNIT_TYPEID::ZERG_DRONE: 41 | case sc2::UNIT_TYPEID::PROTOSS_PROBE: 42 | return true; 43 | default: 44 | return false; 45 | } 46 | } 47 | 48 | inline bool isVespeneHarvester(sc2::UNIT_TYPEID type) { 49 | switch (type) { 50 | case sc2::UNIT_TYPEID::TERRAN_REFINERY: 51 | case sc2::UNIT_TYPEID::ZERG_EXTRACTOR: 52 | case sc2::UNIT_TYPEID::PROTOSS_ASSIMILATOR: 53 | return true; 54 | default: 55 | return false; 56 | } 57 | } 58 | 59 | inline sc2::UNIT_TYPEID getHarvesterUnitForRace(sc2::Race race) { 60 | switch (race) { 61 | case sc2::Race::Protoss: 62 | return sc2::UNIT_TYPEID::PROTOSS_PROBE; 63 | case sc2::Race::Terran: 64 | return sc2::UNIT_TYPEID::TERRAN_SCV; 65 | case sc2::Race::Zerg: 66 | return sc2::UNIT_TYPEID::ZERG_DRONE; 67 | default: 68 | assert(false); 69 | return sc2::UNIT_TYPEID::INVALID; 70 | } 71 | } 72 | 73 | inline sc2::UNIT_TYPEID getSupplyUnitForRace(sc2::Race race) { 74 | switch (race) { 75 | case sc2::Race::Protoss: 76 | return sc2::UNIT_TYPEID::PROTOSS_PYLON; 77 | case sc2::Race::Terran: 78 | return sc2::UNIT_TYPEID::TERRAN_SUPPLYDEPOT; 79 | case sc2::Race::Zerg: 80 | return sc2::UNIT_TYPEID::ZERG_OVERLORD; 81 | default: 82 | assert(false); 83 | return sc2::UNIT_TYPEID::INVALID; 84 | } 85 | } 86 | 87 | inline sc2::UNIT_TYPEID getVespeneHarvesterForRace(sc2::Race race) { 88 | switch (race) { 89 | case sc2::Race::Protoss: 90 | return sc2::UNIT_TYPEID::PROTOSS_ASSIMILATOR; 91 | case sc2::Race::Terran: 92 | return sc2::UNIT_TYPEID::TERRAN_REFINERY; 93 | case sc2::Race::Zerg: 94 | return sc2::UNIT_TYPEID::ZERG_EXTRACTOR; 95 | default: 96 | assert(false); 97 | return sc2::UNIT_TYPEID::INVALID; 98 | } 99 | } 100 | 101 | inline sc2::UNIT_TYPEID getTownHallForRace(sc2::Race race) { 102 | switch (race) { 103 | case sc2::Race::Protoss: 104 | return sc2::UNIT_TYPEID::PROTOSS_NEXUS; 105 | case sc2::Race::Terran: 106 | return sc2::UNIT_TYPEID::TERRAN_COMMANDCENTER; 107 | case sc2::Race::Zerg: 108 | return sc2::UNIT_TYPEID::ZERG_HATCHERY; 109 | default: 110 | assert(false); 111 | return sc2::UNIT_TYPEID::INVALID; 112 | } 113 | } 114 | 115 | inline bool isAddon(sc2::UNIT_TYPEID unit) { 116 | switch (unit) { 117 | case sc2::UNIT_TYPEID::TERRAN_TECHLAB: 118 | case sc2::UNIT_TYPEID::TERRAN_REACTOR: 119 | case sc2::UNIT_TYPEID::TERRAN_BARRACKSREACTOR: 120 | case sc2::UNIT_TYPEID::TERRAN_FACTORYREACTOR: 121 | case sc2::UNIT_TYPEID::TERRAN_STARPORTREACTOR: 122 | case sc2::UNIT_TYPEID::TERRAN_BARRACKSTECHLAB: 123 | case sc2::UNIT_TYPEID::TERRAN_FACTORYTECHLAB: 124 | case sc2::UNIT_TYPEID::TERRAN_STARPORTTECHLAB: 125 | return true; 126 | default: 127 | return false; 128 | } 129 | } 130 | 131 | inline bool isMineralField(sc2::UNIT_TYPEID unit) { 132 | switch (unit) { 133 | case sc2::UNIT_TYPEID::NEUTRAL_BATTLESTATIONMINERALFIELD: 134 | case sc2::UNIT_TYPEID::NEUTRAL_BATTLESTATIONMINERALFIELD750: 135 | case sc2::UNIT_TYPEID::NEUTRAL_LABMINERALFIELD: 136 | case sc2::UNIT_TYPEID::NEUTRAL_LABMINERALFIELD750: 137 | case sc2::UNIT_TYPEID::NEUTRAL_MINERALFIELD: 138 | case sc2::UNIT_TYPEID::NEUTRAL_MINERALFIELD750: 139 | case sc2::UNIT_TYPEID::NEUTRAL_PURIFIERMINERALFIELD: 140 | case sc2::UNIT_TYPEID::NEUTRAL_PURIFIERMINERALFIELD750: 141 | case sc2::UNIT_TYPEID::NEUTRAL_PURIFIERRICHMINERALFIELD: 142 | case sc2::UNIT_TYPEID::NEUTRAL_PURIFIERRICHMINERALFIELD750: 143 | case sc2::UNIT_TYPEID::NEUTRAL_RICHMINERALFIELD: 144 | case sc2::UNIT_TYPEID::NEUTRAL_RICHMINERALFIELD750: 145 | return true; 146 | default: 147 | return false; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /libvoxelbot/combat/simulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sc2api/sc2_interfaces.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct AvailableUnitTypes; 12 | struct BuildState; 13 | struct CombatEnvironment; 14 | 15 | inline bool canBeAttackedByAirWeapons(sc2::UNIT_TYPEID type) { 16 | return isFlying(type) || type == sc2::UNIT_TYPEID::PROTOSS_COLOSSUS; 17 | } 18 | 19 | struct CombatUnit { 20 | int owner; 21 | sc2::UNIT_TYPEID type; 22 | float health; 23 | float health_max; 24 | float shield; 25 | float shield_max; 26 | float energy; 27 | bool is_flying; 28 | float buffTimer = 0; 29 | void modifyHealth(float delta); 30 | 31 | CombatUnit() {} 32 | CombatUnit (int owner, sc2::UNIT_TYPEID type, int health, bool flying) : owner(owner), type(type), health(health), health_max(health), shield(0), shield_max(0), energy(50), is_flying(flying) {} 33 | CombatUnit(const sc2::Unit& unit) : owner(unit.owner), type(unit.unit_type), health(unit.health), health_max(unit.health_max), shield(unit.shield), shield_max(unit.shield_max), energy(unit.energy), is_flying(unit.is_flying) {} 34 | }; 35 | 36 | struct CombatState { 37 | std::vector units; 38 | const CombatEnvironment* environment = nullptr; 39 | 40 | // Owner with the highest total health summed over all units 41 | int owner_with_best_outcome() const; 42 | std::string toString(); 43 | }; 44 | 45 | struct CombatResult { 46 | float time = 0; 47 | std::array averageHealthTime = {{ 0, 0 }}; 48 | CombatState state; 49 | }; 50 | 51 | struct CombatRecordingFrame { 52 | int tick; 53 | std::vector> healths; 54 | void add(sc2::UNIT_TYPEID type, int owner, float health, float shield); 55 | }; 56 | 57 | struct CombatRecording { 58 | std::vector frames; 59 | void writeCSV(std::string filename); 60 | }; 61 | 62 | struct CombatSettings { 63 | bool badMicro = false; 64 | bool debug = false; 65 | bool enableSplash = true; 66 | bool enableTimingAdjustment = true; 67 | bool enableSurroundLimits = true; 68 | bool enableMeleeBlocking = true; 69 | bool workersDoNoDamage = false; 70 | bool assumeReasonablePositioning = true; 71 | float maxTime = std::numeric_limits::infinity(); 72 | float startTime = 0; 73 | }; 74 | 75 | 76 | 77 | struct CombatPredictor { 78 | private: 79 | mutable std::map combatEnvironments; 80 | public: 81 | CombatEnvironment defaultCombatEnvironment; 82 | CombatPredictor(); 83 | void init(); 84 | CombatResult predict_engage(const CombatState& state, bool debug=false, bool badMicro=false, CombatRecording* recording=nullptr, int defenderPlayer = 1) const; 85 | CombatResult predict_engage(const CombatState& state, CombatSettings settings, CombatRecording* recording=nullptr, int defenderPlayer = 1) const; 86 | 87 | const CombatEnvironment& getCombatEnvironment(const CombatUpgrades& upgrades, const CombatUpgrades& targetUpgrades) const; 88 | 89 | const CombatEnvironment& combineCombatEnvironment(const CombatEnvironment* env, const CombatUpgrades& upgrades, int upgradesOwner) const; 90 | 91 | float targetScore(const CombatUnit& unit, bool hasGround, bool hasAir) const; 92 | 93 | float mineralScore(const CombatState& initialState, const CombatResult& combatResult, int player, const std::vector& timeToProduceUnits, const CombatUpgrades upgrades) const; 94 | float mineralScoreFixedTime(const CombatState& initialState, const CombatResult& combatResult, int player, const std::vector& timeToProduceUnits, const CombatUpgrades upgrades) const; 95 | }; 96 | 97 | CombatUnit makeUnit(int owner, sc2::UNIT_TYPEID type); 98 | 99 | struct ArmyComposition { 100 | std::vector> unitCounts; 101 | CombatUpgrades upgrades; 102 | 103 | void combine(const ArmyComposition& other) { 104 | for (auto u : other.unitCounts) { 105 | bool found = false; 106 | for (auto& u2 : unitCounts) { 107 | if (u2.first == u.first) { 108 | u2 = { u2.first, u2.second + u.second }; 109 | found = true; 110 | break; 111 | } 112 | } 113 | 114 | if (!found) unitCounts.push_back(u); 115 | } 116 | 117 | upgrades.combine(other.upgrades); 118 | } 119 | }; 120 | 121 | struct CompositionSearchSettings { 122 | const CombatPredictor& combatPredictor; 123 | const AvailableUnitTypes& availableUnitTypes; 124 | const BuildOptimizerNN* buildTimePredictor = nullptr; 125 | float availableTime = 4 * 60; 126 | 127 | CompositionSearchSettings(const CombatPredictor& combatPredictor, const AvailableUnitTypes& availableUnitTypes, const BuildOptimizerNN* buildTimePredictor = nullptr) : combatPredictor(combatPredictor), availableUnitTypes(availableUnitTypes), buildTimePredictor(buildTimePredictor) {} 128 | }; 129 | 130 | ArmyComposition findBestCompositionGenetic(const CombatPredictor& predictor, const AvailableUnitTypes& availableUnitTypes, const CombatState& opponent, const BuildOptimizerNN* buildTimePredictor = nullptr, const BuildState* startingBuildState = nullptr, std::vector>* seedComposition = nullptr); 131 | ArmyComposition findBestCompositionGenetic(const CombatState& opponent, CompositionSearchSettings settings, const BuildState* startingBuildState = nullptr, std::vector>* seedComposition = nullptr); 132 | 133 | struct CombatRecorder { 134 | private: 135 | std::vector>> frames; 136 | public: 137 | void tick(const sc2::ObservationInterface* observation); 138 | void finalize(std::string filename="recording.csv"); 139 | }; 140 | 141 | struct SurroundInfo { 142 | int maxAttackersPerDefender; 143 | int maxMeleeAttackers; 144 | }; 145 | 146 | void logRecordings(CombatState& state, const CombatPredictor& predictor, float spawnOffset = 0, std::string msg = "recording"); 147 | float timeToBeAbleToAttack (const CombatEnvironment& env, CombatUnit& unit, float distanceToEnemy); 148 | SurroundInfo maxSurround(float enemyGroundUnitArea, int enemyGroundUnits); 149 | -------------------------------------------------------------------------------- /libvoxelbot/caching/dependency_analyzer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | using namespace sc2; 10 | 11 | // //! Stable ID. This ID will not change between patches. 12 | // UnitTypeID unit_type_id; 13 | // //! Unit type name, corresponds to the game's catalog. 14 | // std::string name; 15 | // //! Cost in minerals to build this unit type. 16 | // int mineral_cost; 17 | // //! Cost in vespene to build this unit type. 18 | // int vespene_cost; 19 | // //! Weapons on this unit type. 20 | // std::vector weapons; 21 | // //! How much food the unit requires. 22 | // float food_required; 23 | // //! How much food the unit provides. 24 | // float food_provided; 25 | // //! Which ability id creates the unit. 26 | // AbilityID ability_id; 27 | // //! The race the unit belongs to. 28 | // Race race; 29 | // //! How long the unit takes to build. 30 | // float build_time; 31 | // //! Units this is equivalent to in terms of satisfying tech requirements. 32 | // std::vector tech_alias; 33 | // //! Units that are morphed variants of the same unit. 34 | // UnitTypeID unit_alias; 35 | // //! Structure required to build this unit. (Or any with the same tech_alias) 36 | // UnitTypeID tech_requirement; 37 | // //! Whether tech_requirement is an add-on. 38 | // bool require_attached; 39 | 40 | map gasBuilding = { 41 | { Race::Terran, UNIT_TYPEID::TERRAN_REFINERY }, 42 | { Race::Protoss, UNIT_TYPEID::PROTOSS_ASSIMILATOR }, 43 | { Race::Zerg, UNIT_TYPEID::ZERG_EXTRACTOR }, 44 | }; 45 | 46 | map supplyUnit = { 47 | { Race::Terran, UNIT_TYPEID::TERRAN_SUPPLYDEPOT }, 48 | { Race::Protoss, UNIT_TYPEID::PROTOSS_PYLON }, 49 | { Race::Zerg, UNIT_TYPEID::ZERG_OVERLORD }, 50 | }; 51 | 52 | Race RaceNeutral = (Race)3; 53 | 54 | void DependencyAnalyzer::analyze() { 55 | auto unitTypes = getUnitTypes(); 56 | 57 | map> requirements; 58 | for (UnitTypeData& unitType : unitTypes) { 59 | if (!unitType.available || unitType.race == RaceNeutral) 60 | continue; 61 | // if (unitType.unit_type_id != UNIT_TYPEID::ZERG_GREATERSPIRE) continue; 62 | 63 | if (unitType.ability_id != ABILITY_ID::INVALID) { 64 | // cout << "Ability: " << AbilityTypeToName(unitType.ability_id) << endl; 65 | try { 66 | auto requiredUnitOptions = abilityToCasterUnit(unitType.ability_id); 67 | if (requiredUnitOptions.size() == 0) { 68 | // cout << UnitTypeToName(unitType.unit_type_id) << " requires ability " << AbilityTypeToName(unitType.ability_id) << " but that ability is not cast by any known unit" << endl; 69 | } else { 70 | auto requiredUnit = UNIT_TYPEID::INVALID; 71 | int minCost = 1000000; 72 | 73 | // In case there are several buildings with the same ability, use the one with the lowest cost 74 | // Since costs are accumulative, this will find the one lowest in the tech tree. 75 | // This is useful to pick e.g. command center instead of orbital command, even though both can train SCVs. 76 | for (auto opt : requiredUnitOptions) { 77 | int cost = unitTypes[(int)opt].mineral_cost + unitTypes[(int)opt].vespene_cost; 78 | if (cost < minCost) { 79 | minCost = cost; 80 | requiredUnit = opt; 81 | } 82 | } 83 | 84 | if (requiredUnit != UNIT_TYPEID::INVALID) { 85 | requirements[unitType.unit_type_id].insert(requiredUnit); 86 | 87 | if (unitTypes[(int)requiredUnit].race != unitType.race) { 88 | cerr << "Error in definitions" << endl; 89 | cerr << unitType.name << " requires unit (via ability): " << UnitTypeToName(requiredUnit) << " via " << AbilityTypeToName(unitType.ability_id) << endl; 90 | } 91 | } 92 | } 93 | } catch (exception e) { 94 | cout << "Unhandled ability id: " << unitType.ability_id << "(" << AbilityTypeToName(unitType.ability_id) << ") for unit " << unitType.name << endl; 95 | } 96 | } 97 | 98 | auto requiredTechBuilding = unitType.tech_requirement; 99 | if (requiredTechBuilding != UNIT_TYPEID::INVALID) { 100 | // cout << "Requires tech: " << UnitTypeToName(requiredTechBuilding) << endl; 101 | requirements[unitType.unit_type_id].insert(requiredTechBuilding); 102 | if (unitTypes[requiredTechBuilding].race != unitType.race) { 103 | cerr << "Error in definitions" << endl; 104 | cerr << unitType.name << " requires tech: " << UnitTypeToName(requiredTechBuilding) << endl; 105 | } 106 | } 107 | 108 | if (unitType.vespene_cost > 0) { 109 | // cout << "Requires gas: " << UnitTypeToName(gasBuilding[unitType.race]) << endl; 110 | requirements[unitType.unit_type_id].insert(gasBuilding[unitType.race]); 111 | } 112 | 113 | if (unitType.race == Race::Protoss && isStructure(unitType) && unitType.unit_type_id != UNIT_TYPEID::PROTOSS_NEXUS && unitType.unit_type_id != UNIT_TYPEID::PROTOSS_PYLON && unitType.unit_type_id != UNIT_TYPEID::PROTOSS_ASSIMILATOR) { 114 | // cout << "Requires pylon" << endl; 115 | requirements[unitType.unit_type_id].insert(UNIT_TYPEID::PROTOSS_PYLON); 116 | } 117 | } 118 | 119 | allUnitDependencies = vector>(unitTypes.size()); 120 | for (size_t i = 0; i < unitTypes.size(); i++) { 121 | UnitTypeData& unitType = unitTypes[i]; 122 | if (!unitType.available || unitType.race == RaceNeutral) 123 | continue; 124 | // if (unitType.unit_type_id != UNIT_TYPEID::ZERG_GREATERSPIRE) continue; 125 | // cout << "Unit: " << unitType.name << endl; 126 | vector reqs; 127 | stack st; 128 | st.push(unitType.unit_type_id); 129 | while (st.size() > 0) { 130 | auto u = st.top(); 131 | st.pop(); 132 | for (auto r : requirements[u]) { 133 | if (find(reqs.begin(), reqs.end(), r) == reqs.end()) { 134 | reqs.push_back(r); 135 | st.push(r); 136 | } 137 | } 138 | } 139 | 140 | allUnitDependencies[i] = reqs; 141 | // for (auto u : reqs) { 142 | // cout << unitType.name << " requires " << unitTypes[(int)u].name << endl; 143 | // } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/predicates.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace sc2; 5 | 6 | bool IsAttackable::operator()(const Unit& unit) { 7 | switch (unit.unit_type.ToType()) { 8 | case UNIT_TYPEID::ZERG_OVERLORD: 9 | return false; 10 | case UNIT_TYPEID::ZERG_OVERSEER: 11 | return false; 12 | case UNIT_TYPEID::PROTOSS_OBSERVER: 13 | return false; 14 | default: 15 | return true; 16 | } 17 | } 18 | 19 | bool IsFlying::operator()(const Unit& unit) { 20 | return unit.is_flying; 21 | } 22 | 23 | bool IsArmy::operator()(const Unit& unit) { 24 | auto attributes = getUnitData(unit.unit_type).attributes; 25 | for (const auto& attribute : attributes) { 26 | if (attribute == Attribute::Structure) { 27 | return false; 28 | } 29 | } 30 | switch (unit.unit_type.ToType()) { 31 | case UNIT_TYPEID::ZERG_OVERLORD: 32 | return false; 33 | case UNIT_TYPEID::PROTOSS_PROBE: 34 | return false; 35 | case UNIT_TYPEID::ZERG_DRONE: 36 | return false; 37 | case UNIT_TYPEID::TERRAN_SCV: 38 | return false; 39 | case UNIT_TYPEID::ZERG_QUEEN: 40 | return false; 41 | case UNIT_TYPEID::ZERG_LARVA: 42 | return false; 43 | case UNIT_TYPEID::ZERG_EGG: 44 | return false; 45 | case UNIT_TYPEID::TERRAN_MULE: 46 | return false; 47 | case UNIT_TYPEID::TERRAN_NUKE: 48 | return false; 49 | default: 50 | return true; 51 | } 52 | } 53 | 54 | bool isArmy(UNIT_TYPEID type) { 55 | auto& unitData = getUnitData(type); 56 | auto attributes = unitData.attributes; 57 | for (const auto& attribute : attributes) { 58 | if (attribute == Attribute::Structure) { 59 | return false; 60 | } 61 | } 62 | switch (type) { 63 | case UNIT_TYPEID::ZERG_OVERLORD: 64 | return false; 65 | case UNIT_TYPEID::PROTOSS_PROBE: 66 | return false; 67 | case UNIT_TYPEID::ZERG_DRONE: 68 | return false; 69 | case UNIT_TYPEID::TERRAN_SCV: 70 | return false; 71 | case UNIT_TYPEID::ZERG_QUEEN: 72 | return false; 73 | case UNIT_TYPEID::ZERG_LARVA: 74 | return false; 75 | case UNIT_TYPEID::ZERG_EGG: 76 | return false; 77 | case UNIT_TYPEID::TERRAN_MULE: 78 | return false; 79 | case UNIT_TYPEID::TERRAN_NUKE: 80 | return false; 81 | default: 82 | return true; 83 | } 84 | } 85 | 86 | bool IsTownHall::operator()(const Unit& unit) { 87 | switch (unit.unit_type.ToType()) { 88 | case UNIT_TYPEID::ZERG_HATCHERY: 89 | return true; 90 | case UNIT_TYPEID::ZERG_LAIR: 91 | return true; 92 | case UNIT_TYPEID::ZERG_HIVE: 93 | return true; 94 | case UNIT_TYPEID::TERRAN_COMMANDCENTER: 95 | return true; 96 | case UNIT_TYPEID::TERRAN_ORBITALCOMMAND: 97 | return true; 98 | case UNIT_TYPEID::TERRAN_ORBITALCOMMANDFLYING: 99 | return true; 100 | case UNIT_TYPEID::TERRAN_PLANETARYFORTRESS: 101 | return true; 102 | case UNIT_TYPEID::PROTOSS_NEXUS: 103 | return true; 104 | default: 105 | return false; 106 | } 107 | } 108 | 109 | bool IsVespeneGeyser::operator()(const Unit& unit) { 110 | switch (unit.unit_type.ToType()) { 111 | case UNIT_TYPEID::NEUTRAL_VESPENEGEYSER: 112 | case UNIT_TYPEID::NEUTRAL_SPACEPLATFORMGEYSER: 113 | case UNIT_TYPEID::NEUTRAL_PROTOSSVESPENEGEYSER: 114 | case UNIT_TYPEID::NEUTRAL_PURIFIERVESPENEGEYSER: 115 | case UNIT_TYPEID::NEUTRAL_RICHVESPENEGEYSER: 116 | case UNIT_TYPEID::NEUTRAL_SHAKURASVESPENEGEYSER: 117 | return true; 118 | default: 119 | return false; 120 | } 121 | } 122 | 123 | bool IsStructure::operator()(const Unit& unit) { 124 | return isStructure(getUnitData(unit.unit_type)); 125 | } 126 | 127 | bool isStructure(const UnitTypeData& unitType) { 128 | auto& attributes = unitType.attributes; 129 | bool is_structure = false; 130 | for (const auto& attribute : attributes) { 131 | if (attribute == Attribute::Structure) { 132 | is_structure = true; 133 | } 134 | } 135 | return is_structure; 136 | } 137 | 138 | bool carriesResources(const Unit* unit) { 139 | // CARRYMINERALFIELDMINERALS = 271, 140 | // CARRYHIGHYIELDMINERALFIELDMINERALS = 272, 141 | // CARRYHARVESTABLEVESPENEGEYSERGAS = 273, 142 | // CARRYHARVESTABLEVESPENEGEYSERGASPROTOSS = 274, 143 | // CARRYHARVESTABLEVESPENEGEYSERGASZERG = 275, 144 | 145 | for (auto b : unit->buffs) { 146 | if (b >= 271 && b <= 275) return true; 147 | } 148 | return false; 149 | } 150 | 151 | bool isMelee(sc2::UNIT_TYPEID type) { 152 | switch(type) { 153 | case UNIT_TYPEID::PROTOSS_PROBE: 154 | case UNIT_TYPEID::PROTOSS_ZEALOT: 155 | case UNIT_TYPEID::PROTOSS_DARKTEMPLAR: 156 | case UNIT_TYPEID::TERRAN_SCV: 157 | case UNIT_TYPEID::TERRAN_HELLIONTANK: 158 | case UNIT_TYPEID::ZERG_DRONE: 159 | case UNIT_TYPEID::ZERG_ZERGLING: 160 | case UNIT_TYPEID::ZERG_ZERGLINGBURROWED: 161 | case UNIT_TYPEID::ZERG_BANELING: 162 | case UNIT_TYPEID::ZERG_BANELINGBURROWED: 163 | case UNIT_TYPEID::ZERG_ULTRALISK: 164 | case UNIT_TYPEID::ZERG_BROODLING: 165 | return true; 166 | default: 167 | return false; 168 | } 169 | } 170 | 171 | bool isInfantry(sc2::UNIT_TYPEID type) { 172 | switch(type) { 173 | case UNIT_TYPEID::TERRAN_MARINE: 174 | case UNIT_TYPEID::TERRAN_MARAUDER: 175 | case UNIT_TYPEID::TERRAN_REAPER: 176 | case UNIT_TYPEID::TERRAN_GHOST: 177 | case UNIT_TYPEID::TERRAN_HELLIONTANK: 178 | return true; 179 | default: 180 | return false; 181 | } 182 | } 183 | 184 | bool isChangeling(sc2::UNIT_TYPEID type) { 185 | switch(type) { 186 | case UNIT_TYPEID::ZERG_CHANGELING: 187 | case UNIT_TYPEID::ZERG_CHANGELINGMARINE: 188 | case UNIT_TYPEID::ZERG_CHANGELINGMARINESHIELD: 189 | case UNIT_TYPEID::ZERG_CHANGELINGZEALOT: 190 | case UNIT_TYPEID::ZERG_CHANGELINGZERGLING: 191 | case UNIT_TYPEID::ZERG_CHANGELINGZERGLINGWINGS: 192 | return true; 193 | default: 194 | return false; 195 | } 196 | } 197 | 198 | bool hasBuff (const Unit* unit, BUFF_ID buff) { 199 | for (auto b : unit->buffs) if (b == buff) return true; 200 | return false; 201 | } 202 | 203 | bool isUpgradeWithLevels(sc2::UPGRADE_ID upgrade) { 204 | switch(upgrade) { 205 | case sc2::UPGRADE_ID::TERRANINFANTRYWEAPONSLEVEL1: 206 | case sc2::UPGRADE_ID::TERRANINFANTRYARMORSLEVEL1: 207 | case sc2::UPGRADE_ID::TERRANVEHICLEWEAPONSLEVEL1: 208 | case sc2::UPGRADE_ID::TERRANSHIPWEAPONSLEVEL1: 209 | case sc2::UPGRADE_ID::PROTOSSGROUNDWEAPONSLEVEL1: 210 | case sc2::UPGRADE_ID::PROTOSSGROUNDARMORSLEVEL1: 211 | case sc2::UPGRADE_ID::PROTOSSSHIELDSLEVEL1: 212 | case sc2::UPGRADE_ID::ZERGMELEEWEAPONSLEVEL1: 213 | case sc2::UPGRADE_ID::ZERGGROUNDARMORSLEVEL1: 214 | case sc2::UPGRADE_ID::ZERGMISSILEWEAPONSLEVEL1: 215 | case sc2::UPGRADE_ID::ZERGFLYERWEAPONSLEVEL1: 216 | case sc2::UPGRADE_ID::ZERGFLYERARMORSLEVEL1: 217 | case sc2::UPGRADE_ID::PROTOSSAIRWEAPONSLEVEL1: 218 | case sc2::UPGRADE_ID::PROTOSSAIRARMORSLEVEL1: 219 | case sc2::UPGRADE_ID::TERRANVEHICLEANDSHIPARMORSLEVEL1: 220 | return true; 221 | default: 222 | return false; 223 | } 224 | } 225 | 226 | bool isTownHall(sc2::UNIT_TYPEID type) { 227 | switch (type) { 228 | case UNIT_TYPEID::ZERG_HATCHERY: 229 | case UNIT_TYPEID::ZERG_LAIR: 230 | case UNIT_TYPEID::ZERG_HIVE: 231 | case UNIT_TYPEID::TERRAN_COMMANDCENTER: 232 | case UNIT_TYPEID::TERRAN_ORBITALCOMMAND: 233 | case UNIT_TYPEID::TERRAN_ORBITALCOMMANDFLYING: 234 | case UNIT_TYPEID::TERRAN_PLANETARYFORTRESS: 235 | case UNIT_TYPEID::PROTOSS_NEXUS: 236 | return true; 237 | default: 238 | return false; 239 | } 240 | } 241 | 242 | bool isUpgradeDoneOrInProgress(const ObservationInterface* observation, UPGRADE_ID upgrade) { 243 | for (auto const i : observation->GetUpgrades()) { 244 | if (upgrade == i) { 245 | return true; 246 | } 247 | } 248 | 249 | const UpgradeData& data = getUpgradeData(upgrade); 250 | auto upgradeAbility = data.ability_id; 251 | 252 | for (auto const unit : observation->GetUnits(Unit::Alliance::Self)) { 253 | // Note: Upgrades with levels use generalized ability IDs 254 | if (!unit->orders.empty() && unit->orders[0].ability_id == upgradeAbility) { 255 | return true; 256 | } 257 | } 258 | return false; 259 | } 260 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/sc2_serialization.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace sc2 { 8 | template 9 | void serialize(Archive& archive, PlayerInfo& playerInfo) { 10 | archive( 11 | cereal::make_nvp("player_id", playerInfo.player_id), 12 | cereal::make_nvp("player_type", playerInfo.player_type), 13 | cereal::make_nvp("race_requested", playerInfo.race_requested), 14 | cereal::make_nvp("race_actual", playerInfo.race_actual), 15 | cereal::make_nvp("difficulty", playerInfo.difficulty) 16 | ); 17 | } 18 | 19 | template 20 | void serialize(Archive& archive, GameInfo& gameInfo) { 21 | archive( 22 | cereal::make_nvp("player_info", gameInfo.player_info), 23 | cereal::make_nvp("map_name", gameInfo.map_name), 24 | cereal::make_nvp("local_map_path", gameInfo.local_map_path), 25 | cereal::make_nvp("width", gameInfo.width), 26 | cereal::make_nvp("height", gameInfo.height), 27 | cereal::make_nvp("playable_min", gameInfo.playable_min), 28 | cereal::make_nvp("playable_max", gameInfo.playable_max) 29 | ); 30 | } 31 | 32 | template 33 | void serialize(Archive& archive, ReplayInfo& replayInfo) { 34 | archive( 35 | cereal::make_nvp("duration_gameloops", replayInfo.duration_gameloops), 36 | cereal::make_nvp("replay_path", replayInfo.replay_path), 37 | cereal::make_nvp("version", replayInfo.version), 38 | cereal::make_nvp("num_players", replayInfo.num_players), 39 | cereal::make_nvp("map_name", replayInfo.map_name), 40 | cereal::make_nvp("map_path", replayInfo.map_path) 41 | ); 42 | } 43 | 44 | template 45 | void serialize(Archive& archive, Point2D& p) { 46 | archive( 47 | cereal::make_nvp("x", p.x), 48 | cereal::make_nvp("y", p.y) 49 | ); 50 | } 51 | 52 | template 53 | void serialize(Archive& archive, Point3D& p) { 54 | archive( 55 | cereal::make_nvp("x", p.x), 56 | cereal::make_nvp("y", p.y), 57 | cereal::make_nvp("z", p.z) 58 | ); 59 | } 60 | 61 | 62 | template 63 | void serialize(Archive& archive, UnitOrder& unit) { 64 | ABILITY_ID ability_id = unit.ability_id; 65 | archive( 66 | cereal::make_nvp("ability_id", ability_id), 67 | cereal::make_nvp("target_unit_tag", unit.target_unit_tag), 68 | cereal::make_nvp("target_pos", unit.target_pos), 69 | cereal::make_nvp("progress", unit.progress) 70 | ); 71 | unit.ability_id = ability_id; 72 | } 73 | 74 | template 75 | void serialize(Archive& archive, PassengerUnit& unit) { 76 | UNIT_TYPEID unit_type = unit.unit_type; 77 | archive( 78 | cereal::make_nvp("tag", unit.tag), 79 | cereal::make_nvp("health", unit.health), 80 | cereal::make_nvp("health_max", unit.health_max), 81 | cereal::make_nvp("shield", unit.shield), 82 | cereal::make_nvp("shield_max", unit.shield_max), 83 | cereal::make_nvp("energy", unit.energy), 84 | cereal::make_nvp("energy_max", unit.energy_max), 85 | cereal::make_nvp("unit_type", unit_type) 86 | ); 87 | unit.unit_type = unit_type; 88 | } 89 | 90 | template 91 | void save(Archive& archive, const Unit& unit) { 92 | UNIT_TYPEID unit_type = unit.unit_type; 93 | std::vector buffs; 94 | for (auto b : unit.buffs) buffs.push_back(b); 95 | archive( 96 | cereal::make_nvp("display_type", unit.display_type), 97 | cereal::make_nvp("tag", unit.tag), 98 | cereal::make_nvp("unit_type", unit_type), 99 | cereal::make_nvp("owner", unit.owner), 100 | cereal::make_nvp("pos", unit.pos), 101 | cereal::make_nvp("facing", unit.facing), 102 | cereal::make_nvp("radius", unit.radius), 103 | cereal::make_nvp("build_progress", unit.build_progress), 104 | cereal::make_nvp("cloak", unit.cloak), 105 | cereal::make_nvp("detect_range", unit.detect_range), 106 | cereal::make_nvp("is_blip", unit.is_blip), 107 | cereal::make_nvp("health", unit.health), 108 | cereal::make_nvp("health_max", unit.health_max), 109 | cereal::make_nvp("shield", unit.shield), 110 | cereal::make_nvp("shield_max", unit.shield_max), 111 | cereal::make_nvp("energy", unit.energy), 112 | cereal::make_nvp("energy_max", unit.energy_max), 113 | cereal::make_nvp("mineral_contents", unit.mineral_contents), 114 | cereal::make_nvp("vespene_contents", unit.vespene_contents), 115 | cereal::make_nvp("is_flying", unit.is_flying), 116 | cereal::make_nvp("is_burrowed", unit.is_burrowed), 117 | cereal::make_nvp("weapon_cooldown", unit.weapon_cooldown), 118 | cereal::make_nvp("orders", unit.orders), 119 | cereal::make_nvp("add_on_tag", unit.add_on_tag), 120 | cereal::make_nvp("passengers", unit.passengers), 121 | cereal::make_nvp("cargo_space_taken", unit.cargo_space_taken), 122 | cereal::make_nvp("cargo_space_max", unit.cargo_space_max), 123 | cereal::make_nvp("engaged_target_tag", unit.engaged_target_tag), 124 | cereal::make_nvp("buffs", buffs), 125 | cereal::make_nvp("is_powered", unit.is_powered), 126 | cereal::make_nvp("is_alive", unit.is_alive), 127 | cereal::make_nvp("last_seen_game_loop", unit.last_seen_game_loop) 128 | ); 129 | } 130 | 131 | template 132 | void load(Archive& archive, Unit& unit) { 133 | UNIT_TYPEID unit_type = unit.unit_type; 134 | std::vector buffs; 135 | archive( 136 | cereal::make_nvp("display_type", unit.display_type), 137 | cereal::make_nvp("tag", unit.tag), 138 | cereal::make_nvp("unit_type", unit_type), 139 | cereal::make_nvp("owner", unit.owner), 140 | cereal::make_nvp("pos", unit.pos), 141 | cereal::make_nvp("facing", unit.facing), 142 | cereal::make_nvp("radius", unit.radius), 143 | cereal::make_nvp("build_progress", unit.build_progress), 144 | cereal::make_nvp("cloak", unit.cloak), 145 | cereal::make_nvp("detect_range", unit.detect_range), 146 | cereal::make_nvp("is_blip", unit.is_blip), 147 | cereal::make_nvp("health", unit.health), 148 | cereal::make_nvp("health_max", unit.health_max), 149 | cereal::make_nvp("shield", unit.shield), 150 | cereal::make_nvp("shield_max", unit.shield_max), 151 | cereal::make_nvp("energy", unit.energy), 152 | cereal::make_nvp("energy_max", unit.energy_max), 153 | cereal::make_nvp("mineral_contents", unit.mineral_contents), 154 | cereal::make_nvp("vespene_contents", unit.vespene_contents), 155 | cereal::make_nvp("is_flying", unit.is_flying), 156 | cereal::make_nvp("is_burrowed", unit.is_burrowed), 157 | cereal::make_nvp("weapon_cooldown", unit.weapon_cooldown), 158 | cereal::make_nvp("orders", unit.orders), 159 | cereal::make_nvp("add_on_tag", unit.add_on_tag), 160 | cereal::make_nvp("passengers", unit.passengers), 161 | cereal::make_nvp("cargo_space_taken", unit.cargo_space_taken), 162 | cereal::make_nvp("cargo_space_max", unit.cargo_space_max), 163 | cereal::make_nvp("engaged_target_tag", unit.engaged_target_tag), 164 | cereal::make_nvp("buffs", buffs), 165 | cereal::make_nvp("is_powered", unit.is_powered), 166 | cereal::make_nvp("is_alive", unit.is_alive), 167 | cereal::make_nvp("last_seen_game_loop", unit.last_seen_game_loop) 168 | ); 169 | 170 | unit.unit_type = unit_type; 171 | unit.buffs.clear(); 172 | for (auto b : buffs) unit.buffs.push_back(b); 173 | } 174 | 175 | template 176 | void save(Archive& archive, const UpgradeData& u) { 177 | ABILITY_ID ability_id = u.ability_id; 178 | archive( 179 | cereal::make_nvp("upgrade_id", u.upgrade_id), 180 | cereal::make_nvp("name", u.name), 181 | cereal::make_nvp("mineral_cost", u.mineral_cost), 182 | cereal::make_nvp("vespene_cost", u.vespene_cost), 183 | cereal::make_nvp("ability_id", ability_id), 184 | cereal::make_nvp("research_time", u.research_time) 185 | ); 186 | } 187 | 188 | template 189 | void load(Archive& archive, UpgradeData& u) { 190 | ABILITY_ID ability_id; 191 | archive( 192 | cereal::make_nvp("upgrade_id", u.upgrade_id), 193 | cereal::make_nvp("name", u.name), 194 | cereal::make_nvp("mineral_cost", u.mineral_cost), 195 | cereal::make_nvp("vespene_cost", u.vespene_cost), 196 | cereal::make_nvp("ability_id", ability_id), 197 | cereal::make_nvp("research_time", u.research_time) 198 | ); 199 | u.ability_id = ability_id; 200 | } 201 | }; 202 | -------------------------------------------------------------------------------- /libvoxelbot/combat/simulator.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | using namespace sc2; 8 | 9 | int combatWinner(const CombatPredictor& predictor, const CombatState& state) { 10 | return predictor.predict_engage(state).state.owner_with_best_outcome(); 11 | } 12 | 13 | const static double PI = 3.141592653589793238462643383279502884; 14 | 15 | // static void createState(DebugInterface* debug, const CombatState& state, float offset = 0) { 16 | // if (!debug) return; 17 | // for (auto& u : state.units) { 18 | // debug->DebugCreateUnit(u.type, Point2D(40 + 20 * (u.owner == 1 ? -1 : 1), 20 + offset), u.owner); 19 | // } 20 | // } 21 | 22 | void unitTestSurround() { 23 | // One unit can be surrounded by 6 melee units and attacked by all 6 at the same time 24 | assert(maxSurround(pow(unitRadius(UNIT_TYPEID::TERRAN_MARINE), 2) * PI * 1, 1).maxAttackersPerDefender == 6); 25 | assert(maxSurround(pow(unitRadius(UNIT_TYPEID::TERRAN_MARINE), 2) * PI * 1, 1).maxMeleeAttackers == 6); 26 | 27 | // Two units can be surrounded by 8 melee units, but each one can only be attacked by at most 4 28 | assert(maxSurround(pow(unitRadius(UNIT_TYPEID::TERRAN_MARINE), 2) * PI * 2, 2).maxAttackersPerDefender == 4); 29 | assert(maxSurround(pow(unitRadius(UNIT_TYPEID::TERRAN_MARINE), 2) * PI * 2, 2).maxMeleeAttackers == 8); 30 | 31 | // Two units can be surrounded by 9 melee units, but each one can only be attacked by at most 3 32 | assert(maxSurround(pow(unitRadius(UNIT_TYPEID::TERRAN_MARINE), 2) * PI * 3, 3).maxAttackersPerDefender == 3); 33 | assert(maxSurround(pow(unitRadius(UNIT_TYPEID::TERRAN_MARINE), 2) * PI * 3, 3).maxMeleeAttackers == 9); 34 | 35 | assert(maxSurround(pow(unitRadius(UNIT_TYPEID::TERRAN_MARINE), 2) * PI * 4, 4).maxAttackersPerDefender == 3); 36 | assert(maxSurround(pow(unitRadius(UNIT_TYPEID::TERRAN_MARINE), 2) * PI * 4, 4).maxMeleeAttackers == 10); 37 | 38 | // One thor can be attacked by 10 melee units at a time. 39 | // This seems to be slightly incorrect, the real number is only 9, but it's approximately correct at least 40 | assert(maxSurround(pow(unitRadius(UNIT_TYPEID::TERRAN_THOR), 2) * PI * 1, 1).maxAttackersPerDefender == 10); 41 | assert(maxSurround(pow(unitRadius(UNIT_TYPEID::TERRAN_THOR), 2) * PI * 1, 1).maxMeleeAttackers == 10); 42 | } 43 | 44 | int main() { 45 | initMappings(); 46 | CombatPredictor predictor; 47 | predictor.init(); 48 | unitTestSurround(); 49 | 50 | assert(combatWinner(predictor, {{ 51 | makeUnit(1, UNIT_TYPEID::TERRAN_VIKINGFIGHTER), 52 | makeUnit(2, UNIT_TYPEID::PROTOSS_COLOSSUS), 53 | }}) == 1); 54 | 55 | assert(combatWinner(predictor, {{ 56 | makeUnit(1, UNIT_TYPEID::PROTOSS_PYLON), 57 | makeUnit(1, UNIT_TYPEID::PROTOSS_PHOTONCANNON), 58 | makeUnit(1, UNIT_TYPEID::PROTOSS_SHIELDBATTERY), 59 | makeUnit(1, UNIT_TYPEID::PROTOSS_SHIELDBATTERY), 60 | makeUnit(1, UNIT_TYPEID::PROTOSS_SHIELDBATTERY), 61 | makeUnit(2, UNIT_TYPEID::TERRAN_MEDIVAC), 62 | makeUnit(2, UNIT_TYPEID::TERRAN_MARINE), 63 | }}) == 1); 64 | 65 | // Medivacs are pretty good (this is a very narrow win though) 66 | assert(combatWinner(predictor, {{ 67 | makeUnit(1, UNIT_TYPEID::TERRAN_MARINE), 68 | makeUnit(1, UNIT_TYPEID::TERRAN_MEDIVAC), 69 | makeUnit(2, UNIT_TYPEID::TERRAN_MARINE), 70 | makeUnit(2, UNIT_TYPEID::TERRAN_MARINE), 71 | }}) == 1); 72 | 73 | // 1 marine wins against 1 zergling 74 | assert(combatWinner(predictor, {{ 75 | CombatUnit(1, UNIT_TYPEID::TERRAN_MARINE, 50, false), 76 | CombatUnit(2, UNIT_TYPEID::ZERG_ZERGLING, 35, false), 77 | }}) == 1); 78 | 79 | // Symmetric 80 | assert(combatWinner(predictor, {{ 81 | CombatUnit(2, UNIT_TYPEID::TERRAN_MARINE, 50, false), 82 | CombatUnit(1, UNIT_TYPEID::ZERG_ZERGLING, 35, false), 83 | }}) == 2); 84 | 85 | // 4 marines win against 4 zerglings 86 | assert(combatWinner(predictor, {{ 87 | CombatUnit(1, UNIT_TYPEID::TERRAN_MARINE, 50, false), 88 | CombatUnit(1, UNIT_TYPEID::TERRAN_MARINE, 50, false), 89 | CombatUnit(1, UNIT_TYPEID::TERRAN_MARINE, 50, false), 90 | CombatUnit(1, UNIT_TYPEID::TERRAN_MARINE, 50, false), 91 | CombatUnit(2, UNIT_TYPEID::ZERG_ZERGLING, 35, false), 92 | CombatUnit(2, UNIT_TYPEID::ZERG_ZERGLING, 35, false), 93 | CombatUnit(2, UNIT_TYPEID::ZERG_ZERGLING, 35, false), 94 | CombatUnit(2, UNIT_TYPEID::ZERG_ZERGLING, 35, false), 95 | }}) == 1); 96 | 97 | assert(combatWinner(predictor, {{ 98 | CombatUnit(1, UNIT_TYPEID::TERRAN_MARINE, 50, false), 99 | CombatUnit(2, UNIT_TYPEID::ZERG_ZERGLING, 35, false), 100 | CombatUnit(2, UNIT_TYPEID::ZERG_ZERGLING, 35, false), 101 | CombatUnit(2, UNIT_TYPEID::ZERG_ZERGLING, 35, false), 102 | }}) == 2); 103 | 104 | assert(combatWinner(predictor, {{ 105 | makeUnit(1, UNIT_TYPEID::ZERG_SPORECRAWLER), 106 | makeUnit(1, UNIT_TYPEID::ZERG_SPORECRAWLER), 107 | makeUnit(1, UNIT_TYPEID::ZERG_SPORECRAWLER), 108 | makeUnit(2, UNIT_TYPEID::TERRAN_REAPER), 109 | makeUnit(2, UNIT_TYPEID::TERRAN_REAPER), 110 | makeUnit(2, UNIT_TYPEID::TERRAN_REAPER), 111 | }}) == 2); 112 | 113 | assert(combatWinner(predictor, {{ 114 | CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 115 | CombatUnit(2, UNIT_TYPEID::ZERG_BROODLORD, 225, true), 116 | CombatUnit(2, UNIT_TYPEID::ZERG_BROODLORD, 225, true), 117 | CombatUnit(2, UNIT_TYPEID::ZERG_BROODLORD, 225, true), 118 | CombatUnit(2, UNIT_TYPEID::ZERG_BROODLORD, 225, true), 119 | CombatUnit(2, UNIT_TYPEID::ZERG_BROODLORD, 225, true), 120 | }}) == 1); 121 | 122 | // TODO: Splash? 123 | // assert(combatWinner(predictor, {{ 124 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 125 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 126 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 127 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 128 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 129 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 130 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 131 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 132 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 133 | // CombatUnit(2, UNIT_TYPEID::ZERG_CORRUPTOR, 225, true), 134 | // CombatUnit(2, UNIT_TYPEID::ZERG_CORRUPTOR, 225, true), 135 | // CombatUnit(2, UNIT_TYPEID::ZERG_CORRUPTOR, 225, true), 136 | // CombatUnit(2, UNIT_TYPEID::ZERG_CORRUPTOR, 225, true), 137 | // CombatUnit(2, UNIT_TYPEID::ZERG_CORRUPTOR, 225, true), 138 | // CombatUnit(2, UNIT_TYPEID::ZERG_CORRUPTOR, 225, true), 139 | // CombatUnit(2, UNIT_TYPEID::ZERG_CORRUPTOR, 225, true), 140 | // CombatUnit(2, UNIT_TYPEID::ZERG_CORRUPTOR, 225, true), 141 | // CombatUnit(2, UNIT_TYPEID::ZERG_CORRUPTOR, 225, true), 142 | // }}) == 1); 143 | 144 | assert(combatWinner(predictor, {{ 145 | CombatUnit(1, UNIT_TYPEID::TERRAN_CYCLONE, 180, true), 146 | CombatUnit(2, UNIT_TYPEID::PROTOSS_IMMORTAL, 200, true), 147 | }}) == 2); 148 | 149 | assert(combatWinner(predictor, {{ 150 | makeUnit(1, UNIT_TYPEID::TERRAN_BATTLECRUISER), 151 | makeUnit(2, UNIT_TYPEID::TERRAN_THOR), 152 | }}) == 1); 153 | 154 | assert(combatWinner(predictor, {{ 155 | makeUnit(1, UNIT_TYPEID::ZERG_INFESTOR), 156 | makeUnit(2, UNIT_TYPEID::TERRAN_BANSHEE), 157 | }}) == 1); 158 | 159 | // Wins due to splash damage 160 | // Really depends on microing though... 161 | // assert(combatWinner(predictor, {{ 162 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 163 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 164 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 165 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 166 | // CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 167 | // CombatUnit(2, UNIT_TYPEID::ZERG_MUTALISK, 120, true), 168 | // CombatUnit(2, UNIT_TYPEID::ZERG_MUTALISK, 120, true), 169 | // CombatUnit(2, UNIT_TYPEID::ZERG_MUTALISK, 120, true), 170 | // CombatUnit(2, UNIT_TYPEID::ZERG_MUTALISK, 120, true), 171 | // CombatUnit(2, UNIT_TYPEID::ZERG_MUTALISK, 120, true), 172 | // CombatUnit(2, UNIT_TYPEID::ZERG_MUTALISK, 120, true), 173 | // CombatUnit(2, UNIT_TYPEID::ZERG_MUTALISK, 120, true), 174 | // CombatUnit(2, UNIT_TYPEID::ZERG_MUTALISK, 120, true), 175 | // CombatUnit(2, UNIT_TYPEID::ZERG_MUTALISK, 120, true), 176 | // }}) == 1); 177 | 178 | // Colossus can be attacked by air weapons 179 | assert(combatWinner(predictor, {{ 180 | CombatUnit(1, UNIT_TYPEID::TERRAN_LIBERATOR, 180, true), 181 | CombatUnit(2, UNIT_TYPEID::PROTOSS_COLOSSUS, 200, false), 182 | }}) == 1); 183 | 184 | // Do not assume all enemies will just target the most beefy unit and leave the banshee alone 185 | // while it takes out the hydras 186 | assert(combatWinner(predictor, {{ 187 | makeUnit(1, UNIT_TYPEID::ZERG_ROACH), 188 | makeUnit(1, UNIT_TYPEID::ZERG_ROACH), 189 | makeUnit(1, UNIT_TYPEID::ZERG_ROACH), 190 | makeUnit(1, UNIT_TYPEID::ZERG_ROACH), 191 | makeUnit(1, UNIT_TYPEID::ZERG_ROACH), 192 | makeUnit(1, UNIT_TYPEID::ZERG_ROACH), 193 | makeUnit(1, UNIT_TYPEID::ZERG_HYDRALISK), 194 | makeUnit(1, UNIT_TYPEID::ZERG_HYDRALISK), 195 | makeUnit(1, UNIT_TYPEID::ZERG_HYDRALISK), 196 | makeUnit(1, UNIT_TYPEID::ZERG_HYDRALISK), 197 | makeUnit(1, UNIT_TYPEID::ZERG_ZERGLING), 198 | makeUnit(1, UNIT_TYPEID::ZERG_ZERGLING), 199 | makeUnit(1, UNIT_TYPEID::ZERG_ZERGLING), 200 | makeUnit(1, UNIT_TYPEID::ZERG_ZERGLING), 201 | makeUnit(1, UNIT_TYPEID::ZERG_ZERGLING), 202 | makeUnit(1, UNIT_TYPEID::ZERG_ZERGLING), 203 | makeUnit(1, UNIT_TYPEID::ZERG_ZERGLING), 204 | makeUnit(2, UNIT_TYPEID::TERRAN_BANSHEE), 205 | makeUnit(2, UNIT_TYPEID::TERRAN_THOR), 206 | }}) == 1); 207 | 208 | string green = "\x1b[38;2;0;255;0m"; 209 | string reset = "\033[0m"; 210 | cout << green << "Ok" << reset << endl; 211 | return 0; 212 | } 213 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/unit_data_caching.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | using namespace sc2; 13 | 14 | void save_unit_data(const vector& unit_types, string path) { 15 | auto stream = ofstream(path); 16 | stream << unit_types.size() << endl; 17 | for (const UnitTypeData& type : unit_types) { 18 | stream << type.unit_type_id << "\n"; 19 | stream << '"' << type.name << "\"\n"; 20 | stream << type.available << "\n"; 21 | stream << type.cargo_size << "\n"; 22 | stream << type.mineral_cost << "\n"; 23 | stream << type.vespene_cost << "\n"; 24 | stream << type.attributes.size() << "\n"; 25 | for (auto a : type.attributes) { 26 | stream << (int)a << " "; 27 | } 28 | stream << endl; 29 | stream << type.movement_speed << "\n"; 30 | stream << type.armor << "\n"; 31 | 32 | stream << type.weapons.size() << endl; 33 | for (auto w : type.weapons) { 34 | stream << (int)w.type << " "; 35 | stream << w.damage_ << endl; 36 | stream << w.damage_bonus.size() << endl; 37 | for (auto b : w.damage_bonus) { 38 | stream << (int)b.attribute << " " << b.bonus; 39 | } 40 | stream << endl; 41 | stream << w.attacks << " "; 42 | stream << w.range << " "; 43 | stream << w.speed << endl; 44 | } 45 | stream << endl; 46 | 47 | stream << type.food_required << " "; 48 | stream << type.food_provided << " "; 49 | stream << type.ability_id << " "; 50 | stream << type.race << " "; 51 | stream << type.build_time << " "; 52 | stream << type.has_minerals << " "; 53 | stream << type.has_vespene << " "; 54 | stream << type.sight_range << " "; 55 | 56 | stream << type.tech_alias.size() << endl; 57 | for (auto a : type.tech_alias) { 58 | stream << a << " "; 59 | } 60 | stream << endl; 61 | 62 | stream << type.unit_alias << " "; 63 | stream << type.tech_requirement << " "; 64 | stream << type.require_attached << " "; 65 | stream << endl; 66 | stream << endl; 67 | } 68 | stream << endl; 69 | stream.close(); 70 | } 71 | 72 | std::vector load_unit_data() { 73 | // auto stream = ifstream(UNIT_DATA_CACHE_PATH); 74 | auto stream = stringstream(string((char*)LIBVOXELBOT_DATA_UNITS, LIBVOXELBOT_DATA_UNITS_SIZE)); 75 | if (!stream) { 76 | cerr << "Could not open unit data cache file at " << UNIT_DATA_CACHE_PATH << endl; 77 | exit(1); 78 | } 79 | int nUnits; 80 | stream >> nUnits; 81 | vector unit_types(nUnits); 82 | for (UnitTypeData& type : unit_types) { 83 | int type_id; 84 | stream >> type_id; 85 | type.unit_type_id = (UNIT_TYPEID)type_id; 86 | stream.ignore(numeric_limits::max(), '"'); 87 | getline(stream, type.name, '"'); 88 | stream >> type.available; 89 | stream >> type.cargo_size; 90 | stream >> type.mineral_cost; 91 | stream >> type.vespene_cost; 92 | int nAttributes; 93 | stream >> nAttributes; 94 | type.attributes = vector(nAttributes); 95 | for (auto& a : type.attributes) { 96 | int v; 97 | stream >> v; 98 | a = (Attribute)v; 99 | } 100 | stream >> type.movement_speed; 101 | stream >> type.armor; 102 | 103 | int nWeapons; 104 | stream >> nWeapons; 105 | type.weapons = vector(nWeapons); 106 | for (auto& w : type.weapons) { 107 | int typeI; 108 | stream >> typeI; 109 | w.type = (Weapon::TargetType)typeI; 110 | stream >> w.damage_; 111 | int nBonus; 112 | stream >> nBonus; 113 | w.damage_bonus = vector(nBonus); 114 | for (auto& b : w.damage_bonus) { 115 | int attr; 116 | stream >> attr >> b.bonus; 117 | b.attribute = (Attribute)attr; 118 | } 119 | stream >> w.attacks; 120 | stream >> w.range; 121 | stream >> w.speed; 122 | } 123 | 124 | stream >> type.food_required; 125 | stream >> type.food_provided; 126 | int id, race; 127 | stream >> id; 128 | type.ability_id = (ABILITY_ID)id; 129 | stream >> race; 130 | type.race = (Race)race; 131 | stream >> type.build_time; 132 | stream >> type.has_minerals; 133 | stream >> type.has_vespene; 134 | stream >> type.sight_range; 135 | 136 | int nTech; 137 | stream >> nTech; 138 | type.tech_alias = vector(nTech); 139 | for (auto& a : type.tech_alias) { 140 | int v; 141 | stream >> v; 142 | a = (UNIT_TYPEID)v; 143 | } 144 | 145 | int unit_alias; 146 | stream >> unit_alias; 147 | type.unit_alias = (UNIT_TYPEID)unit_alias; 148 | int tech_req; 149 | stream >> tech_req; 150 | type.tech_requirement = (UNIT_TYPEID)tech_req; 151 | stream >> type.require_attached; 152 | } 153 | //stream.close(); 154 | 155 | // Some sanity checks 156 | assert(unit_types[(int)UNIT_TYPEID::ZERG_OVERLORD].food_provided == 8); 157 | assert(unit_types[(int)UNIT_TYPEID::TERRAN_SCV].mineral_cost == 50); 158 | assert(unit_types[(int)UNIT_TYPEID::ZERG_ROACH].ability_id == ABILITY_ID::TRAIN_ROACH); 159 | return unit_types; 160 | } 161 | 162 | struct SerializableAbility { 163 | bool available; 164 | int ability_id; 165 | std::string link_name; 166 | uint32_t link_index; 167 | std::string button_name; 168 | std::string friendly_name; 169 | std::string hotkey; 170 | uint32_t remaps_to_ability_id; 171 | std::vector remaps_from_ability_id; 172 | AbilityData::Target target; 173 | bool allow_minimap; 174 | bool allow_autocast; 175 | bool is_building; 176 | float footprint_radius; 177 | bool is_instant_placement; 178 | float cast_range; 179 | 180 | template 181 | void serialize(Archive & archive) { 182 | archive( 183 | available, 184 | ability_id, 185 | link_name, 186 | link_index, 187 | button_name, 188 | friendly_name, 189 | hotkey, 190 | remaps_to_ability_id, 191 | remaps_from_ability_id, 192 | target, 193 | allow_minimap, 194 | allow_autocast, 195 | is_building, 196 | footprint_radius, 197 | is_instant_placement, 198 | cast_range 199 | ); 200 | } 201 | }; 202 | 203 | void save_ability_data(vector abilities) { 204 | vector abilities2; 205 | for (auto& a : abilities) { 206 | SerializableAbility a2; 207 | 208 | a2.available = a.available; 209 | a2.ability_id = a.ability_id; 210 | a2.link_name = a.link_name; 211 | a2.link_index = a.link_index; 212 | a2.button_name = a.button_name; 213 | a2.friendly_name = a.friendly_name; 214 | a2.hotkey = a.hotkey; 215 | a2.remaps_to_ability_id = a.remaps_to_ability_id; 216 | a2.remaps_from_ability_id = a.remaps_from_ability_id; 217 | a2.target = a.target; 218 | a2.allow_minimap = a.allow_minimap; 219 | a2.allow_autocast = a.allow_autocast; 220 | a2.is_building = a.is_building; 221 | a2.footprint_radius = a.footprint_radius; 222 | a2.is_instant_placement = a.is_instant_placement; 223 | a2.cast_range = a.cast_range; 224 | abilities2.push_back(a2); 225 | } 226 | 227 | std::ofstream file(ABILITY_DATA_CACHE_PATH, std::ios::binary); 228 | { 229 | cereal::BinaryOutputArchive archive(file); 230 | archive(abilities2); 231 | } 232 | file.close(); 233 | } 234 | 235 | std::vector load_ability_data() { 236 | auto file = stringstream(string((char*)LIBVOXELBOT_DATA_ABILITIES, LIBVOXELBOT_DATA_ABILITIES_SIZE)); 237 | // std::ifstream file(ABILITY_DATA_CACHE_PATH, std::ios::binary); 238 | if (!file) { 239 | cerr << "Could not open unit data cache file at " << ABILITY_DATA_CACHE_PATH << endl; 240 | exit(1); 241 | } 242 | 243 | vector abilities2; 244 | { 245 | cereal::BinaryInputArchive archive(file); 246 | archive(abilities2); 247 | } 248 | //file.close(); 249 | 250 | vector abilities; 251 | for (auto& a : abilities2) { 252 | AbilityData a2; 253 | 254 | a2.available = a.available; 255 | a2.ability_id = a.ability_id; 256 | a2.link_name = a.link_name; 257 | a2.link_index = a.link_index; 258 | a2.button_name = a.button_name; 259 | a2.friendly_name = a.friendly_name; 260 | a2.hotkey = a.hotkey; 261 | a2.remaps_to_ability_id = a.remaps_to_ability_id; 262 | a2.remaps_from_ability_id = a.remaps_from_ability_id; 263 | a2.target = a.target; 264 | a2.allow_minimap = a.allow_minimap; 265 | a2.allow_autocast = a.allow_autocast; 266 | a2.is_building = a.is_building; 267 | a2.footprint_radius = a.footprint_radius; 268 | a2.is_instant_placement = a.is_instant_placement; 269 | a2.cast_range = a.cast_range; 270 | abilities.push_back(a2); 271 | } 272 | 273 | return abilities; 274 | } 275 | 276 | void save_upgrade_data(vector upgrades) { 277 | std::ofstream file(UPGRADE_DATA_CACHE_PATH, std::ios::binary); 278 | { 279 | cereal::BinaryOutputArchive archive(file); 280 | archive(upgrades); 281 | } 282 | file.close(); 283 | } 284 | 285 | std::vector load_upgrade_data() { 286 | auto file = stringstream(string((char*)LIBVOXELBOT_DATA_UPGRADES, LIBVOXELBOT_DATA_UPGRADES_SIZE)); 287 | // std::ifstream file(UPGRADE_DATA_CACHE_PATH, std::ios::binary); 288 | if (!file) { 289 | cerr << "Could not open upgrade data cache file at " << UPGRADE_DATA_CACHE_PATH << endl; 290 | exit(1); 291 | } 292 | 293 | vector upgrades; 294 | { 295 | cereal::BinaryInputArchive archive(file); 296 | archive(upgrades); 297 | } 298 | //file.close(); 299 | return upgrades; 300 | } 301 | -------------------------------------------------------------------------------- /libvoxelbot/buildorder/build_state.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "sc2api/sc2_interfaces.h" 6 | #include 7 | #include 8 | #include 9 | 10 | struct BuildState; 11 | struct BuildOrderTracker; 12 | 13 | struct BuildResources { 14 | float minerals; 15 | float vespene; 16 | 17 | BuildResources(float minerals, float vespene) 18 | : minerals(minerals), vespene(vespene) {} 19 | }; 20 | 21 | struct MiningSpeed { 22 | float mineralsPerSecond; 23 | float vespenePerSecond; 24 | 25 | /** Simulate mining with the given speed for the given amount of time */ 26 | void simulateMining (BuildState& state, float dt) const; 27 | 28 | inline bool operator== (const MiningSpeed& other) const { 29 | return mineralsPerSecond == other.mineralsPerSecond && vespenePerSecond == other.vespenePerSecond; 30 | } 31 | }; 32 | 33 | struct BuildUnitInfo { 34 | /** Type of the unit */ 35 | sc2::UNIT_TYPEID type; 36 | 37 | /** Optional addon attached to the unit (or UNIT_TYPEID::INVALID otherwise) */ 38 | sc2::UNIT_TYPEID addon; 39 | 40 | /** Total number of units */ 41 | int units; 42 | 43 | /** How many units out of the total that are busy right now. 44 | * E.g. with constructing a building, training a unit, etc. 45 | * Technically this is the number of casting slots for abilities rather than the number of units, 46 | * in case the building has a reactor the number of busy units can be up to two times the number of units because each unit can train two units at the same time. 47 | */ 48 | int busyUnits; 49 | 50 | BuildUnitInfo() 51 | : type(sc2::UNIT_TYPEID::INVALID), addon(sc2::UNIT_TYPEID::INVALID), units(0), busyUnits(0) {} 52 | BuildUnitInfo(sc2::UNIT_TYPEID type, sc2::UNIT_TYPEID addon, int units) 53 | : type(type), addon(addon), units(units), busyUnits(0) {} 54 | 55 | /** Number of units that are available for casting abilities */ 56 | inline int availableUnits() const { 57 | if (addon == sc2::UNIT_TYPEID::TERRAN_REACTOR) { 58 | return units - busyUnits / 2; 59 | } else { 60 | return units - busyUnits; 61 | } 62 | } 63 | }; 64 | 65 | enum BuildEventType { 66 | FinishedUnit, 67 | SpawnLarva, 68 | MuleTimeout, 69 | MakeUnitAvailable, // Un-busy unit 70 | FinishedUpgrade, 71 | WarpGateTransition, 72 | }; 73 | 74 | struct BuildEvent { 75 | /** Type of event */ 76 | BuildEventType type; 77 | /** The ability that is being cast */ 78 | sc2::ABILITY_ID ability; 79 | /** Unit type of the caster of the ability */ 80 | sc2::UNIT_TYPEID caster; 81 | /** Addon unit type of the caster of the ability (if any) */ 82 | sc2::UNIT_TYPEID casterAddon; 83 | /** Time at which this event will happen */ 84 | float time; 85 | float chronoEndTime = 0; 86 | 87 | BuildEvent(BuildEventType type, float time, sc2::UNIT_TYPEID caster, sc2::ABILITY_ID ability) 88 | : type(type), ability(ability), caster(caster), casterAddon(sc2::UNIT_TYPEID::INVALID), time(time) {} 89 | 90 | explicit BuildEvent() {} 91 | 92 | /** True if this event may have an impact on food or mining speed */ 93 | bool impactsEconomy() const; 94 | 95 | /** Applies the effects of this event on the given state */ 96 | void apply(BuildState& state) const; 97 | 98 | inline bool operator<(const BuildEvent& other) const { 99 | return time < other.time; 100 | } 101 | }; 102 | 103 | 104 | struct BaseInfo { 105 | float remainingMinerals = 0; 106 | float remainingVespene1 = 0; 107 | float remainingVespene2 = 0; 108 | 109 | explicit BaseInfo (float minerals, float vespene1, float vespene2) : remainingMinerals(minerals), remainingVespene1(vespene1), remainingVespene2(vespene2) {} 110 | explicit BaseInfo() {} 111 | 112 | inline void mineMinerals(float amount) { 113 | remainingMinerals = fmax(0.0f, remainingMinerals - amount); 114 | } 115 | 116 | /** Returns (high yield, low yield) mineral slots on this expansion */ 117 | inline std::pair mineralSlots () const { 118 | // Max is 10800 for an expansion with 8 patches 119 | if (remainingMinerals > 4800) return {16, 8}; 120 | if (remainingMinerals > 4000) return {12, 6}; 121 | if (remainingMinerals > 100) return {8, 4}; 122 | if (remainingMinerals > 0) return {2, 1}; 123 | return {0, 0}; 124 | } 125 | }; 126 | 127 | 128 | const float NexusEnergyPerSecond = 1; 129 | const float ChronoBoostEnergy = 50; 130 | const float NexusInitialEnergy = 50; 131 | const float ChronoBoostDuration = 20; 132 | 133 | 134 | struct ChronoBoostInfo { 135 | std::vector energyOffsets; 136 | std::vector> chronoEndTimes; 137 | 138 | std::pair getChronoBoostEndTime(sc2::UNIT_TYPEID caster, float currentTime); 139 | 140 | std::pair useChronoBoost(float time); 141 | 142 | void addRemainingChronoBoost(sc2::UNIT_TYPEID caster, float endTime) { 143 | chronoEndTimes.emplace_back(caster, endTime); 144 | } 145 | 146 | void addNexusWithEnergy(float currentTime, float energy) { 147 | energyOffsets.push_back(energy - currentTime * NexusEnergyPerSecond); 148 | } 149 | }; 150 | 151 | /** Represents all units, buildings and current build/train actions that are in progress for a given player */ 152 | struct BuildState { 153 | /** Time in game time seconds at normal speed */ 154 | float time = 0; 155 | /** Race of the player */ 156 | sc2::Race race = sc2::Race::Terran; 157 | 158 | /** All units in the current state */ 159 | std::vector units; 160 | /** All future events, sorted in ascending order by their time */ 161 | std::vector events; 162 | /** Current resources */ 163 | BuildResources resources = BuildResources(0,0); 164 | /** Metadata (in particular resource info) about the bases that the player has */ 165 | std::vector baseInfos; 166 | 167 | ChronoBoostInfo chronoInfo; 168 | CombatUpgrades upgrades; 169 | 170 | private: 171 | mutable uint64_t cachedHash = 0; 172 | public: 173 | 174 | BuildState() {} 175 | explicit BuildState(std::vector> unitCounts); 176 | explicit BuildState(const sc2::ObservationInterface* observation, sc2::Unit::Alliance alliance, sc2::Race race, BuildResources resources, float time); 177 | 178 | uint64_t hash() const { 179 | cachedHash = 0; 180 | return immutableHash(); 181 | } 182 | 183 | void recalculateHash() { 184 | cachedHash = 0; 185 | immutableHash(); 186 | } 187 | 188 | void transitionToWarpgates (const std::function* eventCallback); 189 | 190 | /** Returns the hash of the build state. 191 | * Note: Assumes the object is immutable, the hash is cached the first time the method is called 192 | */ 193 | uint64_t immutableHash() const { 194 | if (cachedHash == 0) { 195 | uint64_t h = 0; 196 | h = h*31 ^ (int)(time * 1000); 197 | h = h*31 ^ (int)(resources.minerals * 1000); 198 | h = h*31 ^ (int)(resources.vespene * 1000); 199 | h = h*31 ^ (int)(race); 200 | for (auto& u : units) { 201 | h = h*31 ^ (int)u.type; 202 | h = h*31 ^ (int)u.addon; 203 | h = h*31 ^ (int)u.busyUnits; 204 | h = h*31 ^ (int)u.units; 205 | } 206 | for (auto& s : baseInfos) { 207 | h = h*31 ^ ((int)s.remainingMinerals); 208 | h = h*31 ^ ((int)s.remainingVespene1); 209 | h = h*31 ^ ((int)s.remainingVespene2); 210 | } 211 | for (auto& e : events) { 212 | h = h*31 ^ ((int)e.ability); 213 | h = h*31 ^ ((int)e.caster); 214 | h = h*31 ^ ((int)e.casterAddon); 215 | h = h*31 ^ ((int)e.time); 216 | h = h*31 ^ ((int)e.type); // Type is really implied by the ability, but oh well 217 | } 218 | cachedHash = h; 219 | } 220 | return cachedHash; 221 | } 222 | 223 | /** Marks a number of units with the given type (and optionally addon) as being busy. 224 | * Delta may be negative to indicate that the units should be made available again after having been busy. 225 | * 226 | * If this action could not be performed (e.g. there were no non-busy units that could be made busy) then the function will panic. 227 | */ 228 | void makeUnitsBusy(sc2::UNIT_TYPEID type, sc2::UNIT_TYPEID addon, int delta); 229 | 230 | void addUnits(sc2::UNIT_TYPEID type, int delta); 231 | 232 | /** Adds a number of given unit type (with optional addon) to the state. 233 | * Delta may be negative to indicate that units should be removed. 234 | * 235 | * If this action could not be performed (e.g. there were no units that could be removed) then this function will panic. 236 | */ 237 | void addUnits(sc2::UNIT_TYPEID type, sc2::UNIT_TYPEID addon, int delta); 238 | void killUnits(sc2::UNIT_TYPEID type, sc2::UNIT_TYPEID addon, int count); 239 | 240 | /** Returns the current mining speed of (minerals,vespene gas) per second (at normal game speed) */ 241 | MiningSpeed miningSpeed() const; 242 | 243 | /** Returns the time it will take to get the specified resources using the given mining speed */ 244 | float timeToGetResources(MiningSpeed miningSpeed, float mineralCost, float vespeneCost) const; 245 | 246 | /** Adds a new future event to the state */ 247 | void addEvent(BuildEvent event); 248 | 249 | /** Simulate the state until a given point in time. 250 | * All actions up to and including the end time will have been completed after the function has been called. 251 | * This will update the current resources using the simulated mining speed. 252 | */ 253 | void simulate(float endTime, const std::function* eventCallback = nullptr); 254 | 255 | /** Simulates the execution of a given build order. 256 | * The function will return false if the build order could not be performed. 257 | * The resulting state is the state right after the final item in the build order has been completed. 258 | * 259 | * The optional callback will be called once exactly when build order item number N is executed, with N as the parameter. 260 | * Note that the this is when the item starts to be executed, not when the item is finished. 261 | * The callback is called right after the action has been executed, but not necessarily completed. 262 | */ 263 | bool simulateBuildOrder(const BuildOrder& buildOrder, const std::function = nullptr, bool waitUntilItemsFinished = true); 264 | bool simulateBuildOrder(BuildOrderState& buildOrder, const std::function callback, bool waitUntilItemsFinished, float maxTime = std::numeric_limits::infinity(), const std::function* eventCallback = nullptr); 265 | 266 | float foodCap() const; 267 | 268 | /** Food that is currently available. 269 | * Positive if there is a surplus of food. 270 | * Note that food is a floating point number, zerglings in particular use 0.5 food. 271 | * It is still safe to work with floating point numbers because they can exactly represent whole numbers and whole numbers + 0.5 exactly up to very large values. 272 | */ 273 | float foodAvailable() const; 274 | 275 | /** Food that will be available when following the currents event into the future */ 276 | float foodAvailableInFuture() const; 277 | 278 | /** True if the state contains the given unit type or which is equivalent to the given unit type for tech purposes */ 279 | bool hasEquivalentTech(sc2::UNIT_TYPEID type) const; 280 | }; 281 | -------------------------------------------------------------------------------- /libvoxelbot/utilities/renderer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | namespace { 13 | 14 | SDL_Rect CreateRect(int x, int y, int w, int h) { 15 | SDL_Rect r; 16 | r.x = x; 17 | r.y = y; 18 | r.w = w; 19 | r.h = h; 20 | return r; 21 | } 22 | } // namespace 23 | 24 | struct Pixel { 25 | uint8_t b, g, r; 26 | }; 27 | 28 | static vector colormap = { { 4, 0, 0 }, { 5, 0, 1 }, { 6, 1, 1 }, { 8, 1, 1 }, { 10, 1, 2 }, { 12, 2, 2 }, { 14, 2, 2 }, { 16, 2, 3 }, { 18, 3, 4 }, { 21, 3, 4 }, { 23, 4, 5 }, { 25, 4, 6 }, { 27, 5, 7 }, { 29, 6, 8 }, { 32, 6, 9 }, { 34, 7, 10 }, { 36, 7, 11 }, { 38, 8, 12 }, { 41, 8, 13 }, { 43, 9, 14 }, { 45, 9, 16 }, { 48, 10, 17 }, { 50, 10, 18 }, { 53, 11, 20 }, { 55, 11, 21 }, { 58, 11, 22 }, { 60, 12, 24 }, { 62, 12, 25 }, { 65, 12, 27 }, { 67, 12, 28 }, { 70, 12, 30 }, { 72, 12, 31 }, { 74, 12, 33 }, { 77, 12, 35 }, { 79, 12, 36 }, { 81, 12, 38 }, { 83, 11, 40 }, { 85, 11, 42 }, { 87, 11, 43 }, { 89, 11, 45 }, { 91, 10, 47 }, { 93, 10, 49 }, { 94, 10, 51 }, { 96, 10, 52 }, { 97, 9, 54 }, { 98, 9, 56 }, { 99, 9, 58 }, { 100, 9, 59 }, { 101, 9, 61 }, { 102, 9, 63 }, { 103, 10, 64 }, { 104, 10, 66 }, { 105, 10, 68 }, { 105, 10, 69 }, { 106, 11, 71 }, { 107, 11, 73 }, { 107, 12, 74 }, { 108, 12, 76 }, { 108, 13, 78 }, { 108, 13, 79 }, { 109, 14, 81 }, { 109, 14, 83 }, { 109, 15, 84 }, { 110, 15, 86 }, { 110, 16, 87 }, { 110, 17, 89 }, { 110, 17, 91 }, { 110, 18, 92 }, { 111, 18, 94 }, { 111, 19, 95 }, { 111, 20, 97 }, { 111, 20, 99 }, { 111, 21, 100 }, { 111, 21, 102 }, { 111, 22, 103 }, { 111, 23, 105 }, { 111, 23, 107 }, { 111, 24, 108 }, { 111, 24, 110 }, { 111, 25, 111 }, { 110, 25, 113 }, { 110, 26, 115 }, { 110, 27, 116 }, { 110, 27, 118 }, { 110, 28, 119 }, { 110, 28, 121 }, { 109, 29, 123 }, { 109, 29, 124 }, { 109, 30, 126 }, { 109, 31, 127 }, { 108, 31, 129 }, { 108, 32, 130 }, { 108, 32, 132 }, { 107, 33, 134 }, { 107, 33, 135 }, { 107, 34, 137 }, { 106, 34, 138 }, { 106, 35, 140 }, { 105, 36, 142 }, { 105, 36, 143 }, { 105, 37, 145 }, { 104, 37, 146 }, { 104, 38, 148 }, { 103, 38, 150 }, { 102, 39, 151 }, { 102, 40, 153 }, { 101, 40, 154 }, { 101, 41, 156 }, { 100, 41, 158 }, { 100, 42, 159 }, { 99, 43, 161 }, { 98, 43, 162 }, { 98, 44, 164 }, { 97, 45, 165 }, { 96, 45, 167 }, { 95, 46, 169 }, { 95, 46, 170 }, { 94, 47, 172 }, { 93, 48, 173 }, { 92, 49, 175 }, { 92, 49, 176 }, { 91, 50, 178 }, { 90, 51, 179 }, { 89, 51, 181 }, { 88, 52, 182 }, { 87, 53, 184 }, { 86, 54, 185 }, { 85, 54, 187 }, { 85, 55, 188 }, { 84, 56, 190 }, { 83, 57, 191 }, { 82, 58, 193 }, { 81, 59, 194 }, { 80, 60, 196 }, { 79, 60, 197 }, { 78, 61, 198 }, { 77, 62, 200 }, { 76, 63, 201 }, { 75, 64, 203 }, { 74, 65, 204 }, { 72, 66, 205 }, { 71, 67, 207 }, { 70, 68, 208 }, { 69, 69, 209 }, { 68, 70, 211 }, { 67, 72, 212 }, { 66, 73, 213 }, { 65, 74, 214 }, { 64, 75, 216 }, { 62, 76, 217 }, { 61, 77, 218 }, { 60, 79, 219 }, { 59, 80, 220 }, { 58, 81, 221 }, { 57, 82, 223 }, { 56, 84, 224 }, { 54, 85, 225 }, { 53, 86, 226 }, { 52, 88, 227 }, { 51, 89, 228 }, { 50, 90, 229 }, { 48, 92, 230 }, { 47, 93, 231 }, { 46, 95, 232 }, { 45, 96, 233 }, { 43, 98, 234 }, { 42, 99, 235 }, { 41, 101, 235 }, { 40, 102, 236 }, { 38, 104, 237 }, { 37, 105, 238 }, { 36, 107, 239 }, { 35, 109, 240 }, { 33, 110, 240 }, { 32, 112, 241 }, { 31, 113, 242 }, { 30, 115, 242 }, { 28, 117, 243 }, { 27, 118, 244 }, { 26, 120, 244 }, { 24, 122, 245 }, { 23, 123, 246 }, { 22, 125, 246 }, { 20, 127, 247 }, { 19, 129, 247 }, { 18, 130, 248 }, { 16, 132, 248 }, { 15, 134, 249 }, { 14, 136, 249 }, { 12, 137, 249 }, { 11, 139, 250 }, { 10, 141, 250 }, { 9, 143, 250 }, { 8, 145, 251 }, { 7, 146, 251 }, { 7, 148, 251 }, { 6, 150, 252 }, { 6, 152, 252 }, { 6, 154, 252 }, { 6, 156, 252 }, { 7, 158, 252 }, { 7, 160, 253 }, { 8, 161, 253 }, { 9, 163, 253 }, { 10, 165, 253 }, { 12, 167, 253 }, { 13, 169, 253 }, { 15, 171, 253 }, { 17, 173, 253 }, { 19, 175, 253 }, { 20, 177, 253 }, { 22, 179, 253 }, { 24, 181, 253 }, { 27, 183, 252 }, { 29, 185, 252 }, { 31, 186, 252 }, { 33, 188, 252 }, { 35, 190, 252 }, { 38, 192, 251 }, { 40, 194, 251 }, { 43, 196, 251 }, { 45, 198, 251 }, { 48, 200, 250 }, { 50, 202, 250 }, { 53, 204, 250 }, { 56, 206, 249 }, { 58, 208, 249 }, { 61, 210, 248 }, { 64, 212, 248 }, { 67, 214, 247 }, { 70, 216, 247 }, { 73, 218, 246 }, { 76, 220, 246 }, { 80, 222, 245 }, { 83, 224, 245 }, { 86, 226, 244 }, { 90, 228, 244 }, { 94, 229, 244 }, { 97, 231, 243 }, { 101, 233, 243 }, { 105, 235, 243 }, { 109, 237, 242 }, { 113, 238, 242 }, { 117, 240, 242 }, { 122, 241, 242 }, { 126, 243, 243 }, { 130, 244, 243 }, { 134, 246, 244 }, { 138, 247, 244 }, { 142, 249, 245 }, { 146, 250, 246 }, { 150, 251, 247 }, { 154, 252, 249 }, { 158, 253, 250 }, { 162, 254, 251 }, { 165, 255, 253 } }; 29 | 30 | MapRenderer::MapRenderer(const char* title, int x, int y, int w, int h, unsigned int flags) { 31 | int init_result = SDL_Init(SDL_INIT_VIDEO); 32 | if (init_result) { 33 | const char* error = SDL_GetError(); 34 | std::cerr << "SDL_Init failed with error: " << error << std::endl; 35 | exit(1); 36 | } 37 | 38 | window = SDL_CreateWindow(title, x, y, w, h, flags == 0 ? SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE : flags); 39 | assert(window); 40 | 41 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); 42 | assert(renderer); 43 | 44 | // Clear window to black. 45 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); 46 | SDL_RenderClear(renderer); 47 | } 48 | 49 | void MapRenderer::shutdown() { 50 | SDL_DestroyRenderer(renderer); 51 | SDL_DestroyWindow(window); 52 | SDL_Quit(); 53 | window = nullptr; 54 | renderer = nullptr; 55 | } 56 | 57 | const static int InfluenceMapScale = 2; 58 | 59 | void MapRenderer::renderInfluenceMap(const InfluenceMap& influenceMap, int x0, int y0) { 60 | renderImageGrayscale((double*)influenceMap.weights.data(), influenceMap.w, influenceMap.h, InfluenceMapScale * x0 * (influenceMap.w + 5), InfluenceMapScale * y0 * (influenceMap.h + 5), InfluenceMapScale, false); 61 | } 62 | 63 | void MapRenderer::renderInfluenceMapNormalized(const InfluenceMap& influenceMap, int x0, int y0) { 64 | renderImageGrayscale((double*)influenceMap.weights.data(), influenceMap.w, influenceMap.h, InfluenceMapScale * x0 * (influenceMap.w + 5), InfluenceMapScale * y0 * (influenceMap.h + 5), InfluenceMapScale, true); 65 | } 66 | 67 | void MapRenderer::renderMatrix1BPP(const char* bytes, int w_mat, int h_mat, int off_x, int off_y, int px_w, int px_h) { 68 | if (!window) return; 69 | assert(renderer); 70 | assert(window); 71 | 72 | SDL_Rect rect = CreateRect(0, 0, px_w, px_h); 73 | for (int y = 0; y < h_mat; ++y) { 74 | for (int x = 0; x < w_mat; ++x) { 75 | rect.x = off_x + (int(x) * rect.w); 76 | rect.y = off_y + (int(y) * rect.h); 77 | 78 | int index = x + y * w_mat; 79 | unsigned char mask = 1 << (7 - (index % 8)); 80 | unsigned char data = bytes[index / 8]; 81 | bool value = (data & mask) != 0; 82 | 83 | if (value) 84 | SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); 85 | else 86 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); 87 | 88 | SDL_RenderFillRect(renderer, &rect); 89 | } 90 | } 91 | } 92 | 93 | void MapRenderer::renderMatrix8BPPHeightMap(const char* bytes, int w_mat, int h_mat, int off_x, int off_y, int px_w, int px_h) { 94 | if (!window) return; 95 | assert(renderer); 96 | assert(window); 97 | 98 | SDL_Rect rect = CreateRect(0, 0, px_w, px_h); 99 | for (int y = 0; y < h_mat; ++y) { 100 | for (int x = 0; x < w_mat; ++x) { 101 | rect.x = off_x + (int(x) * rect.w); 102 | rect.y = off_y + (int(y) * rect.h); 103 | 104 | // Renders the height map in grayscale [0-255] 105 | int index = x + y * w_mat; 106 | SDL_SetRenderDrawColor(renderer, bytes[index], bytes[index], bytes[index], 255); 107 | SDL_RenderFillRect(renderer, &rect); 108 | } 109 | } 110 | } 111 | 112 | void MapRenderer::renderMatrix8BPPPlayers(const char* bytes, int w_mat, int h_mat, int off_x, int off_y, int px_w, int px_h) { 113 | if (!window) return; 114 | assert(renderer); 115 | assert(window); 116 | 117 | SDL_Rect rect = CreateRect(0, 0, px_w, px_h); 118 | for (int y = 0; y < h_mat; ++y) { 119 | for (int x = 0; x < w_mat; ++x) { 120 | rect.x = off_x + (int(x) * rect.w); 121 | rect.y = off_y + (int(y) * rect.h); 122 | 123 | int index = x + y * w_mat; 124 | switch (bytes[index]) { 125 | case 0: 126 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); 127 | break; 128 | case 1: 129 | // Self. 130 | SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); 131 | break; 132 | case 2: 133 | // Enemy. 134 | SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); 135 | break; 136 | case 3: 137 | // Neutral. 138 | SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); 139 | break; 140 | case 4: 141 | SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); 142 | break; 143 | case 5: 144 | SDL_SetRenderDrawColor(renderer, 0, 255, 255, 255); 145 | break; 146 | default: 147 | SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); 148 | } 149 | SDL_RenderFillRect(renderer, &rect); 150 | } 151 | } 152 | } 153 | 154 | void MapRenderer::renderImageRGB(const char* bytes, int width, int height, int off_x, int off_y, int scale) { 155 | if (!window) return; 156 | assert(renderer); 157 | assert(window); 158 | 159 | SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void*)bytes, width, height, 24, 3 * width, 0xFF0000, 0x00FF00, 0x0000FF, 0x000000); 160 | SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); 161 | SDL_FreeSurface(surface); 162 | 163 | SDL_Rect dstRect = CreateRect(off_x, off_y, width * scale, height * scale); 164 | SDL_RenderCopyEx(renderer, texture, NULL, &dstRect, 0, NULL, SDL_FLIP_VERTICAL); 165 | 166 | SDL_DestroyTexture(texture); 167 | } 168 | 169 | static vector converted; 170 | void MapRenderer::renderImageGrayscale(const double* values, int width, int height, int off_x, int off_y, int scale, bool normalized) { 171 | if (!window) return; 172 | converted.resize(width * height); 173 | double multiplier = normalized ? 256.0 : 256.0 / 3.0; 174 | for (int i = 0; i < width * height; i++) { 175 | if (isfinite(values[i])) { 176 | converted[i] = colormap[max(min((int)(values[i] * multiplier), 255), 0)]; 177 | } else { 178 | if (values[i] < 0) { 179 | // Neg inf 180 | converted[i] = { 0, 0, 200 }; 181 | } else if (values[i] > 0) { 182 | // Pos inf 183 | converted[i] = { 200, 0, 0 }; 184 | } else { 185 | // NaN 186 | converted[i] = { 255, 0, 255 }; 187 | } 188 | } 189 | } 190 | 191 | // Make a border 192 | for (int x = 0; x < width; x++) { 193 | converted[0 * width + x] = converted[width * (height - 1) + x] = { 200, 200, 200 }; 194 | } 195 | for (int y = 0; y < height; y++) { 196 | converted[y * width + 0] = converted[width * y + width - 1] = { 200, 200, 200 }; 197 | } 198 | 199 | renderImageRGB((const char*)converted.data(), width, height, off_x, off_y, scale); 200 | } 201 | 202 | void MapRenderer::present() { 203 | if (!window) return; 204 | assert(renderer); 205 | assert(window); 206 | SDL_Event e; 207 | while (SDL_PollEvent(&e)) { 208 | if (e.type == SDL_QUIT) { 209 | shutdown(); 210 | return; 211 | } 212 | } 213 | 214 | SDL_RenderPresent(renderer); 215 | SDL_RenderClear(renderer); 216 | } 217 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # libvoxelbot 2 | 3 | 4 | This library contains several algorithms that are useful when building bots for Starcraft 2. 5 | 6 | It contains a build order simulator, a build order optimizer, a combat simulator, influence maps, many useful predicates and unit data lookups. 7 | 8 | ## Combat Simulator 9 | 10 | The combat simulator can simulate battles between units of all SC2 races. 11 | It properly takes into account things like: 12 | 13 | - Damage bonuses (e.g. Immortal bonus against armored units). 14 | - Melee units take a longer time to reach the enemy. 15 | - Not an infinite number of melee units can attack the same unit at the same time. 16 | - Damage/armor bonuses due to upgrades. 17 | - A few special cased upgrades like: Resonating Glaives, Charge, Extended Thermal Lance.. 18 | - Healing units like Medivacs and Shield Batteries as well as some shielding units like the Sentry. 19 | - Melee units typically block enemy melee units from attacking the ranged units further back. 20 | - The range of units is properly taken into account when the engagement starts. For example siege tanks can start to attack much earlier than roaches as tanks have a much higher range. 21 | - Splash damage is approximated ok-ish. 22 | - It assumes that units do decent target firing. 23 | 24 | It does *not* take the following effects into account: 25 | 26 | - Terrain effects like choke points and low/high ground. 27 | - Unit positions. Many effects that relate to unit positions are approximated, but the exact values are not used. 28 | - Bunkers and other cases where units can be inside another unit. 29 | - Kiting. 30 | - More advanced abilities like Psi Storm and many others. 31 | 32 | The simulator is pretty fast. It can simulate on the order of tens of thousands of battles per second. The performance does of course depend on the number of units in the fight and how long the fight continues for. 33 | 34 | The image below shows 4 battles as simulated in the combat simulator and the ground truth when running in Starcraft 2. 35 | 36 | ![Comparion of combat simulator vs ground truth](docs/images/combat.png) 37 | 38 | ### Simple example 39 | ```C++ 40 | initMappings(); 41 | CombatPredictor simulator; 42 | simulator.init(); 43 | CombatState state = {{ 44 | makeUnit(1, UNIT_TYPEID::TERRAN_MARINE), 45 | makeUnit(1, UNIT_TYPEID::TERRAN_MEDIVAC), 46 | makeUnit(1, UNIT_TYPEID::TERRAN_MARINE), 47 | 48 | makeUnit(2, UNIT_TYPEID::ZERG_ROACH), 49 | makeUnit(2, UNIT_TYPEID::ZERG_ROACH), 50 | makeUnit(2, UNIT_TYPEID::ZERG_ZERGLING), 51 | }}; 52 | CombatResult outcome = simulator.predict_engage(state); 53 | cout << outcome.state.toString() << endl; 54 | cout << "Winner player is " << outcome.state.owner_with_best_outcome() << endl; 55 | ``` 56 | [Show full source code](examples/combat_simulator.cpp) 57 | 58 | Output: 59 | 60 | ``` 61 | Owner Unit Health 62 | 1 Marine 0/45 63 | 1 Medivac 150/150 64 | 1 Marine 0/45 65 | 2 Roach 58/145 66 | 2 Roach 145/145 67 | 2 Zergling 0/35 68 | 69 | Winner player is 2 70 | ``` 71 | 72 | ### More advanced example 73 | 74 | This shows how upgrades can be specified and how to access the unit data in the outcome. 75 | 76 |
Show code 77 | 78 | ```C++ 79 | initMappings(); 80 | CombatPredictor simulator; 81 | simulator.init(); 82 | 83 | CombatState state = {{ 84 | makeUnit(1, UNIT_TYPEID::TERRAN_MARINE), 85 | makeUnit(1, UNIT_TYPEID::TERRAN_MEDIVAC), 86 | makeUnit(1, UNIT_TYPEID::TERRAN_MARINE), 87 | 88 | makeUnit(2, UNIT_TYPEID::ZERG_ROACH), 89 | makeUnit(2, UNIT_TYPEID::ZERG_ROACH), 90 | makeUnit(2, UNIT_TYPEID::ZERG_ZERGLING), 91 | }}; 92 | 93 | CombatUpgrades player1upgrades = { 94 | UPGRADE_ID::TERRANINFANTRYWEAPONSLEVEL1, 95 | UPGRADE_ID::TERRANINFANTRYWEAPONSLEVEL2, 96 | UPGRADE_ID::TERRANINFANTRYWEAPONSLEVEL3, 97 | UPGRADE_ID::TERRANINFANTRYARMORSLEVEL1, 98 | UPGRADE_ID::TERRANINFANTRYARMORSLEVEL2, 99 | }; 100 | 101 | CombatUpgrades player2upgrades = { 102 | UPGRADE_ID::ZERGMISSILEWEAPONSLEVEL1, 103 | }; 104 | 105 | state.environment = &simulator.getCombatEnvironment(player1upgrades, player2upgrades); 106 | 107 | CombatSettings settings; 108 | // Simulate for at most 100 *game* seconds 109 | // Just to show that it can be configured, in this case 100 game seconds is more than enough for the battle to finish. 110 | settings.maxTime = 100; 111 | CombatResult outcome = simulator.predict_engage(state, settings); 112 | cout << outcome.state.toString() << endl; 113 | for (auto& unit : outcome.state.units) { 114 | cout << getUnitData(unit.type).name << " ended up with " << unit.health << " hp" << endl; 115 | } 116 | cout << "Winner player is " << outcome.state.owner_with_best_outcome() << endl; 117 | cout << "Battle concluded after " << outcome.time << " seconds" << endl; 118 | ``` 119 | 120 | [Show full source code](examples/combat_simulator2.cpp) 121 | 122 |
123 | 124 | Output: 125 | ``` 126 | Owner Unit Health 127 | 1 Marine 45/45 128 | 1 Medivac 150/150 129 | 1 Marine 0/45 130 | 2 Roach 0/145 131 | 2 Roach 0/145 132 | 2 Zergling 0/35 133 | 134 | Marine ended up with 45 hp 135 | Medivac ended up with 150 hp 136 | Marine ended up with 0 hp 137 | Roach ended up with 0 hp 138 | Roach ended up with 0 hp 139 | Zergling ended up with 0 hp 140 | Winner player is 1 141 | Battle concluded after 33 seconds 142 | ``` 143 | 144 | ## Build Order Optimizer 145 | 146 | The build order optimizer can find a highly optimized build order for producing a given set of units. 147 | 148 | - It can automatically build additional Nexuses/Hatcheries/Command Centers, workers and even Warpgate research if that speeds up the build order. 149 | - It has support for chrono boost. 150 | - It works for any built-in race, however Protoss is the one most heavily tested. Zerg and Terran may require some additional work. 151 | - Takes on the order of half a second to run. The number of iterations can be lowered/increased to adjust the quality/time trade-off. 152 | - Does not optimize for the absolutely shortest build order by default, but optimizes for a good economy as well. 153 | - Can take both units and upgrades as requirements. 154 | 155 | ### Simple example 156 | 157 | This example shows how to generate a build order from a given starting state. 158 | 159 | ```C++ 160 | initMappings(); 161 | 162 | // Start with 1 nexus and 12 probes 163 | BuildState state {{ 164 | { UNIT_TYPEID::PROTOSS_NEXUS, 1 }, 165 | { UNIT_TYPEID::PROTOSS_PROBE, 12 }, 166 | }}; 167 | // Start with 50 minerals 168 | state.resources.minerals = 50; 169 | state.resources.vespene = 0; 170 | // Our Nexus has 50 energy at the start of the game 171 | state.chronoInfo.addNexusWithEnergy(state.time, 50); 172 | 173 | // Find a good build order for producing some units 174 | BuildOrder buildOrder = findBestBuildOrderGenetic(state, { 175 | { BuildOrderItem(UNIT_TYPEID::PROTOSS_ZEALOT), 4 }, 176 | { BuildOrderItem(UNIT_TYPEID::PROTOSS_STALKER), 4 }, 177 | { BuildOrderItem(UNIT_TYPEID::PROTOSS_IMMORTAL), 2 }, 178 | { BuildOrderItem(UPGRADE_ID::PROTOSSGROUNDARMORSLEVEL1), 1 }, 179 | }); 180 | 181 | // Print the build order 182 | cout << buildOrder.toString() << endl; 183 | 184 | // Print a more detailed build order output which includes timing information 185 | cout << buildOrder.toString(state, BuildOrderPrintMode::Detailed); 186 | ``` 187 | [Show full source code](examples/build_optimizer.cpp) 188 | 189 |
Show example output 190 | 191 | ``` 192 | 0 PROTOSS_PROBE 193 | 1 PROTOSS_PROBE 194 | 2 PROTOSS_PYLON 195 | 3 PROTOSS_PROBE 196 | 4 PROTOSS_PROBE 197 | 5 PROTOSS_GATEWAY 198 | 6 PROTOSS_PROBE 199 | 7 PROTOSS_PROBE 200 | 8 PROTOSS_ASSIMILATOR 201 | 9 PROTOSS_PROBE 202 | 10 PROTOSS_FORGE 203 | 11 PROTOSS_PROBE 204 | 12 PROTOSS_PROBE 205 | 13 PROTOSS_ASSIMILATOR 206 | 14 PROTOSS_PROBE 207 | 15 PROTOSS_GATEWAY 208 | 16 PROTOSS_CYBERNETICSCORE 209 | 17 PROTOSS_PROBE 210 | 18 PROTOSS_PYLON 211 | 19 PROTOSS_STALKER 212 | 20 PROTOSS_ROBOTICSFACILITY 213 | 21 PROTOSS_ZEALOT 214 | 22 PROTOSSGROUNDARMORSLEVEL1 215 | 23 PROTOSS_PYLON 216 | 24 PROTOSS_ZEALOT 217 | 25 PROTOSS_ZEALOT 218 | 26 PROTOSS_IMMORTAL 219 | 27 PROTOSS_STALKER 220 | 28 PROTOSS_STALKER 221 | 29 PROTOSS_PYLON 222 | 30 PROTOSS_STALKER 223 | 31 PROTOSS_IMMORTAL 224 | 32 PROTOSS_ZEALOT 225 | 226 | Time Food Min. Ves. Name 227 | 0 0:00 13/15 0 0 PROTOSS_PROBE (chrono boosted) 228 | 1 0:08 14/15 40 0 PROTOSS_PROBE 229 | 2 0:12 14/15 0 0 PROTOSS_PYLON 230 | 3 0:17 15/15 0 0 PROTOSS_PROBE 231 | 4 0:30 16/23 127 0 PROTOSS_PROBE 232 | 5 0:32 16/23 0 0 PROTOSS_GATEWAY 233 | 6 0:38 17/23 34 0 PROTOSS_PROBE 234 | 7 0:47 18/23 105 0 PROTOSS_PROBE 235 | 8 0:47 18/23 30 0 PROTOSS_ASSIMILATOR 236 | 9 0:57 19/23 135 0 PROTOSS_PROBE 237 | 10 0:58 19/23 0 0 PROTOSS_FORGE 238 | 11 1:09 20/23 124 2 PROTOSS_PROBE (chrono boosted) 239 | 12 1:17 21/23 195 24 PROTOSS_PROBE 240 | 13 1:17 21/23 120 24 PROTOSS_ASSIMILATOR 241 | 14 1:25 22/23 192 45 PROTOSS_PROBE 242 | 15 1:25 22/23 42 45 PROTOSS_GATEWAY 243 | 16 1:32 22/23 0 64 PROTOSS_CYBERNETICSCORE 244 | 17 1:35 23/23 1 72 PROTOSS_PROBE 245 | 18 1:42 23/23 0 98 PROTOSS_PYLON 246 | 19 2:08 25/31 268 186 PROTOSS_STALKER 247 | 20 2:08 25/31 118 86 PROTOSS_ROBOTICSFACILITY 248 | 21 2:12 27/31 74 107 PROTOSS_ZEALOT 249 | 22 2:13 27/31 0 16 PROTOSSGROUNDARMORSLEVEL1 250 | 23 2:20 27/31 0 50 PROTOSS_PYLON 251 | 24 2:38 29/39 175 146 PROTOSS_ZEALOT 252 | 25 2:39 31/39 90 151 PROTOSS_ZEALOT 253 | 26 2:54 35/39 54 133 PROTOSS_IMMORTAL 254 | 27 3:05 37/39 94 140 PROTOSS_STALKER 255 | 28 3:07 39/39 0 101 PROTOSS_STALKER 256 | 29 3:13 39/39 0 135 PROTOSS_PYLON 257 | 30 3:35 41/47 205 200 PROTOSS_STALKER 258 | 31 3:39 45/47 0 124 PROTOSS_IMMORTAL (chrono boosted) 259 | 32 3:46 47/47 0 159 PROTOSS_ZEALOT (chrono boosted) 260 | Done at 4:09 261 | ``` 262 |
263 | 264 | If you have a SC2 observation instance you can construct a BuildState easily. It would be annoying to have to manually add everything. 265 | This takes care of things like finding existing buildings, units and upgrades as well as buildings and units that are in progress. 266 | It will also estimate the remaining number of mineral fields around each base. 267 | 268 | ```C++ 269 | // If you have a SC2 agent observation you can construct a build state easily 270 | BuildResources resources(observation->GetMinerals(), observation->GetVespene()); 271 | float time = ticksToSeconds(observation->GetGameLoop()); 272 | BuildState state(observation, Unit::Alliance::Self, Race::Protoss, resources, time)); 273 | ``` 274 | 275 | ## Using the precompiled library 276 | 277 | Requirements: 278 | - C++14 279 | - [s2client-api](https://github.com/Blizzard/s2client-api) 280 | 281 | Steps 282 | - Download the precompiled library (see [latest release](https://github.com/HalfVoxel/sc2-libvoxelbot/releases/latest)). 283 | - Unzip the compressed folder. Inside there are two folders: include and lib. 284 | - Add the include directory to your project's list of include directories. 285 | - Add either the release or debug library in the lib folder to your project's linker settings. 286 | In Visual Studio this is done in the Project Properties -> Linker -> Input -> Additional Dependencies setting. 287 | In CMake you use the `target_link_libraries` command. 288 | 289 | After you have done this you should be good to go. Assuming your project already uses the s2client-api library and links against it. 290 | 291 | ## Building 292 | 293 | Requirements: 294 | - C++14 295 | 296 | ```bash 297 | git clone --recursive 298 | cd sc2-libvoxelbot 299 | mkdir build 300 | cd build 301 | cmake .. 302 | 303 | # The above will generate project files for you. 304 | # Which type depends on which platform you are using. 305 | # On Windows it will for example generate a Visual Studio project which you can then build. 306 | # On linux/mac you can now write 307 | make -j8 308 | # to build the library (the -j8 parameter makes it use a multithreaded build, so it goes faster) 309 | ``` 310 | 311 | When everything is built you can execute one of the tests 312 | 313 | ```bash 314 | # Unix/Mac 315 | ./build/bin/example_combat_simulator 316 | 317 | # Windows 318 | ./build/Release/example_combat_simulator.exe 319 | ``` 320 | -------------------------------------------------------------------------------- /libvoxelbot/caching/caching.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "sc2api/sc2_api.h" 8 | #include "sc2utils/sc2_manage_process.h" 9 | 10 | using namespace sc2; 11 | using namespace std; 12 | 13 | static const char* EmptyMap = "Test/Empty.SC2Map"; 14 | 15 | vector attributeNames = { 16 | "Invalid", 17 | "Light", 18 | "Armored", 19 | "Biological", 20 | "Mechanical", 21 | "Robotic", 22 | "Psionic", 23 | "Massive", 24 | "Structure", 25 | "Hover", 26 | "Heroic", 27 | "Summoned", 28 | "Invalid", 29 | }; 30 | 31 | vector weaponTypeNames = { 32 | "Invalid", 33 | "Ground", 34 | "Air", 35 | "Any", 36 | "Invalid", 37 | }; 38 | 39 | int hashFile(string path) { 40 | ifstream stream(path); 41 | int hash = 0; 42 | char c; 43 | while(stream >> c) { 44 | hash = (hash * 31) ^ c; 45 | } 46 | return hash; 47 | } 48 | 49 | void writeUnitTypeOverview (ofstream& result, const vector& unit_types, const DependencyAnalyzer& deps) { 50 | for (const UnitTypeData& type : unit_types) { 51 | if (!type.available) 52 | continue; 53 | result << type.name << " " << UnitTypeToName(type.unit_type_id) << endl; 54 | result << "\tID: " << (int)type.unit_type_id << endl; 55 | result << "\tMinerals: " << type.mineral_cost << endl; 56 | result << "\tVespene: " << type.vespene_cost << endl; 57 | result << "\tSupply Required: " << type.food_required << endl; 58 | result << "\tSupply Provided: " << type.food_provided << endl; 59 | result << "\tBuild time: " << type.build_time << endl; 60 | result << "\tArmor: " << type.armor << endl; 61 | result << "\tMovement speed: " << type.movement_speed << endl; 62 | for (auto t : type.tech_alias) { 63 | result << "\tTech Alias: " << UnitTypeToName(t) << endl; 64 | } 65 | result << "\tUnit Alias: " << UnitTypeToName(type.unit_alias) << endl; 66 | result << "\tTech Aliases: "; 67 | for (auto t : type.tech_alias) result << UnitTypeToName(t) << " "; 68 | result << endl; 69 | result << "\tTech Requirement: " << UnitTypeToName(type.tech_requirement) << endl; 70 | 71 | result << "\tAbility: " << AbilityTypeToName(type.ability_id) << endl; 72 | result << "\tAttributes:"; 73 | for (auto a : type.attributes) { 74 | result << " " << attributeNames[(int)a]; 75 | } 76 | result << endl; 77 | result << "\tWeapons:" << endl; 78 | for (auto w : type.weapons) { 79 | result << "\t\tDamage: " << w.damage_ << endl; 80 | result << "\t\tTarget: " << weaponTypeNames[(int)w.type] << endl; 81 | result << "\t\tBonuses: "; 82 | for (auto b : w.damage_bonus) { 83 | result << attributeNames[(int)b.attribute] << "=+" << b.bonus << " "; 84 | } 85 | result << endl; 86 | result << "\t\tAttacks: " << w.attacks << endl; 87 | result << "\t\tRange: " << w.range << endl; 88 | result << "\t\tCooldown: " << w.speed << endl; 89 | result << "\t\tDPS: " << (w.attacks * w.damage_ / w.speed) << endl; 90 | result << endl; 91 | } 92 | 93 | result << "\tHas been: "; 94 | for (auto u : hasBeen(type.unit_type_id)) { 95 | result << UnitTypeToName(u) << ", "; 96 | } 97 | result << endl; 98 | result << "\tCan become: "; 99 | for (auto u : canBecome(type.unit_type_id)) { 100 | result << UnitTypeToName(u) << ", "; 101 | } 102 | result << endl; 103 | result << "\tDependencies: "; 104 | for (auto u : deps.allUnitDependencies[(int)type.unit_type_id]) { 105 | result << UnitTypeToName(u) << ", "; 106 | } 107 | result << endl; 108 | 109 | result << endl; 110 | } 111 | } 112 | 113 | // Model 114 | // Σ Pi + Σ PiSij 115 | class CachingBot : public sc2::Agent { 116 | ofstream results; 117 | int tick = 0; 118 | 119 | public: 120 | int mode = 0; 121 | 122 | void OnGameLoading() { 123 | } 124 | 125 | void OnGameStart() override { 126 | if (mode == 0) { 127 | results = ofstream("bot/generated/abilities.h"); 128 | results << "#pragma once" << endl; 129 | results << "#include" << endl; 130 | results << "extern std::vector> unit_type_has_ability;" << endl; 131 | results << "extern std::vector> unit_type_initial_health;" << endl; 132 | results << "extern std::vector unit_type_is_flying;" << endl; 133 | results << "extern std::vector unit_type_radius;" << endl; 134 | 135 | results.close(); 136 | 137 | results = ofstream("bot/generated/abilities.cpp"); 138 | results << "#include \"abilities.h\"" << endl; 139 | } 140 | 141 | Debug()->DebugEnemyControl(); 142 | Debug()->DebugShowMap(); 143 | Debug()->DebugIgnoreFood(); 144 | Debug()->DebugIgnoreResourceCost(); 145 | Debug()->SendDebug(); 146 | Debug()->DebugGiveAllTech(); 147 | Debug()->SendDebug(); 148 | 149 | Point2D mn = Observation()->GetGameInfo().playable_min; 150 | Point2D mx = Observation()->GetGameInfo().playable_max; 151 | 152 | const sc2::UnitTypes& unit_types = Observation()->GetUnitTypeData(true); 153 | 154 | if (mode == 1) { 155 | initMappings(Observation()); 156 | 157 | DependencyAnalyzer deps; 158 | // TODO: Use Observation here 159 | deps.analyze(); 160 | 161 | // Note: this depends on output from this program, so it really has to be run twice to get the correct output. 162 | ofstream unit_lookup = ofstream("bot/generated/units.txt"); 163 | writeUnitTypeOverview(unit_lookup, unit_types, deps); 164 | unit_lookup.close(); 165 | 166 | const sc2::UnitTypes& unit_types3 = Observation()->GetUnitTypeData(true); 167 | 168 | // Note: this depends on output from this program, so it really has to be run twice to get the correct output. 169 | unit_lookup = ofstream("bot/generated/units_with_tech.txt"); 170 | writeUnitTypeOverview(unit_lookup, unit_types3, deps); 171 | unit_lookup.close(); 172 | 173 | save_unit_data(unit_types); 174 | 175 | // Try loading and re-saving the data to make sure that everything is loaded correctly 176 | int hash = hashFile(UNIT_DATA_CACHE_PATH); 177 | auto unit_types2 = load_unit_data(); 178 | save_unit_data(unit_types2, "/tmp/unit_data.data"); 179 | int hash2 = hashFile("/tmp/unit_data.data"); 180 | if (hash != hash2) { 181 | cerr << "Error in unit type data serialization code" << endl; 182 | exit(1); 183 | } 184 | 185 | ofstream unit_lookup2 = ofstream("bot/generated/units_verify.txt"); 186 | writeUnitTypeOverview(unit_lookup2, unit_types2, deps); 187 | unit_lookup2.close(); 188 | 189 | save_ability_data(Observation()->GetAbilityData()); 190 | save_upgrade_data(Observation()->GetUpgradeData()); 191 | } else { 192 | cerr << "Note: run program again with 'pass2' as an argument on the commandline to get the correct output" << endl; 193 | 194 | int i = 0; 195 | for (const UnitTypeData& type : unit_types) { 196 | if (!type.available) 197 | continue; 198 | float x = mn.x + ((rand() % 1000) / 1000.0f) * (mx.x - mn.x); 199 | float y = mn.y + ((rand() % 1000) / 1000.0f) * (mx.y - mn.y); 200 | if (type.race == Race::Protoss && type.movement_speed == 0) { 201 | Debug()->DebugCreateUnit(UNIT_TYPEID::PROTOSS_PYLON, Point2D(x, y)); 202 | } 203 | Debug()->DebugCreateUnit(type.unit_type_id, Point2D(x, y)); 204 | i++; 205 | } 206 | } 207 | 208 | Debug()->SendDebug(); 209 | } 210 | 211 | void OnStep() override { 212 | if (tick == 2) { 213 | if (mode == 0) { 214 | results << "std::vector> unit_type_has_ability = {" << endl; 215 | auto ourUnits = Observation()->GetUnits(Unit::Alliance::Self); 216 | auto abilities = Query()->GetAbilitiesForUnits(ourUnits, true); 217 | const sc2::UnitTypes& unitTypes = Observation()->GetUnitTypeData(); 218 | for (int i = 0; i < ourUnits.size(); i++) { 219 | AvailableAbilities& abilitiesForUnit = abilities[i]; 220 | for (auto availableAbility : abilitiesForUnit.abilities) { 221 | results << "\t{ " << ourUnits[i]->unit_type << ", " << availableAbility.ability_id << " }," << endl; 222 | cout << unitTypes[ourUnits[i]->unit_type].name << " has " << AbilityTypeToName(availableAbility.ability_id) << endl; 223 | } 224 | } 225 | 226 | results << "};" << endl; 227 | 228 | const sc2::UnitTypes& unit_types = Observation()->GetUnitTypeData(); 229 | vector> initial_health(unit_types.size()); 230 | vector is_flying(unit_types.size()); 231 | vector radii(unit_types.size()); 232 | for (int i = 0; i < ourUnits.size(); i++) { 233 | initial_health[(int)ourUnits[i]->unit_type] = make_pair(ourUnits[i]->health_max, ourUnits[i]->shield_max); 234 | is_flying[(int)ourUnits[i]->unit_type] = ourUnits[i]->is_flying; 235 | radii[(int)ourUnits[i]->unit_type] = ourUnits[i]->radius; 236 | } 237 | 238 | // Fix marine health, DebugGiveAllTech will make marines get 10 extra hp 239 | initial_health[(int)UNIT_TYPEID::TERRAN_MARINE] = make_pair(45, 0); 240 | 241 | results << "std::vector> unit_type_initial_health = {" << endl; 242 | for (int i = 0; i < initial_health.size(); i++) { 243 | results << "\t{" << initial_health[i].first << ", " << initial_health[i].second << "},\n"; 244 | } 245 | results << "};" << endl; 246 | 247 | results << "std::vector unit_type_is_flying = {" << endl; 248 | for (int i = 0; i < initial_health.size(); i++) { 249 | results << "\t" << (is_flying[i] ? "true" : "false") << ",\n"; 250 | } 251 | results << "};" << endl; 252 | 253 | results << "std::vector unit_type_radius = {" << endl; 254 | for (int i = 0; i < initial_health.size(); i++) { 255 | results << "\t" << radii[i] << ",\n"; 256 | } 257 | results << "};" << endl; 258 | results.close(); 259 | } 260 | 261 | Debug()->DebugEndGame(false); 262 | } 263 | 264 | Actions()->SendActions(); 265 | Debug()->SendDebug(); 266 | tick++; 267 | } 268 | }; 269 | 270 | int main(int argc, char* argv[]) { 271 | Coordinator coordinator; 272 | if (!coordinator.LoadSettings(argc, argv)) { 273 | return 1; 274 | } 275 | 276 | coordinator.SetMultithreaded(true); 277 | 278 | CachingBot bot; 279 | coordinator.SetParticipants({ CreateParticipant(Race::Terran, &bot) }); 280 | 281 | // Start the game. 282 | 283 | coordinator.SetRealtime(false); 284 | coordinator.LaunchStarcraft(); 285 | bool do_break = false; 286 | 287 | bot.OnGameLoading(); 288 | 289 | if (argc > 1 && string(argv[1]) == "pass2") { 290 | bot.mode = 1; 291 | } 292 | coordinator.StartGame(EmptyMap); 293 | 294 | while (coordinator.Update() && !do_break) { 295 | if (PollKeyPress()) { 296 | do_break = true; 297 | } 298 | } 299 | return 0; 300 | } 301 | -------------------------------------------------------------------------------- /libvoxelbot/combat/combat_environment.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | using namespace sc2; 8 | 9 | int getDamageBonus(UNIT_TYPEID unit, const CombatUpgrades& upgrades) { 10 | if (isStructure(unit)) return 0; 11 | 12 | int bonus = 0; 13 | switch(getUnitData(unit).race) { 14 | case Race::Protoss: 15 | if (isFlying(unit)) { 16 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSAIRWEAPONSLEVEL1)) bonus += 1; 17 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSAIRWEAPONSLEVEL2)) bonus += 1; 18 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSAIRWEAPONSLEVEL3)) bonus += 1; 19 | } else { 20 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSGROUNDWEAPONSLEVEL1)) bonus += 1; 21 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSGROUNDWEAPONSLEVEL2)) bonus += 1; 22 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSGROUNDWEAPONSLEVEL3)) bonus += 1; 23 | } 24 | break; 25 | case Race::Zerg: 26 | if (isFlying(unit)) { 27 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGFLYERWEAPONSLEVEL1)) bonus += 1; 28 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGFLYERWEAPONSLEVEL2)) bonus += 1; 29 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGFLYERWEAPONSLEVEL3)) bonus += 1; 30 | } else if (isMelee(unit)) { 31 | // TODO: Might need to check which units are actually melee 32 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGMELEEWEAPONSLEVEL1)) bonus += 1; 33 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGMELEEWEAPONSLEVEL2)) bonus += 1; 34 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGMELEEWEAPONSLEVEL3)) bonus += 1; 35 | } else { 36 | // Ranged 37 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGMISSILEWEAPONSLEVEL1)) bonus += 1; 38 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGMISSILEWEAPONSLEVEL2)) bonus += 1; 39 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGMISSILEWEAPONSLEVEL3)) bonus += 1; 40 | } 41 | break; 42 | case Race::Terran: { 43 | auto canonical = canonicalize(unit); 44 | auto& casters = abilityToCasterUnit(getUnitData(canonical).ability_id); 45 | if (casters.size() > 0 && casters[0] == UNIT_TYPEID::TERRAN_BARRACKS) { 46 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANINFANTRYWEAPONSLEVEL1)) bonus += 1; 47 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANINFANTRYWEAPONSLEVEL2)) bonus += 1; 48 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANINFANTRYWEAPONSLEVEL3)) bonus += 1; 49 | } else if (casters.size() > 0 && casters[0] == UNIT_TYPEID::TERRAN_FACTORY) { 50 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANVEHICLEWEAPONSLEVEL1)) bonus += 1; 51 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANVEHICLEWEAPONSLEVEL2)) bonus += 1; 52 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANVEHICLEWEAPONSLEVEL3)) bonus += 1; 53 | } else if (casters.size() > 0 && casters[0] == UNIT_TYPEID::TERRAN_STARPORT) { 54 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANSHIPWEAPONSLEVEL1)) bonus += 1; 55 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANSHIPWEAPONSLEVEL2)) bonus += 1; 56 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANSHIPWEAPONSLEVEL3)) bonus += 1; 57 | } 58 | break; 59 | } 60 | default: 61 | break; 62 | } 63 | return bonus; 64 | } 65 | 66 | float getArmorBonus(UNIT_TYPEID unit, const CombatUpgrades& upgrades) { 67 | if (isStructure(unit)) { 68 | if (getUnitData(unit).race == Race::Terran && upgrades.hasUpgrade(UPGRADE_ID::TERRANBUILDINGARMOR)) return 2; 69 | return 0; 70 | } 71 | 72 | float bonus = 0; 73 | switch(getUnitData(unit).race) { 74 | case Race::Protoss: 75 | if (isFlying(unit)) { 76 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSAIRARMORSLEVEL1)) bonus += 1; 77 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSAIRARMORSLEVEL2)) bonus += 1; 78 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSAIRARMORSLEVEL3)) bonus += 1; 79 | } else { 80 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSGROUNDARMORSLEVEL1)) bonus += 1; 81 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSGROUNDARMORSLEVEL2)) bonus += 1; 82 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSGROUNDARMORSLEVEL3)) bonus += 1; 83 | } 84 | 85 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSSHIELDSLEVEL1)) bonus += 1; 86 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSSHIELDSLEVEL2)) bonus += 1; 87 | if (upgrades.hasUpgrade(UPGRADE_ID::PROTOSSSHIELDSLEVEL3)) bonus += 1; 88 | break; 89 | case Race::Zerg: 90 | if (isFlying(unit)) { 91 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGFLYERARMORSLEVEL1)) bonus += 1; 92 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGFLYERARMORSLEVEL2)) bonus += 1; 93 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGFLYERARMORSLEVEL3)) bonus += 1; 94 | } else { 95 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGGROUNDARMORSLEVEL1)) bonus += 1; 96 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGGROUNDARMORSLEVEL2)) bonus += 1; 97 | if (upgrades.hasUpgrade(UPGRADE_ID::ZERGGROUNDARMORSLEVEL3)) bonus += 1; 98 | } 99 | case Race::Terran: { 100 | auto canonical = canonicalize(unit); 101 | auto& casters = abilityToCasterUnit(getUnitData(canonical).ability_id); 102 | if (casters.size() > 0 && casters[0] == UNIT_TYPEID::TERRAN_BARRACKS) { 103 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANINFANTRYARMORSLEVEL1)) bonus += 1; 104 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANINFANTRYARMORSLEVEL2)) bonus += 1; 105 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANINFANTRYARMORSLEVEL3)) bonus += 1; 106 | } else if (casters.size() > 0 && (casters[0] == UNIT_TYPEID::TERRAN_FACTORY || casters[0] == UNIT_TYPEID::TERRAN_STARPORT)) { 107 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANVEHICLEANDSHIPARMORSLEVEL1)) bonus += 1; 108 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANVEHICLEANDSHIPARMORSLEVEL2)) bonus += 1; 109 | if (upgrades.hasUpgrade(UPGRADE_ID::TERRANVEHICLEANDSHIPARMORSLEVEL3)) bonus += 1; 110 | } 111 | break; 112 | } 113 | default: 114 | break; 115 | } 116 | return bonus; 117 | } 118 | 119 | CombatEnvironment::CombatEnvironment(const CombatUpgrades& upgrades, const CombatUpgrades& targetUpgrades) : upgrades({{ upgrades, targetUpgrades }}) { 120 | assertMappingsInitialized(); 121 | auto& unitTypes = getUnitTypes(); 122 | for (size_t i = 0; i < unitTypes.size(); i++) { 123 | combatInfo[0].push_back(UnitCombatInfo((UNIT_TYPEID)i, upgrades, targetUpgrades)); 124 | } 125 | for (size_t i = 0; i < unitTypes.size(); i++) { 126 | combatInfo[1].push_back(UnitCombatInfo((UNIT_TYPEID)i, targetUpgrades, upgrades)); 127 | } 128 | 129 | for (int owner = 0; owner < 2; owner++) { 130 | combatInfo[owner][(int)UNIT_TYPEID::TERRAN_LIBERATOR].airWeapon.splash = 3.0; 131 | combatInfo[owner][(int)UNIT_TYPEID::TERRAN_MISSILETURRET].airWeapon.splash = 3.0; 132 | combatInfo[owner][(int)UNIT_TYPEID::TERRAN_SIEGETANKSIEGED].groundWeapon.splash = 4.0; 133 | combatInfo[owner][(int)UNIT_TYPEID::TERRAN_HELLION].groundWeapon.splash = 2; 134 | combatInfo[owner][(int)UNIT_TYPEID::TERRAN_HELLIONTANK].groundWeapon.splash = 3; 135 | combatInfo[owner][(int)UNIT_TYPEID::ZERG_MUTALISK].groundWeapon.splash = 1.44f; 136 | combatInfo[owner][(int)UNIT_TYPEID::ZERG_MUTALISK].airWeapon.splash = 1.44f; 137 | combatInfo[owner][(int)UNIT_TYPEID::TERRAN_THOR].airWeapon.splash = 3; 138 | combatInfo[owner][(int)UNIT_TYPEID::PROTOSS_ARCHON].groundWeapon.splash = 3; 139 | combatInfo[owner][(int)UNIT_TYPEID::PROTOSS_ARCHON].airWeapon.splash = 3; 140 | combatInfo[owner][(int)UNIT_TYPEID::PROTOSS_COLOSSUS].groundWeapon.splash = 3; 141 | } 142 | } 143 | 144 | const CombatEnvironment& CombatPredictor::combineCombatEnvironment(const CombatEnvironment* env, const CombatUpgrades& upgrades, int upgradesOwner) const { 145 | assert(upgradesOwner == 1 || upgradesOwner == 2); 146 | 147 | array newUpgrades = env != nullptr ? env->upgrades : array{{ {}, {} }}; 148 | newUpgrades[upgradesOwner - 1].combine(upgrades); 149 | 150 | return getCombatEnvironment(newUpgrades[0], newUpgrades[1]); 151 | } 152 | 153 | const CombatEnvironment& CombatPredictor::getCombatEnvironment(const CombatUpgrades& upgrades, const CombatUpgrades& targetUpgrades) const { 154 | uint64_t hash = (upgrades.hash() * 5123143) ^ targetUpgrades.hash(); 155 | auto it = combatEnvironments.find(hash); 156 | if (it != combatEnvironments.end()) { 157 | return (*it).second; 158 | } 159 | 160 | // Note: references to map values are guaranteed to remain valid even after additional insertion into the map 161 | return (*combatEnvironments.emplace(make_pair(hash, CombatEnvironment(upgrades, targetUpgrades))).first).second; 162 | } 163 | 164 | // TODO: Air? 165 | float CombatEnvironment::attackRange(int owner, UNIT_TYPEID type) const { 166 | auto& info = combatInfo[owner - 1][(int)type]; 167 | return max(info.airWeapon.range(), info.groundWeapon.range()); 168 | } 169 | 170 | float CombatEnvironment::attackRange(const CombatUnit& unit) const { 171 | auto& info = combatInfo[unit.owner - 1][(int)unit.type]; 172 | float range = max(info.airWeapon.range(), info.groundWeapon.range()); 173 | 174 | if (unit.type == UNIT_TYPEID::PROTOSS_COLOSSUS && upgrades[unit.owner-1].hasUpgrade(UPGRADE_ID::EXTENDEDTHERMALLANCE)) range += 2; 175 | 176 | return range; 177 | } 178 | 179 | const UnitCombatInfo& CombatEnvironment::getCombatInfo(const CombatUnit& unit) const { 180 | return combatInfo[unit.owner - 1][(int)unit.type]; 181 | } 182 | 183 | float CombatEnvironment::calculateDPS(int owner, UNIT_TYPEID type, bool air) const { 184 | auto& info = combatInfo[owner - 1][(int)type]; 185 | return air ? info.airWeapon.getDPS() : info.groundWeapon.getDPS(); 186 | } 187 | 188 | float CombatEnvironment::calculateDPS(const vector& units, bool air) const { 189 | float dps = 0; 190 | for (auto& u : units) 191 | dps += calculateDPS(u.owner, u.type, air); 192 | return dps; 193 | } 194 | 195 | float CombatEnvironment::calculateDPS(const CombatUnit& unit, bool air) const { 196 | auto& info = combatInfo[unit.owner - 1][(int)unit.type]; 197 | return air ? info.airWeapon.getDPS() : info.groundWeapon.getDPS(); 198 | } 199 | 200 | float CombatEnvironment::calculateDPS(const CombatUnit& unit1, const CombatUnit& unit2) const { 201 | auto& info = combatInfo[unit1.owner - 1][(int)unit1.type]; 202 | return max(info.groundWeapon.getDPS(unit2.type), info.airWeapon.getDPS(unit2.type)); 203 | } 204 | 205 | 206 | float calculateDPS(UNIT_TYPEID attacker, UNIT_TYPEID target, const Weapon& weapon, const CombatUpgrades& attackerUpgrades, const CombatUpgrades& targetUpgrades) { 207 | // canBeAttackedByAirWeapons is primarily for coloussus. 208 | if (weapon.type == Weapon::TargetType::Any || (weapon.type == Weapon::TargetType::Air ? canBeAttackedByAirWeapons(target) : !isFlying(target))) { 209 | float dmg = weapon.damage_; 210 | for (auto& b : weapon.damage_bonus) { 211 | if (contains(getUnitData(target).attributes, b.attribute)) { 212 | dmg += b.bonus; 213 | } 214 | } 215 | 216 | dmg += getDamageBonus(attacker, attackerUpgrades); 217 | 218 | float armor = getUnitData(target).armor + getArmorBonus(target, targetUpgrades); 219 | 220 | // Note: cannot distinguish between damage to shields and damage to health yet, so average out so that the armor is about 0.5 over the whole shield+health of the unit 221 | // Important only for protoss 222 | armor *= maxHealth(target) / (maxHealth(target) + maxShield(target)); 223 | 224 | float timeBetweenAttacks = weapon.speed; 225 | 226 | if (attacker == UNIT_TYPEID::PROTOSS_ADEPT && attackerUpgrades.hasUpgrade(UPGRADE_ID::ADEPTPIERCINGATTACK)) timeBetweenAttacks /= 1.45f; 227 | 228 | return max(0.0f, dmg - armor) * weapon.attacks / timeBetweenAttacks; 229 | } 230 | 231 | return 0; 232 | } 233 | 234 | float WeaponInfo::getDPS(UNIT_TYPEID target, float modifier) const { 235 | if (!available) 236 | return 0; 237 | 238 | if ((int)target >= (int)dpsCache.size()) { 239 | throw std::out_of_range(UnitTypeToName(target)); 240 | } 241 | 242 | // TODO: Modifier ignores speed upgrades 243 | return max(0.0f, dpsCache[(int)target] + modifier*weapon->attacks/weapon->speed); 244 | } 245 | 246 | float WeaponInfo::range() const { 247 | return weapon != nullptr ? weapon->range : 0; 248 | } 249 | 250 | float UnitCombatInfo::attackInterval() const { 251 | float v = numeric_limits::infinity(); 252 | if (airWeapon.weapon != nullptr) v = airWeapon.weapon->speed; 253 | if (groundWeapon.weapon != nullptr) v = min(v, groundWeapon.weapon->speed); 254 | return v; 255 | } 256 | 257 | WeaponInfo::WeaponInfo(const Weapon* weapon, UNIT_TYPEID type, const CombatUpgrades& upgrades, const CombatUpgrades& targetUpgrades) { 258 | available = true; 259 | splash = 0; 260 | this->weapon = weapon; 261 | // TODO: Range upgrades! 262 | 263 | baseDPS = (weapon->damage_ + getDamageBonus(type, upgrades)) * weapon->attacks / weapon->speed; 264 | 265 | auto& unitTypes = getUnitTypes(); 266 | for (size_t i = 0; i < unitTypes.size(); i++) { 267 | dpsCache.push_back(calculateDPS(type, (UNIT_TYPEID)i, *weapon, upgrades, targetUpgrades)); 268 | } 269 | } 270 | 271 | 272 | UnitCombatInfo::UnitCombatInfo(UNIT_TYPEID type, const CombatUpgrades& upgrades, const CombatUpgrades& targetUpgrades) { 273 | auto& data = getUnitData(type); 274 | 275 | for (const Weapon& weapon : data.weapons) { 276 | if (weapon.type == Weapon::TargetType::Any || weapon.type == Weapon::TargetType::Air) { 277 | if (airWeapon.available) { 278 | cerr << "For unit type " << UnitTypeToName(type) << endl; 279 | cerr << "Weapon slot is already used"; 280 | assert(false); 281 | } 282 | airWeapon = WeaponInfo(&weapon, type, upgrades, targetUpgrades); 283 | } 284 | if (weapon.type == Weapon::TargetType::Any || weapon.type == Weapon::TargetType::Ground) { 285 | if (groundWeapon.available) { 286 | cerr << "For unit type " << UnitTypeToName(type) << endl; 287 | cerr << "Weapon slot is already used"; 288 | assert(false); 289 | } 290 | groundWeapon = WeaponInfo(&weapon, type, upgrades, targetUpgrades); 291 | } 292 | } 293 | } -------------------------------------------------------------------------------- /libvoxelbot/utilities/influence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "renderer.h" 9 | 10 | using namespace std; 11 | using namespace sc2; 12 | 13 | InfluenceMap::InfluenceMap(const sc2::ImageData map) 14 | : InfluenceMap(map.width, map.height) { 15 | assert(map.bits_per_pixel == 8); 16 | for (int y = 0; y < h; y++) { 17 | for (int x = 0; x < w; x++) { 18 | weights[y * w + x] = (uint8_t)map.data[(h - y - 1) * w + x] == 255 ? 0.0 : 1.0; 19 | } 20 | } 21 | } 22 | 23 | InfluenceMap::InfluenceMap(const SC2APIProtocol::ImageData map) 24 | : InfluenceMap(map.size().x(), map.size().y()) { 25 | auto& data = map.data(); 26 | for (int y = 0; y < h; y++) { 27 | for (int x = 0; x < w; x++) { 28 | weights[y * w + x] = (uint8_t)data[(h - y - 1) * w + x]; 29 | } 30 | } 31 | } 32 | 33 | static pair round_point(Point2D p) { 34 | return make_pair((int)round(p.x), (int)round(p.y)); 35 | } 36 | 37 | InfluenceMap& InfluenceMap::operator+=(const InfluenceMap& other) { 38 | for (int i = 0; i < w * h; i++) { 39 | weights[i] += other.weights[i]; 40 | } 41 | return (*this); 42 | } 43 | 44 | InfluenceMap& InfluenceMap::operator+=(double other) { 45 | for (int i = 0; i < w * h; i++) { 46 | weights[i] += other; 47 | } 48 | return (*this); 49 | } 50 | 51 | InfluenceMap InfluenceMap::operator+(const InfluenceMap& other) const { 52 | auto ret = (*this); 53 | return (ret += other); 54 | } 55 | 56 | InfluenceMap& InfluenceMap::operator-=(const InfluenceMap& other) { 57 | assert(w == other.w); 58 | assert(h == other.h); 59 | 60 | for (int i = 0; i < w * h; i++) { 61 | weights[i] -= other.weights[i]; 62 | } 63 | return (*this); 64 | } 65 | 66 | InfluenceMap InfluenceMap::operator-(const InfluenceMap& other) const { 67 | auto ret = (*this); 68 | return (ret -= other); 69 | } 70 | 71 | InfluenceMap& InfluenceMap::operator*=(const InfluenceMap& other) { 72 | assert(w == other.w); 73 | assert(h == other.h); 74 | for (int i = 0; i < w * h; i++) { 75 | weights[i] *= other.weights[i]; 76 | } 77 | return (*this); 78 | } 79 | 80 | InfluenceMap InfluenceMap::operator*(const InfluenceMap& other) const { 81 | auto ret = (*this); 82 | return (ret *= other); 83 | } 84 | 85 | InfluenceMap& InfluenceMap::operator/=(const InfluenceMap& other) { 86 | assert(w == other.w); 87 | assert(h == other.h); 88 | 89 | for (int i = 0; i < w * h; i++) { 90 | weights[i] /= other.weights[i]; 91 | } 92 | return (*this); 93 | } 94 | 95 | InfluenceMap InfluenceMap::operator/(const InfluenceMap& other) const { 96 | auto ret = (*this); 97 | return (ret /= other); 98 | } 99 | 100 | InfluenceMap InfluenceMap::operator+(double factor) const { 101 | InfluenceMap ret = InfluenceMap(w, h); 102 | for (int i = 0; i < w * h; i++) { 103 | ret.weights[i] = weights[i] + factor; 104 | } 105 | return ret; 106 | } 107 | 108 | InfluenceMap InfluenceMap::operator-(double factor) const { 109 | InfluenceMap ret = InfluenceMap(w, h); 110 | for (int i = 0; i < w * h; i++) { 111 | ret.weights[i] = weights[i] - factor; 112 | } 113 | return ret; 114 | } 115 | 116 | InfluenceMap InfluenceMap::operator*(double factor) const { 117 | InfluenceMap ret = InfluenceMap(w, h); 118 | for (int i = 0; i < w * h; i++) { 119 | ret.weights[i] = weights[i] * factor; 120 | } 121 | return ret; 122 | } 123 | 124 | void InfluenceMap::operator*=(double factor) { 125 | for (int i = 0; i < w * h; i++) { 126 | weights[i] *= factor; 127 | } 128 | } 129 | 130 | void InfluenceMap::threshold(double value) { 131 | for (int i = 0; i < w * h; i++) 132 | weights[i] = weights[i] >= value ? 1 : 0; 133 | } 134 | 135 | double InfluenceMap::sum() const { 136 | double ret = 0.0; 137 | for (int y = 0; y < h; y++) { 138 | for (int x = 0; x < w; x++) { 139 | ret += weights[y * w + x]; 140 | } 141 | } 142 | return ret; 143 | } 144 | 145 | void InfluenceMap::max(const InfluenceMap& other) { 146 | for (int i = 0; i < w*h; i++) { 147 | weights[i] = std::max(weights[i], other.weights[i]); 148 | } 149 | } 150 | 151 | double InfluenceMap::max() const { 152 | double ret = 0.0; 153 | for (int i = 0; i < w * h; i++) { 154 | ret = std::max(ret, weights[i]); 155 | } 156 | return ret; 157 | } 158 | 159 | double InfluenceMap::maxFinite() const { 160 | double ret = 0.0; 161 | for (auto w : weights) { 162 | if (isfinite(w)) 163 | ret = std::max(ret, w); 164 | } 165 | return ret; 166 | } 167 | 168 | Point2DI InfluenceMap::argmax() const { 169 | double mx = -100000; 170 | Point2DI best(0, 0); 171 | for (int y = 0; y < h; y++) { 172 | for (int x = 0; x < w; x++) { 173 | if (weights[y * w + x] > mx && isfinite(weights[y * w + x])) { 174 | mx = weights[y * w + x]; 175 | best = Point2DI(x, y); 176 | } 177 | } 178 | } 179 | return best; 180 | } 181 | 182 | InfluenceMap InfluenceMap::replace_nonzero(double with) const { 183 | InfluenceMap ret = InfluenceMap(w, h); 184 | for (int i = 0; i < w * h; i++) { 185 | ret.weights[i] = weights[i] != 0 ? with : 0; 186 | } 187 | return ret; 188 | } 189 | 190 | InfluenceMap InfluenceMap::replace_nan(double with) const { 191 | InfluenceMap ret = InfluenceMap(w, h); 192 | for (int i = 0; i < w * h; i++) { 193 | ret.weights[i] = isnan(weights[i]) ? with : weights[i]; 194 | } 195 | return ret; 196 | } 197 | 198 | InfluenceMap InfluenceMap::replace(double value, double with) const { 199 | InfluenceMap ret = InfluenceMap(w, h); 200 | for (int i = 0; i < w * h; i++) { 201 | ret.weights[i] = weights[i] == value ? with : weights[i]; 202 | } 203 | return ret; 204 | } 205 | 206 | void InfluenceMap::addInfluence(double influence, Point2DI pos) { 207 | assert(pos.x >= 0 && pos.x < w && pos.y >= 0 && pos.y < h); 208 | weights[pos.y * w + pos.x] += influence; 209 | } 210 | 211 | void InfluenceMap::addInfluence(double influence, Point2D pos) { 212 | auto p = round_point(pos); 213 | assert(p.first >= 0 && p.first < w && p.second >= 0 && p.second < h); 214 | weights[p.second * w + p.first] += influence; 215 | } 216 | 217 | void InfluenceMap::setInfluence(double influence, Point2D pos) { 218 | auto p = round_point(pos); 219 | assert(p.first >= 0 && p.first < w && p.second >= 0 && p.second < h); 220 | weights[p.second * w + p.first] = influence; 221 | } 222 | 223 | void InfluenceMap::addInfluenceInDecayingCircle(double influence, double radius, Point2D pos) { 224 | int x0, y0; 225 | tie(x0, y0) = round_point(pos); 226 | 227 | int r = (int)ceil(radius); 228 | for (int dx = -r; dx <= r; dx++) { 229 | for (int dy = -r; dy <= r; dy++) { 230 | int x = x0 + dx; 231 | int y = y0 + dy; 232 | if (x >= 0 && y >= 0 && x < w && y < h && dx * dx + dy * dy < radius * radius) { 233 | weights[y * w + x] = influence / (1 + dx * dx + dy * dy); 234 | } 235 | } 236 | } 237 | } 238 | 239 | void InfluenceMap::setInfluenceInCircle(double influence, double radius, Point2D pos) { 240 | int x0, y0; 241 | tie(x0, y0) = round_point(pos); 242 | 243 | int r = (int)ceil(radius); 244 | for (int dx = -r; dx <= r; dx++) { 245 | for (int dy = -r; dy <= r; dy++) { 246 | int x = x0 + dx; 247 | int y = y0 + dy; 248 | if (x >= 0 && y >= 0 && x < w && y < h && dx * dx + dy * dy < radius * radius) { 249 | weights[y * w + x] = influence; 250 | } 251 | } 252 | } 253 | } 254 | 255 | void InfluenceMap::addInfluence(const vector >& influence, Point2D pos) { 256 | int x0, y0; 257 | tie(x0, y0) = round_point(pos); 258 | 259 | int r = influence.size() / 2; 260 | for (int dx = -r; dx <= r; dx++) { 261 | for (int dy = -r; dy <= r; dy++) { 262 | int x = x0 + dx; 263 | int y = y0 + dy; 264 | if (x >= 0 && y >= 0 && x < w && y < h) { 265 | weights[y * w + x] += influence[dx + r][dy + r]; 266 | } 267 | } 268 | } 269 | } 270 | 271 | void InfluenceMap::addInfluenceMultiple(const vector >& influence, Point2D pos, double factor) { 272 | int x0, y0; 273 | tie(x0, y0) = round_point(pos); 274 | 275 | int r = influence.size() / 2; 276 | for (int dy = -r; dy <= r; dy++) { 277 | for (int dx = -r; dx <= r; dx++) { 278 | int x = x0 + dx; 279 | int y = y0 + dy; 280 | if (x >= 0 && y >= 0 && x < w && y < h) { 281 | weights[y * w + x] += influence[dx + r][dy + r] * factor; 282 | } 283 | } 284 | } 285 | } 286 | 287 | void InfluenceMap::maxInfluence(const vector >& influence, Point2D pos) { 288 | int x0, y0; 289 | tie(x0, y0) = round_point(pos); 290 | 291 | int r = influence.size() / 2; 292 | for (int dy = -r; dy <= r; dy++) { 293 | for (int dx = -r; dx <= r; dx++) { 294 | int x = x0 + dx; 295 | int y = y0 + dy; 296 | if (x >= 0 && y >= 0 && x < w && y < h) { 297 | weights[y * w + x] = std::max(weights[y * w + x], influence[dx + r][dy + r]); 298 | } 299 | } 300 | } 301 | } 302 | 303 | void InfluenceMap::maxInfluenceMultiple(const vector >& influence, Point2D pos, double factor) { 304 | int x0, y0; 305 | tie(x0, y0) = round_point(pos); 306 | 307 | int r = influence.size() / 2; 308 | for (int dy = -r; dy <= r; dy++) { 309 | for (int dx = -r; dx <= r; dx++) { 310 | int x = x0 + dx; 311 | int y = y0 + dy; 312 | if (x >= 0 && y >= 0 && x < w && y < h) { 313 | weights[y * w + x] = std::max(weights[y * w + x], influence[dx + r][dy + r] * factor); 314 | } 315 | } 316 | } 317 | } 318 | 319 | vector temporary_buffer; 320 | void InfluenceMap::propagateMax(double decay, double speed, const InfluenceMap& traversable) { 321 | double factor = 1 - decay; 322 | // Diagonal decay 323 | double factor2 = pow(factor, 1.41); 324 | 325 | temporary_buffer.resize(weights.size()); 326 | vector& newWeights = temporary_buffer; 327 | 328 | for (int y = 0; y < h; y++) { 329 | weights[y * w + 0] = weights[y * w + 1]; 330 | weights[y * w + w - 1] = weights[y * w + w - 2]; 331 | } 332 | for (int x = 0; x < w; x++) { 333 | weights[x] = weights[x + w]; 334 | weights[(h - 1) * w + x] = weights[(h - 2) * w + x]; 335 | } 336 | 337 | for (int y = 1; y < h - 1; y++) { 338 | int yw = y * w; 339 | for (int x = 1; x < w - 1; x++) { 340 | int i = yw + x; 341 | if (traversable[i] == 0) { 342 | newWeights[i] = 0; 343 | continue; 344 | } 345 | 346 | double c = 0; 347 | c = std::max(c, weights[i]); 348 | c = std::max(c, weights[i - 1]); 349 | c = std::max(c, weights[i + 1]); 350 | c = std::max(c, weights[i - w]); 351 | c = std::max(c, weights[i + w]); 352 | c *= factor; 353 | 354 | double c2 = 0; 355 | c2 = std::max(c2, weights[i - w - 1]); 356 | c2 = std::max(c2, weights[i - w + 1]); 357 | c2 = std::max(c2, weights[i + w - 1]); 358 | c2 = std::max(c2, weights[i + w + 1]); 359 | c2 *= factor2; 360 | c = std::max(c, c2); 361 | 362 | c = c * speed + (1 - speed) * weights[i]; 363 | newWeights[i] = c; 364 | } 365 | } 366 | 367 | swap(weights, temporary_buffer); 368 | } 369 | 370 | void InfluenceMap::propagateSum(double decay, double speed, const InfluenceMap& traversable) { 371 | // double decayCorrectionFactor = (5*(1-decay) + 4*pow(1-decay,1.41))/9; 372 | // decay *= decay / (1 - decayCorrectionFactor); 373 | double factor = 1 - decay; 374 | // cout << "Estimated decay at " << ((5*(1-decay) + 4*pow(1-decay,1.41))/9) << endl; 375 | // Diagonal decay 376 | // double factor2 = pow(factor, 1.41); 377 | 378 | double gaussianFactor0 = 1; //0.195346; 379 | double gaussianFactor1 = 1; //0.123317; 380 | double gaussianFactor2 = 0.75; //0.077847; 381 | 382 | temporary_buffer.resize(weights.size()); 383 | vector& newWeights = temporary_buffer; 384 | 385 | for (int y = 0; y < h; y++) { 386 | weights[y * w + 0] = weights[y * w + 1]; 387 | weights[y * w + w - 1] = weights[y * w + w - 2]; 388 | } 389 | for (int x = 0; x < w; x++) { 390 | weights[x] = weights[x + w]; 391 | weights[(h - 1) * w + x] = weights[(h - 2) * w + x]; 392 | } 393 | 394 | for (int y = 1; y < h - 1; y++) { 395 | int yw = y * w; 396 | for (int x = 1; x < w - 1; x++) { 397 | int i = yw + x; 398 | 399 | if (traversable[i] == 0) { 400 | newWeights[i] = 0; 401 | continue; 402 | } 403 | 404 | double neighbours = gaussianFactor0; 405 | neighbours += gaussianFactor1 * (traversable[i - 1] + traversable[i + 1] + traversable[i - w] + traversable[i + w]) + gaussianFactor2 * (traversable[i - w - 1] + traversable[i - w + 1] + traversable[i + w - 1] + traversable[i + w + 1]); 406 | 407 | double c = 0; 408 | c += weights[i - 1]; 409 | c += weights[i + 1]; 410 | c += weights[i - w]; 411 | c += weights[i + w]; 412 | c *= gaussianFactor1; 413 | 414 | c += weights[i] * gaussianFactor0; 415 | 416 | double c2 = 0; 417 | c2 += weights[i - w - 1]; 418 | c2 += weights[i - w + 1]; 419 | c2 += weights[i + w - 1]; 420 | c2 += weights[i + w + 1]; 421 | c2 *= gaussianFactor2; 422 | c += c2; 423 | 424 | // To prevent the total weight values from increasing unbounded 425 | if (neighbours > 0) 426 | c /= neighbours; 427 | 428 | c = c * speed + (1 - speed) * weights[i]; 429 | newWeights[i] = c * factor; 430 | } 431 | } 432 | 433 | swap(weights, temporary_buffer); 434 | } 435 | 436 | void InfluenceMap::print() const { 437 | for (int y = 0; y < w; y++) { 438 | for (int x = 0; x < h; x++) { 439 | cout << setfill(' ') << setw(6) << setprecision(1) << fixed << weights[y * w + x] << " "; 440 | } 441 | cout << endl; 442 | } 443 | } 444 | 445 | static default_random_engine generator(time(0)); 446 | 447 | /** Sample a point from this influence map. 448 | * The probability of picking each cell is proportional to its weight. 449 | * The map does not have to be normalized. 450 | */ 451 | Point2DI InfluenceMap::samplePointFromProbabilityDistribution() const { 452 | uniform_real_distribution distribution(0.0, sum()); 453 | double picked = distribution(generator); 454 | 455 | for (int i = 0; i < w * h; i++) { 456 | picked -= weights[i]; 457 | if (picked <= 0) 458 | return Point2DI(i % w, i / w); 459 | } 460 | 461 | // Should in theory not happen, but it may actually happen because of floating point errors 462 | return Point2DI(w - 1, h - 1); 463 | } 464 | --------------------------------------------------------------------------------