Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • stebr364/pycommandcenter
  • starcraft-ai-course/pycommandcenter
  • eriah592/pycommandcenter
  • edvbe696/pycommandcenter
  • dawab699/pycommandcenter
  • hanja189/pycommandcenter
  • teoga849/pycommandcenter
  • musab250/pycommandcenter
  • emibr898/pycommandcenter
  • chrgu102/pycommandcenter
  • axega544/pycommandcenter
  • edvth289/pycommandcenter
  • jonbo278/py-command-center-v-2
13 results
Show changes
Showing
with 720 additions and 131 deletions
......@@ -5,40 +5,80 @@ namespace py = pybind11;
void define_unit(py::module & m)
{
py::class_<Unit>(m, "Unit")
.def_property_readonly("unit_type", &Unit::getType, "The :class:`library.UnitType` of the unit")
.def_property_readonly("position", &Unit::getPosition, "The :class:`library.Point2D` of the unit")
.def_property_readonly("tile_position", &Unit::getTilePosition, "The :class:`library.Point2DI` of the unit")
.def_property_readonly("hit_points", &Unit::getHitPoints)
.def_property_readonly("shields", &Unit::getShields)
.def_property_readonly("energy", &Unit::getEnergy)
.def_property_readonly("player", &Unit::getPlayer)
.def_property_readonly("id", &Unit::getID)
.def_property_readonly("build_percentage", &Unit::getBuildPercentage)
.def_property_readonly("weapon_cooldown", &Unit::getWeaponCooldown)
.def_property_readonly("is_completed", &Unit::isCompleted)
.def_property_readonly("is_being_constructed", &Unit::isBeingConstructed)
.def_property_readonly("is_cloaked", &Unit::isCloaked)
.def_property_readonly("is_flying", &Unit::isFlying)
.def_property_readonly("is_alive", &Unit::isAlive)
.def_property_readonly("is_powered", &Unit::isPowered)
.def_property_readonly("is_idle", &Unit::isIdle)
.def_property_readonly("is_burrowed", &Unit::isBurrowed)
.def_property_readonly("is_valid", &Unit::isValid)
.def_property_readonly("is_training", &Unit::isTraining)
.def_property_readonly("is_constructing", &Unit::isConstructing)
.def("stop", &Unit::stop)
.def("attack_unit", &Unit::attackUnit)
.def("attack_move", &Unit::attackMove)
.def_property_readonly("unit_type", &Unit::getType, "The :class:`commandcenter.UnitType` of the unit.")
.def_property_readonly("position", &Unit::getPosition, "The :class:`commandcenter.Point2D` of the unit.")
.def_property_readonly("tile_position", &Unit::getTilePosition, "The :class:`commandcenter.Point2DI` of the unit.")
.def_property_readonly("hit_points", &Unit::getHitPoints, "Health of the unit.")
.def_property_readonly("shields", &Unit::getShields, "Shield of the unit.")
.def_property_readonly("energy", &Unit::getEnergy, "Energy of the unit.")
.def_property_readonly("player", &Unit::getPlayer, "Returns the constant corresponding to player which this unit belongs to. See :ref:`playerconstants` for more information.")
.def_property_readonly("id", &Unit::getID, "Returns an int to identify the unit. This value does not stay the same for units you lose vision of, so maybe not best idea to use as a way of keeping track of them.")
.def_property_readonly("build_percentage", &Unit::getBuildPercentage, "Gives progress under construction. Range [0.0, 1.0]. 1.0 == finished.")
.def_property_readonly("weapon_cooldown", &Unit::getWeaponCooldown, "Time remaining for a weapon on cooldown.")
.def_property_readonly("is_completed", &Unit::isCompleted, "Returns build_progress >= 1.")
.def_property_readonly("is_being_constructed", &Unit::isBeingConstructed, "Returns (build_progress > 0 and not is_completed).")
.def_property_readonly("is_cloaked", &Unit::isCloaked, "If the unit is cloaked.")
.def_property_readonly("is_flying", &Unit::isFlying, "If the unit is flying.")
.def_property_readonly("buffs", &Unit::buffs, "Returns a list buffs on this unit. Only valid for this player's units.")
.def_property_readonly("is_alive", &Unit::isAlive, "Whether the unit is alive or not.")
.def_property_readonly("is_powered", &Unit::isPowered, "Whether the unit is powered by a pylon.")
.def_property_readonly("is_idle", &Unit::isIdle, "Whether the unit has any orders.")
.def_property_readonly("is_burrowed", &Unit::isBurrowed, "Whether the unit is burrowed.")
.def_property_readonly("is_valid", &Unit::isValid, "Whether the unit is valid")
.def_property_readonly("is_training", &Unit::isTraining, "Returns True if the unit is a building and is training another unit.")
.def_property_readonly("is_blip", &Unit::isBlip, "Returns true if unit is a 'blip' - a ping on the map.")
.def_property_readonly("target", &Unit::getTarget, "Returns target unit if one exists, otherwise return itself.")
.def_property_readonly("has_target", &Unit::hasTarget, "Returns True if the target has a valid target and False otherwise.")
.def_property_readonly("max_hit_points", &Unit::getMaxHitPoints, "Returns the max health.")
.def_property_readonly("progress", &Unit::getProgress, "Returns the progress of currently used ability (-1 if not using ability).")
.def_property_readonly("progression_list", &Unit::getAllProgress, "Returns a list containing the progression on all the processes in order. Empty list if no process exists.")
.def_property_readonly("current_ability_id", &Unit::getCurrentAbilityID, "The AbilityID of currently used ability.")
.def_property_readonly("facing", &Unit::getFacing, "Direction the unit faces in radians (1 radian == 57.296 degrees).")
.def_property_readonly("radius", &Unit::getRadius, "Returns the radius of the unit.")
.def_property_readonly("is_carrying_minerals", &Unit::isCarryingMinerals, "Returns if this unit is currently holding minerals.")
.def_property_readonly("is_carrying_gas", &Unit::isCarryingGas, "Returns if this unit is currently holding gas.")
.def_property_readonly("gas_left_in_refinery", &Unit::gasLeftInGeyser, "Amount of vespene left in the the refinery.")
.def_property_readonly("minerals_left_in_mineralfield", &Unit::mineralsLeftInMineralfield, "Amount of minerals if the unit is a mineral field.")
.def_property_readonly("owner", &Unit::getOwner, "Which player owns a unit. Not same as player; 'player' originates from IDA implementation whilst 'owner' originates from StarCraft II.")
.def_property_readonly("max_shields", &Unit::maxShields, "Max shield of the unit.")
.def_property_readonly("max_energy", &Unit::maxEnergy, "Max energy of the unit.")
.def("hold_position", &Unit::holdPosition, "Unit will hold its position.")
.def("patrol", py::overload_cast<const CCPosition &>(&Unit::patrol, py::const_), "Unit will patrol back and forth from its current location to the given Point2D.")
.def("stop_dance", &Unit::stopDance, "Unit will Stop and dance.")
.def("stop", &Unit::stop, "Unit will stop.")
.def("attack_unit", &Unit::attackUnit, "Unit will attack the provided unit.")
.def("attack_move", &Unit::attackMove, "Moves to provided Point2D location. If an enemy is seen on the way it will try to attack it. Will chase after the enemy as long as it is visible.")
.def("ability", py::overload_cast<sc2::AbilityID>(&Unit::ability, py::const_))
.def("ability", py::overload_cast<sc2::AbilityID, const CCPosition &>(&Unit::ability, py::const_))
.def("ability", py::overload_cast<sc2::AbilityID, const Unit &>(&Unit::ability, py::const_), "Call an ability directly, different abilities has different targets. Some target the unit itself (no argument), target a point (Point2D as argument) and some target a Unit (instance of Unit as argument).")
.def("move", py::overload_cast<const CCPosition &>(&Unit::move, py::const_))
.def("move", py::overload_cast<const CCTilePosition &>(&Unit::move, py::const_))
.def("right_click", &Unit::rightClick, "Same as right-clicking in the game, for example making workers mine minerals")
.def("repair", &Unit::repair)
.def("build", &Unit::build, "Build unit of type building_type at given position", "building_type"_a, "position"_a)
.def("build_target", &Unit::buildTarget, "Build building_type on top of target Unit, useful for building refineries", "building_type"_a, "target"_a)
.def("train", &Unit::train, "Train unit of type", "unit_type"_a)
.def("morph", &Unit::morph, "Morph into type of unit_type", "unit_type"_a)
.def("research", &Unit::research, "Research the given :class:`library.UPGRADE_ID`", "upgrade_id"_a)
.def("move", py::overload_cast<const CCTilePosition &>(&Unit::move, py::const_), "Move the unit to the given point, the point being an instance of either :class:`commandcenter.Point2D` or :class:`commandcenter.Point2DI`.")
.def("right_click", &Unit::rightClick, "Same as right-clicking in the game, for example making workers mine minerals.")
.def("repair", &Unit::repair, "Right-clicks on the provided unit in order to repair it.")
.def("build", &Unit::build, "Build unit of type building_type at given position.", "building_type"_a, "position"_a)
.def("build_target", &Unit::buildTarget, "Build building_type on top of target Unit, useful for building refineries.", "building_type"_a, "target"_a)
.def("train", &Unit::train, "Train unit of type.", "unit_type"_a)
.def("morph", &Unit::morph, "Morph into type of unit_type.", "unit_type"_a)
.def("research", &Unit::research, "Research the given :class:`commandcenter.UPGRADE_ID`.", "upgrade_id"_a)
.def("is_constructing", &Unit::isConstructing, "unit_type"_a, "Returns true if the unit is currently constructing another unit of type `unit_type`. Note that `unit_type` needs to be an instance of :class:`commandcenter.UnitType`.")
.def("__hash__", [](const Unit & unit) { return std::hash<const sc2::Unit *>{}(unit.getUnitPtr()); })
.def(py::self == py::self)
.def("__repr__", [](const Unit & unit) { return "<Unit of type: '" + unit.getType().getName() + "'>"; });
.def("__repr__", [](const Unit & unit) { return "<Unit of type: '" + unit.getType().getName() + "'>"; })
.doc() = R"(
An instance of the class Unit represents one unit in the game. The units are
not limited to moveable units, but every entity which is not part of the
background is a unit. For example, the minerals and geysers are units as
well as all buildings.
For all possible types of units see the enum :class:`commandcenter.UNIT_TYPEID`.
Some types of objects are almost the same, for example there are many types
of mineral deposits, but all of them are mineable. This is one of the
motivations behind the :class:`commandcenter.UnitType` which aims to to make the
list of types more manageable. The UnitType can be accessed by the
:any:`Unit.unit_type` property.
It is possible to use Unit as keys in a dictionary, which might be helpful
for bookkeeping.
)" ;
}
......@@ -5,42 +5,60 @@ namespace py = pybind11;
void define_unittype(py::module & m)
{
py::class_<UnitType>(m, "UnitType")
.def(py::init<const sc2::UnitTypeID &, IDABot &>())
.def(py::self == py::self)
.def(py::init([](const sc2::UnitTypeID & type, IDABot & bot) {
return UnitType(type, bot, bot);
}))
.def_property_readonly("unit_typeid", [](UnitType & unit_type) { return static_cast<sc2::UNIT_TYPEID>(unit_type.getAPIUnitType()); })
.def_property_readonly("name", &UnitType::getName)
.def_property_readonly("race", &UnitType::getRace)
.def_property_readonly("is_valid", &UnitType::isValid)
.def_property_readonly("is_building", &UnitType::isBuilding)
.def_property_readonly("is_combat_unit", &UnitType::isCombatUnit, "The unit is not any of the following: worker, supply provider, building, larva, egg")
.def_property_readonly("is_supply_provider", &UnitType::isSupplyProvider)
.def_property_readonly("is_resource_depot", &UnitType::isResourceDepot)
.def_property_readonly("is_refinery", &UnitType::isRefinery)
.def_property_readonly("is_detector", &UnitType::isDetector)
.def_property_readonly("is_geyser", &UnitType::isGeyser)
.def_property_readonly("is_mineral", &UnitType::isMineral)
.def_property_readonly("is_worker", &UnitType::isWorker)
.def_property_readonly("is_morphed_building", &UnitType::isMorphedBuilding)
.def_property_readonly("name", &UnitType::getName, "The name of the unit as a string.")
.def_property_readonly("race", &UnitType::getRace, "The race the unit belongs to.")
.def_property_readonly("movement_speed", &UnitType::getMovementSpeed, "Movement speed of unit type.")
.def_property_readonly("sight_range", &UnitType::getSightRange, "Range the unit reveals vision.")
.def_property_readonly("required_structure", &UnitType::getRequiredStructure, "Structure required to build this unit. (Or any with the same tech_alias.)")
.def_property_readonly("is_valid", &UnitType::isValid, "Its a valid unit type.")
.def_property_readonly("is_building", &UnitType::isBuilding, "Is this unit type a building or not.")
.def_property_readonly("is_combat_unit", &UnitType::isCombatUnit, "The unit is not any of the following, worker, supply provider, building, larva, egg.")
.def_property_readonly("is_supply_provider", &UnitType::isSupplyProvider, "The unit provides supply.")
.def_property_readonly("is_resource_depot", &UnitType::isResourceDepot, "The unit is one of the following, hatchery, lair, hive, commandcenter, orbialtcommand, planetaryfortress, nexus.")
.def_property_readonly("is_refinery", &UnitType::isRefinery, "The unit is one of the following (depending on race), refinery, assimilator, extractor.")
.def_property_readonly("is_detector", &UnitType::isDetector, "Is this a unit type which is a detector unit.")
.def_property_readonly("is_geyser", &UnitType::isGeyser, "Is the unit type a geyser.")
.def_property_readonly("is_mineral", &UnitType::isMineral, "Is the unit type a mineralfield.")
.def_property_readonly("is_worker", &UnitType::isWorker, "Is the unit type a unit which is a worker.")
.def_property_readonly("is_morphed_building", &UnitType::isMorphedBuilding, "Has this building been morphed.")
// Not implemented in CommandCenter
//.def_property_readonly("can_attack", &UnitType::canAttack)
//.def_property_readonly("can_Move", &UnitType::canMove)
.def_property_readonly("is_addon", &UnitType::isAddon)
.def_property_readonly("attack_range", &UnitType::getAttackRange)
.def_property_readonly("tile_width", &UnitType::tileWidth)
.def_property_readonly("tile_height", &UnitType::tileHeight)
.def_property_readonly("supply_provided", &UnitType::supplyProvided)
.def_property_readonly("supply_required", &UnitType::supplyRequired)
.def_property_readonly("mineral_price", &UnitType::mineralPrice)
.def_property_readonly("gas_price", &UnitType::gasPrice)
.def_property_readonly("is_overlord", &UnitType::isOverlord)
.def_property_readonly("is_larva", &UnitType::isLarva)
.def_property_readonly("is_egg", &UnitType::isEgg)
.def_property_readonly("is_queen", &UnitType::isQueen)
.def_property_readonly("is_tank", &UnitType::isTank)
.def_property_readonly("is_addon", &UnitType::isAddon, "Is this unit type a add-on.")
.def_property_readonly("can_attack_ground", &UnitType::canAttackGound, "True if this unit can attack ground units.")
.def_property_readonly("can_attack_air", &UnitType::canAttackAir, "True if this unit can attack air units.")
.def_property_readonly("attack_range", &UnitType::getAttackRange, "Returns the attack range of the unit type.")
.def_property_readonly("attack_damage", &UnitType::getAttackDamage, "Returns the base attack damage of the unit type.")
.def_property_readonly("tile_width", &UnitType::tileWidth, "Returns the width of the unit type.")
.def_property_readonly("tile_height", &UnitType::tileHeight, "Returns the height of the unit type.")
.def_property_readonly("supply_provided", &UnitType::supplyProvided, "Amount of supply provided by the unit type.")
.def_property_readonly("supply_required", &UnitType::supplyRequired, "Amount of supply required for the unit type.")
.def_property_readonly("mineral_price", &UnitType::mineralPrice, "Mineral price of the unit type.")
.def_property_readonly("gas_price", &UnitType::gasPrice, "Gas price of the unit type.")
.def_property_readonly("is_overlord", &UnitType::isOverlord, "Is this a ZERG_OVERLORD.")
.def_property_readonly("is_larva", &UnitType::isLarva, "is this a ZERG_LARVA.")
.def_property_readonly("is_egg", &UnitType::isEgg, "is this a ZERG_EGG.")
.def_property_readonly("is_queen", &UnitType::isQueen, "is this a ZERG_QUEEN.")
.def_property_readonly("is_tank", &UnitType::isTank, "is this a TERRAN_SIEGETANK or TERRAN_SIEGETANKSIEGED.")
.def_property_readonly("get_equivalent_units", &UnitType::getEquivalentUnits, "Units this is equivalent to in terms of satisfying tech requirements.")
.def_property_readonly("required_attached", &UnitType::requiredAttached, "Whether tech_requirement is an add-on.")
.def_property_readonly("build_time", &UnitType::getBuildTime, "How long the unit takes to build.")
.def("__hash__", [](const UnitType & unit_type) { return std::hash<CCUnitID>{}(unit_type.getAPIUnitType()); })
.def(py::self == py::self)
.def("__repr__", [](const UnitType & unit_type) { return "<UnitType: '" + unit_type.getName() + "'>"; });
.def("__repr__", [](const UnitType & unit_type) { return "<UnitType: '" + unit_type.getName() + "'>"; })
.def("is", &UnitType::is)
.def("__lt__", &UnitType::operator<, py::is_operator())
.def("__eq__", &UnitType::operator==, py::is_operator())
.doc() = R"(
Wrapper for :class:`commandcenter.UNIT_TYPEID`. Represents a type of unit in the game.
NOTE: A lot of functions utilize checks that require the game to be running.
Therefore if you get an unexpected segmentation fault, it is likely due to the game not being in a running state.
)";
// Not implemented in CommandCenter
//.def("whatBuilds", &UnitType::whatBuilds);
}
\ No newline at end of file
}
......@@ -7,7 +7,7 @@ void define_util(py::module & mod)
py::module m = mod.def_submodule("util");
/*
TODO:
TODO: Implement and document these functions
Maybe should be included in Point instead?
CCTilePosition GetTilePosition(const CCPosition & pos);
CCPosition GetPosition(const CCTilePosition & tile);
......@@ -22,4 +22,7 @@ void define_util(py::module & mod)
m.def("dist", py::overload_cast<const Unit &, const Unit &>(&Util::Dist));
m.def("dist", py::overload_cast<const Unit &, const CCPosition &>(&Util::Dist));
m.def("dist", py::overload_cast<const CCPosition &, const CCPosition &>(&Util::Dist));
m.doc() = R"(
The util module provides utility functions for the library.
)";
}
\ No newline at end of file
......@@ -2,12 +2,13 @@
namespace py = pybind11;
PYBIND11_MODULE(library, m)
PYBIND11_MODULE(commandcenter, m)
{
m.doc() = "Python API for playing Starcraft II";
define_typeenums(m);
define_unit(m);
define_replay_unit(m);
define_unittype(m);
define_util(m);
define_point(m);
......@@ -17,40 +18,102 @@ PYBIND11_MODULE(library, m)
define_map_tools(m);
define_building_placer(m);
// Note: This is not sc2::Coordinator but a small wrapper class which
// overrides the constructor of sc2::Coordinator, see library.h.
py::class_<Coordinator>(m, "Coordinator")
.def(py::init())
.def(py::init<std::string>())
.def("set_participants", &sc2::Coordinator::SetParticipants, "participants"_a)
.def("launch_starcraft", &sc2::Coordinator::LaunchStarcraft)
.def("start_game", &sc2::Coordinator::StartGame, "map_path"_a)
.def("update", &sc2::Coordinator::Update);
py::enum_<sc2::Race>(m, "Race")
.def("connect", &sc2::Coordinator::Connect, "port"_a)
.def("update", &sc2::Coordinator::Update)
.def("all_games_ended", &sc2::Coordinator::AllGamesEnded)
.def("join_game", &sc2::Coordinator::JoinGame)
.def("set_real_time", &sc2::Coordinator::SetRealtime)
.def("load_replay_list",&sc2::Coordinator::SetReplayPath, "replay_path"_a)
.def("add_replay_observer",&sc2::Coordinator::AddReplayObserver, "replay_observer"_a)
.def("set_replay_perspective",&sc2::Coordinator::SetReplayPerspective, "perspective"_a)
.def("leave_game", &sc2::Coordinator::LeaveGame)
.doc() = R"(
The Coordinator class is a wrapper class that provides a simplified interface for managing and controlling Starcraft II games. It allows you to set up game parameters, launch the Starcraft II application, start a game on a specific map, connect to the game, update the game state, and perform various game-related actions.
Here are some key functionalities provided by the Coordinator class:
* Initialization: The Coordinator class has multiple constructors that allow you to create an instance of the class. You can initialize it without any arguments or provide a string argument representing the path to the Starcraft II executable.
* Setting Participants: You can use the set_participants method to set the participants for the game. This method takes a list of participants as an argument, where each participant is defined by their race and other relevant information.
* Launching Starcraft II: The launch_starcraft method launches the Starcraft II application.
* Starting a Game: The start_game method starts a game on a specific map. You need to provide the path to the map as an argument.
* Connecting to the Game: The connect method allows you to connect to the game by specifying the port number.
* Updating the Game State: The update method updates the game state, including the positions and statuses of units, resources, and other game elements.
* Game Control: The all_games_ended method checks if all games have ended. The join_game method allows you to join a game. The set_real_time method sets the game to real-time mode. The leave_game method allows you to leave the current game.
These are just some of the methods provided by the Coordinator class. It serves as a central control point for managing Starcraft II games and provides an interface for interacting with the game engine.
)";
py::enum_<sc2::Race>(m, "Race", "The Race enum in the provided code represents the different races in the Starcraft II game. It is used to define the race of a player or a unit. \
In Starcraft II, each race has its own unique units, buildings, and abilities. The Race enum allows you to specify the race of a player or a unit, which can be useful for various game-related operations and strategies. \
For example, if you want to set the race of a player to Terran, you can use the Race.Terran value. Similarly, if you want to check the race of a unit, you can compare its race with the enum values.")
.value("Terran", sc2::Race::Terran)
.value("Zerg", sc2::Race::Zerg)
.value("Protoss", sc2::Race::Protoss)
.value("Random", sc2::Race::Random);
//DON'T USE .doc on enum
m.attr("PLAYER_SELF") = py::int_((int) Players::Self);
m.attr("PLAYER_ENEMY") = py::int_((int) Players::Enemy);
m.attr("PLAYER_NEUTRAL") = py::int_((int) Players::Neutral);
m.attr("PLAYER_ALLY") = py::int_((int) Players::Ally);
/*
TODO: BuffID is not defined, defined as following by Blizzard:
typedef SC2Type<BUFF_ID> BuffID;
*/
py::class_<sc2::BuffID>(m, "BuffID")
.def(py::init<sc2::BUFF_ID>())
.doc() = R"(
The BuffID class is a wrapper class that represents the BuffID enum in the Starcraft II game. It is used to define the different buffs that can be applied to units in the game.
In Starcraft II, buffs are temporary effects that modify the behavior or attributes of units. The BuffID class allows you to specify a particular buff by its ID, which can be useful for various game-related operations and strategies.
For example, if you want to apply a specific buff to a unit, you can use the BuffID class to represent the buff and apply it to the unit. Similarly, if you want to check if a unit has a particular buff, you can compare its ID with the enum values.
)";
py::implicitly_convertible<sc2::BUFF_ID, sc2::BuffID>();
py::class_<sc2::UnitTypeID>(m, "UnitTypeID")
.def(py::init<sc2::UNIT_TYPEID>());
.def(py::init<sc2::UNIT_TYPEID>())
.def("__eq__", [](const sc2::UnitTypeID &value, sc2::UNIT_TYPEID &value2) { return value == value2; })
.doc() = R"(
The UnitTypeID class is a wrapper class that represents the UnitTypeID enum in the Starcraft II game. It is used to define the different types of units that exist in the game.
In Starcraft II, each unit has a unique type ID that identifies its unit type. The UnitTypeID class allows you to specify a particular unit type by its ID, which can be useful for various game-related operations and strategies.
For example, if you want to create a specific unit, you can use the UnitTypeID class to represent the unit type and create the unit. Similarly, if you want to check the type of a unit, you can compare its ID with the enum values.
)";
py::implicitly_convertible<sc2::UNIT_TYPEID, sc2::UnitTypeID>();
py::class_<sc2::UpgradeID>(m, "UpgradeID")
.def(py::init<sc2::UPGRADE_ID>());
.def(py::init<sc2::UPGRADE_ID>())
.doc() = R"(
The UpgradeID class is a wrapper class that represents the UpgradeID enum in the Starcraft II game. It is used to define the different upgrades that can be researched in the game.
In Starcraft II, upgrades are enhancements that improve the attributes or abilities of units and buildings. The UpgradeID class allows you to specify a particular upgrade by its ID, which can be useful for various game-related operations and strategies.
For example, if you want to research a specific upgrade, you can use the UpgradeID class to represent the upgrade and research it. Similarly, if you want to check if a unit or building has a particular upgrade, you can compare its ID with the enum values.
)";
py::implicitly_convertible<sc2::UPGRADE_ID, sc2::UpgradeID>();
py::class_<sc2::EffectID>(m, "EffectID")
.def(py::init<sc2::EFFECT_ID>())
.doc() = R"(
The EffectID class is a wrapper class that represents the EffectID enum in the Starcraft II game. It is used to define the different effects that can be applied to units or abilities in the game.
In Starcraft II, effects are special abilities or attributes that modify the behavior or properties of units or abilities. The EffectID class allows you to specify a particular effect by its ID, which can be useful for various game-related operations and strategies.
For example, if you want to apply a specific effect to a unit or ability, you can use the EffectID class to represent the effect and apply it. Similarly, if you want to check if a unit or ability has a particular effect, you can compare its ID with the enum values.
)";
py::implicitly_convertible<sc2::EFFECT_ID, sc2::EffectID>();
py::class_<sc2::AbilityID>(m, "AbilityID")
.def(py::init<sc2::ABILITY_ID>());
.def(py::init<sc2::ABILITY_ID>())
.doc() = R"(
The AbilityID class is a wrapper class that represents the AbilityID enum in the Starcraft II game. It is used to define the different abilities that units and buildings can use in the game.
In Starcraft II, abilities are special actions or commands that units and buildings can perform. The AbilityID class allows you to specify a particular ability by its ID, which can be useful for various game-related operations and strategies.
For example, if you want to use a specific ability with a unit or building, you can use the AbilityID class to represent the ability and use it. Similarly, if you want to check if a unit or building has a particular ability, you can compare its ID with the enum values.
)";
py::implicitly_convertible<sc2::ABILITY_ID, sc2::AbilityID>();
py::implicitly_convertible<sc2::AbilityID, sc2::ABILITY_ID>();
......@@ -59,22 +122,94 @@ PYBIND11_MODULE(library, m)
.def(py::init());
// IDABot is a specialization of Agent
py::class_<IDABot, PyIDABot, sc2::Agent>(m, "IDABot")
.def(py::init())
.def("on_game_start", &IDABot::OnGameStart)
.def("on_step", &IDABot::OnStep)
.def("get_all_units", &IDABot::GetAllUnits, "Returns a list of all units")
.def("get_my_units", &IDABot::GetMyUnits, "Returns a list of all units beloning to the player")
.def("get_player_race", &IDABot::GetPlayerRace)
.def_property_readonly("base_location_manager", &IDABot::Bases)
.def_property_readonly("tech_tree", &IDABot::GetTechTree)
.def_property_readonly("map_tools", &IDABot::Map)
.def_property_readonly("building_placer", &IDABot::GetBuildingPlacer)
.def_property_readonly("start_location", &IDABot::GetStartLocation, "CCPosition representing the start location")
.def_property_readonly("minerals", &IDABot::GetMinerals, "How much minerals we currently have")
.def_property_readonly("current_supply", &IDABot::GetCurrentSupply, "How much supply we are currently using")
.def_property_readonly("max_supply", &IDABot::GetMaxSupply, "How much supply we can currently use")
.def_property_readonly("gas", &IDABot::GetGas, "How much gas we currently have");
py::class_<IDABot, PyIDABot, sc2::Agent>(m, "IDABot")
.def(py::init())
.def("on_game_start", &IDABot::OnGameStart, R"(The on_game_start function is a method defined in the IDABot class. It is called when a game starts in Starcraft II.
This function is responsible for initializing the bot and performing any necessary setup before the game begins.
This can include setting up initial game state, gathering information about the map and opponents, and making any initial decisions or strategies.)")
.def("on_step", &IDABot::OnStep, R"(The on_step function in the IDABot class is a method that is called every game step (or frame) during a Starcraft II match.
This function is crucial for implementing the bot's logic that needs to be executed continuously throughout the game.
It's where the bot evaluates the current game state, makes decisions, and issues commands to units.)")
.def("on_game_end", &IDABot::OnGameEnd, R"(The on_game_end function is a method defined in the IDABot class. It is called when a game ends in Starcraft II.
This function can for example be used to save a replay and log performance information.)")
.def("send_chat", &IDABot::SendChat, "Sends the string 'message' to the game chat.", "message"_a)
.def("get_all_units", &IDABot::GetAllUnits, "Returns a list of all visible units, including minerals and geysers.")
.def("get_my_units", &IDABot::GetMyUnits, "Returns a list of all your units.")
.def("get_player_race", &IDABot::GetPlayerRace, "Returns the players race, useful if you play Race.Random.")
.def("debug_create_unit", &IDABot::DebugCreateUnit, "This method creates the nr (INT) of units on the position :class:`commandcenter.Point2D`, the unit belongs to the Player Constant.", "unit_type"_a, "p"_a, "player_id"_a = 0, "count"_a = 1)
.def("debug_kill_unit", &IDABot::DebugKillUnit, "Kill the unit from debug mode.")
.def("debug_show_map", &IDABot::DebugShowMap, "Show the entire map through debug mode.")
.def("debug_fast_build", &IDABot::DebugFastBuild, "Set build time in game to 1 through debug mode.")
.def("debug_enemy_control", &IDABot::DebugEnemyControl, "Control the enemy through debug mode.")
.def("debug_ignore_food", &IDABot::DebugIgnoreFood, "Ignore the food in game through debug mode.")
.def("debug_ignore_resource_cost", &IDABot::DebugIgnoreResourceCost, "Ignore the resource cost in game, making, everything cost zero resources through debug mode.")
.def("debug_give_all_resources", &IDABot::DebugGiveAllResources, "Set the mineral and vespene gas to 5000 through debug mode.")
.def("debug_god_mode", &IDABot::DebugGodMode, "Give the player god mode.")
.def("debug_ignore_mineral", &IDABot::DebugIgnoreMineral, "Ignore the mineral cost in game through debug mode.")
.def("debug_no_cooldowns", &IDABot::DebugNoCooldowns, "Deactive cooldowns (Basically setting them to 0) through debug mode.")
.def("debug_give_all_tech", &IDABot::DebugGiveAllTech, "Give all the tech to the player through debug mode.")
.def("debug_give_all_upgrades", &IDABot::DebugGiveAllUpgrades, "Give all the upgrades to the player through debug mode.")
.def("debug_set_score", &IDABot::DebugSetScore, "Set the Players score in game through debug mode.")
.def("debug_end_game", &IDABot::DebugEndGame, "End the game through debug mode. If the Boolean is True then victory. If False, defeat.")
.def("debug_set_energy", &IDABot::DebugSetEnergy, "Set the energy on a unit through debug mode.")
.def("debug_set_life", &IDABot::DebugSetLife, "Set the life on a unit through debug mode.")
.def("debug_set_shields", &IDABot::DebugSetShields, "Set the shields on a unit through debug mode.")
.def("get_enemy_base_location", &IDABot::GetEnemyBaseLocations, "Return the CCposition of the enemy base.")
.def("move_camera", &IDABot::CameraMove, "Move the camera to the position.", "p"_a)
.def("has_creep", &IDABot::HasCreep, "Returns true if there is creep at position p.", "p"_a)
.def("ability_for_upgrade", &IDABot::abilityForUpgrade, "Ability that researches this upgrade.", "upgrade"_a)
.def("upgrade_mineral_cost", &IDABot::UpgradeMineralCost, "Mineral cost of researching the upgrade.", "upgrade"_a)
.def("upgrade_gas_cost", &IDABot::UpgradeGasCost, "Vespene/gas cost of researching the upgrade.", "upgrade"_a)
.def("upgrade_research_time", &IDABot::UpgradeResearchTime, "Time in GameLoops to research this upgrade.", "upgrade"_a)
.def("effect_radius", &IDABot::RadiusEffect, "Size of the circle the effect impacts.", "effect"_a)
.def("save_replay", &IDABot::SaveReplay, "Saves the game as an SC2Replay. Returns whether successful.", "path"_a)
.def_property_readonly("base_location_manager", &IDABot::Bases, "An instance of the class :class:`commandcenter.BaseLocationManager`. ")
.def_property_readonly("tech_tree", &IDABot::GetTechTree, "An instance of the class :class:`commandcenter.TechTree`.")
.def_property_readonly("map_tools", &IDABot::Map, "An instance of the class :class:`commandcenter.MapTools`.")
.def_property_readonly("building_placer", &IDABot::GetBuildingPlacer, "An instance of the class :class:`commandcenter.BuildingPlacer`.")
.def_property_readonly("start_location", &IDABot::GetStartLocation, "CCPosition representing the start location, note that it is the depot position that is returned.")
.def_property_readonly("start_locations", &IDABot::GetStartLocations, "List of CCPositions representing the start locations, note that it is the depot positions and not the center positions.")
.def_property_readonly("minerals", &IDABot::GetMinerals, "How much minerals we currently have.")
.def_property_readonly("current_supply", &IDABot::GetCurrentSupply, "How much supply we are currently using.")
.def_property_readonly("max_supply", &IDABot::GetMaxSupply, "How much supply we can currently use.")
.def_property_readonly("gas", &IDABot::GetGas, "How much gas we currently have.")
.def_property_readonly("current_frame", &IDABot::GetCurrentFrame, "Which frame we are currently on.")
.def_property_readonly("score", &IDABot::GetScore, "The current score.")
.def_property_readonly("player_result", &IDABot::GetPlayerResults, "The match result for the player.")
.doc() = R"(This is a stripped-down version of a bot. It contains all available managers and basic methods.)";
// API extended summer 2020
py::class_<sc2::ReplayObserver>(m, "ReplayObserver")
.def(py::init());
py::class_<IDAReplayObserver, PyReplayObserver, sc2::ReplayObserver>(m, "IDAReplayObserver")
.def(py::init())
.def("on_game_start", &IDAReplayObserver::OnGameStart)
.def("on_step", &IDAReplayObserver::OnStep)
.def("on_game_end", &IDAReplayObserver::OnGameEnd)
.def("get_all_units", &IDAReplayObserver::GetAllUnits, "Returns a list of all units")
.def("get_player_race", &IDAReplayObserver::GetPlayerRace,"player_id"_a)
.def("get_replay_path", &IDAReplayObserver::GetReplayPath)
.def("get_result_for_player", &IDAReplayObserver::GetResultForPlayer, "player_id"_a)
.def("on_unit_destroyed", &IDAReplayObserver::OnReplayUnitDestroyed, "unit"_a)
.def("on_unit_created", &IDAReplayObserver::OnReplayUnitCreated, "unit"_a)
.def_property_readonly("tech_tree", &IDAReplayObserver::GetTechTree)
;
py::enum_<sc2::GameResult>(m, "GameResult")
.value("Win", sc2::GameResult::Win)
.value("Loss", sc2::GameResult::Loss)
.value("Tie", sc2::GameResult::Tie)
.value("Undecided", sc2::GameResult::Undecided);
py::class_<sc2::PlayerSetup>(m, "PlayerSetup");
......@@ -90,8 +225,17 @@ PYBIND11_MODULE(library, m)
.value("CheatMoney", sc2::Difficulty::CheatMoney)
.value("CheatInsane", sc2::Difficulty::CheatInsane);
m.def("create_participants", &sc2::CreateParticipant, "Create participant from bot", "race"_a, "bot"_a);
m.def("create_computer", &sc2::CreateComputer, "Create participant from built-in Starcraft computer", "race"_a, "difficulty"_a);
py::enum_<sc2::AIBuild>(m, "AIBuild")
.value("RandomBuild", sc2::AIBuild::RandomBuild)
.value("Rush", sc2::AIBuild::Rush)
.value("Timing", sc2::AIBuild::Timing)
.value("Power", sc2::AIBuild::Power)
.value("Macro", sc2::AIBuild::Macro)
.value("Air", sc2::AIBuild::Air);
m.def("create_participants", &sc2::CreateParticipant, "Create participant from bot", "race"_a, "bot"_a, "player_name"_a = "");
m.def("create_computer", &sc2::CreateComputer, "Create participant from built-in Starcraft computer", "race"_a, "difficulty"_a, "ai_build"_a = sc2::AIBuild::RandomBuild, "player_name"_a = "");
/*
py::class_<BuildDescription>(m, "BuildDescription")
......
......@@ -3,6 +3,7 @@
#include <pybind11/pybind11.h>
#include <sc2api/sc2_api.h>
#include "../src/IDABot.h"
#include "../src/IDAReplayObserver.h"
#include <iostream>
#include <pybind11/stl.h> /* Automatic conversion from std::vector to Python lists */
#include <pybind11/operators.h> /* Convenient operator support */
......@@ -14,12 +15,26 @@ using namespace pybind11::literals;
class Coordinator : public sc2::Coordinator
{
public:
// TODO: We might not always want default value when we run on Linux
Coordinator() : sc2::Coordinator()
{
char *argv[] = { "executable", NULL};
int argc = sizeof(argv) / sizeof(char*) - 1;
LoadSettings(argc, argv);
std::vector<std::string> arguments = { "pycommandcenter" };
CustomLoadSettings(arguments);
}
Coordinator(std::string path) : sc2::Coordinator()
{
std::vector<std::string> arguments = { "pycommandcenter", "-e", path };
CustomLoadSettings(arguments);
}
void CustomLoadSettings(std::vector<std::string> arguments)
{
std::vector<char*> argv;
for (const auto& arg : arguments)
argv.push_back((char*)arg.data());
argv.push_back(nullptr);
LoadSettings(argv.size() - 1, argv.data());
}
};
......@@ -45,12 +60,78 @@ public:
OnStep
);
}
void OnGameEnd() override
{
PYBIND11_OVERLOAD_NAME(
void,
IDABot,
"on_game_end",
OnGameEnd
);
}
};
class PyReplayObserver : public IDAReplayObserver
{
public:
using IDAReplayObserver::IDAReplayObserver;
void OnGameStart() override
{
PYBIND11_OVERLOAD_NAME(
void,
IDAReplayObserver,
"on_game_start",
OnGameStart
);
}
void OnStep() override
{
PYBIND11_OVERLOAD_NAME(
void,
IDAReplayObserver,
"on_step",
OnStep
);
}
void OnGameEnd() override
{
PYBIND11_OVERLOAD_NAME(
void,
IDAReplayObserver,
"on_game_end",
OnGameEnd
);
}
void OnReplayUnitDestroyed(const ReplayUnit *unit) override
{
PYBIND11_OVERLOAD_NAME(
void,
IDAReplayObserver,
"on_unit_destroyed",
OnReplayUnitDestroyed,
unit
);
}
void OnReplayUnitCreated(const ReplayUnit *unit) override
{
PYBIND11_OVERLOAD_NAME(
void,
IDAReplayObserver,
"on_unit_created",
OnReplayUnitCreated,
unit
);
}
};
// The functions below are all defined in different .cpp files, in order
// to keep compilation snappy
void define_typeenums(pybind11::module & m);
void define_unit(pybind11::module & m);
void define_replay_unit(pybind11::module & m);
void define_unittype(pybind11::module &m);
void define_util(pybind11::module &m);
void define_point(pybind11::module &m);
......
alabaster==0.7.16
Babel==2.15.0
certifi==2024.6.2
charset-normalizer==3.3.2
docutils==0.21.2
idna==3.7
imagesize==1.4.1
Jinja2==3.1.4
MarkupSafe==2.1.5
mypy==1.10.0
mypy-extensions==1.0.0
packaging==24.1
Pygments==2.18.0
requests==2.32.3
snowballstemmer==2.2.0
Sphinx==7.3.7
sphinx_design==0.6.0
sphinxcontrib-applehelp==1.0.8
sphinxcontrib-devhelp==1.0.6
sphinxcontrib-htmlhelp==2.0.5
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.7
sphinxcontrib-serializinghtml==1.1.10
typing_extensions==4.12.2
urllib3==2.2.2
mkdir build
cd build
"C:\Program Files\CMake\bin\cmake" .. -G "Visual Studio 17 2022" -DPYTHON_EXECUTABLE:FILEPATH="C:/Program Files/Python312/python.exe"
"C:\Program Files\CMake\bin\cmake.exe" --build . --config Release --verbose
#!/bin/sh
mkdir build
cd build
"C:\Program Files\CMake\bin\cmake.exe" -G "Visual Studio 17 2022" -DPYTHON_EXECUTABLE:FILEPATH="C:/Program Files/Python312/python.exe" ..
"C:\Program Files\CMake\bin\cmake.exe" --build . --config Release --verbose
#!/bin/sh
mkdir build37
cd build37
"C:\Program Files\CMake\bin\cmake" .. -G "Visual Studio 15 Win64" -DPYTHON_EXECUTABLE:FILEPATH="C:/Program Files/Python37/python.exe"
#!/bin/sh
mkdir build38
cd build38
"C:\Program Files\CMake\bin\cmake" .. -G "Visual Studio 15 Win64" -DPYTHON_EXECUTABLE:FILEPATH="C:/Program Files/Python38/python.exe"
#!/bin/sh
mkdir build
cd build
"C:\Program Files\CMake\bin\cmake" .. -G "Visual Studio 15 Win64" -DPYTHON_EXECUTABLE:FILEPATH="C:/Program Files/Python39/python.exe"
"""
This file inserts the docs of each class and method into the pyi file for autocompletetion.
Before running make sure you have created the pyi according to the README.
You might need to change the pyi_path.
Then simply run this file.
After running all the docs should have been inserted.
Now you're done and can enjoy working autocompletion of this library.
"""
import sys
import inspect
import re
sys.path.append('build/python-api-src') #Unix
sys.path.append('build/python-api-src/Release') #Windows
import commandcenter
def update_pyi_with_docstrings(pyi_path):
"""
Update the .pyi file with docstrings from the corresponding library.
Args:
pyi_path (str): The path to the .pyi file.
Returns:
None
"""
with open(pyi_path, 'r') as file:
lines = file.readlines()
new_lines = []
known_def = {} #To keep track of docs to class methods
for i in range(len(lines)):
line = lines[i]
if line.strip().startswith('class '):
new_lines.append(line)
if not line.strip().endswith("ID:"):
class_name = re.split(r'[\(: ]', line)[1]
docstring = inspect.getdoc(getattr(commandcenter, class_name))
new_lines.append(f' """{docstring}"""\n')
for func in dir(getattr(commandcenter, class_name)):
if func.startswith("__"):
continue
func_docs = inspect.getdoc(getattr(getattr(commandcenter, class_name), func))
known_def[func] = func_docs
else:
class_name = re.split(r'[\(: ]', line)[1]
docstring = inspect.getdoc(getattr(commandcenter, class_name))
docstring = docstring.split("Members:")[0]
new_lines.append(f' """{docstring}"""\n')
elif line.strip().startswith('def '):
new_lines.append(line[:-4])
func_name = re.split(r'[\(: ]', line.strip())[1]
if func_name.startswith("__"):
new_lines.append(f' {line[-4:]}')
continue
elif func_name in known_def:
new_lines.append(f'\n """{known_def[func_name]}"""\n')
new_lines.append(f' {line[-4:]}')
continue
else:
try:
func_doc = inspect.getdoc(getattr(commandcenter, func_name))
if func_name == "create_computer" or func_name == "create_participants": #Special case with indentation
new_lines.append(f'\n """{func_doc}"""\n')
else:
new_lines.append(f'\n """{func_doc}"""\n')
new_lines.append(f' {line[-4:]}')
continue
except AttributeError:
new_lines.append(f'\n """not found"""\n')
new_lines.append(f' {line[-4:]}')
continue
else:
new_lines.append(line)
# Write the updated content back to the .pyi file
with open(pyi_path, 'w') as file:
file.writelines(new_lines)
# Path to the generated .pyi file
pyi_file_path = 'build/python-api-src/commandcenter.pyi' #Unix
#pyi_file_path = 'build/python-api-src/Release/commandcenter.pyi' #Windows
update_pyi_with_docstrings(pyi_file_path)
......@@ -131,6 +131,14 @@ void BaseLocation::setPlayerOccupying(CCPlayer player, bool occupying)
}
}
void BaseLocation::setMinerals(std::vector<Unit> & mineralFields) {
m_minerals = mineralFields;
}
void BaseLocation::setGeysers(std::vector<Unit> & geysers) {
m_geysers = geysers;
}
bool BaseLocation::isInResourceBox(int tileX, int tileY) const
{
CCPositionType px = Util::TileToPosition((float)tileX);
......@@ -202,7 +210,9 @@ void BaseLocation::draw()
{
CCPositionType radius = Util::TileToPosition(1.0f);
m_bot.Map().drawCircle(m_centerOfResources, radius, CCColor(255, 255, 0));
// Take first mineral for the Z-pos.
sc2::Point3D center_pos = sc2::Point3D(m_centerOfResources.x, m_centerOfResources.y, m_minerals[0].getUnitPtr()->pos.z);
m_bot.Map().drawResourceSphere(center_pos, radius, CCColor(255, 255, 0));
std::stringstream ss;
ss << "BaseLocation: " << m_baseID << "\n";
......@@ -237,14 +247,14 @@ void BaseLocation::draw()
//m_bot.Map().drawLine(m_left, y, m_right, y, CCColor(160, 160, 160));
}
for (auto & mineralPos : m_mineralPositions)
for (auto & mineral : m_minerals)
{
m_bot.Map().drawCircle(mineralPos, radius, CCColor(0, 128, 128));
m_bot.Map().drawResourceSphere(mineral.getUnitPtr()->pos, radius, CCColor(0, 128, 128));
}
for (auto & geyserPos : m_geyserPositions)
for (auto & geyser : m_geysers)
{
m_bot.Map().drawCircle(geyserPos, radius, CCColor(0, 255, 0));
m_bot.Map().drawResourceSphere(geyser.getUnitPtr()->pos, radius, CCColor(0, 255, 0));
}
if (m_isStartLocation)
......
......@@ -50,6 +50,8 @@ public:
bool isInResourceBox(int x, int y) const;
void setPlayerOccupying(CCPlayer player, bool occupying);
void setMinerals(std::vector<Unit> & mineralFields);
void setGeysers(std::vector<Unit> & geysers);
const std::vector<CCTilePosition> & getClosestTiles() const;
......
......@@ -138,6 +138,12 @@ void BaseLocationManager::onFrame()
{
drawBaseLocations();
// make sure that mineralfields and geysers are up to date in the different bases
for (BaseLocation & baselocation : m_baseLocationData) {
updateMinerals(baselocation);
updateGeysers(baselocation);
}
// reset the player occupation information for each location
for (auto & baseLocation : m_baseLocationData)
{
......@@ -247,6 +253,32 @@ void BaseLocationManager::onFrame()
}
void BaseLocationManager::updateMinerals(BaseLocation & baseToUpdate) {
std::vector<Unit> newMinerals;
for (Unit oldMineral : baseToUpdate.getMinerals()) {
for (auto & mineral : m_bot.GetAllUnits()) {
if (oldMineral.getPosition() == mineral.getPosition() && mineral.getType().isMineral()) {
newMinerals.push_back(mineral);
break;
}
}
}
baseToUpdate.setMinerals(newMinerals);
}
void BaseLocationManager::updateGeysers(BaseLocation & baseToUpdate) {
std::vector<Unit> newGeysers;
for (Unit oldGeyser : baseToUpdate.getGeysers()) {
for (auto & geyser : m_bot.GetAllUnits()) {
if (oldGeyser.getPosition() == geyser.getPosition() && geyser.getType().isGeyser()) {
newGeysers.push_back(geyser);
break;
}
}
}
baseToUpdate.setGeysers(newGeysers);
}
BaseLocation * BaseLocationManager::getBaseLocation(const CCPosition & pos) const
{
if (!m_bot.Map().isValidPosition(pos)) { return nullptr; }
......@@ -269,7 +301,7 @@ void BaseLocationManager::drawBaseLocations()
const BaseLocation * next_expansion = getNextExpansion(Players::Self);
CCTilePosition nextExpansionPosition = next_expansion->getDepotPosition();
m_bot.Map().drawCircle(Util::GetPosition(nextExpansionPosition), 1, CCColor(255, 0, 255));
m_bot.Map().drawResourceSphere(sc2::Point3D(Util::GetPosition(nextExpansionPosition).x, Util::GetPosition(nextExpansionPosition).y, next_expansion->getMinerals()[0].getUnitPtr()->pos.z), 1, CCColor(255, 0, 255));
m_bot.Map().drawText(Util::GetPosition(nextExpansionPosition), "Next Expansion Location", CCColor(255, 0, 255));
}
......@@ -293,7 +325,6 @@ const std::set<const BaseLocation *> & BaseLocationManager::getOccupiedBaseLocat
return m_occupiedBaseLocations.at(player);
}
const BaseLocation * BaseLocationManager::getNextExpansion(int player) const
{
const BaseLocation * homeBase = getPlayerStartingBaseLocation(player);
......
......@@ -32,4 +32,9 @@ public:
const BaseLocation * getNextExpansion(int player) const;
private:
void updateMinerals(BaseLocation & baseToUpdate);
void updateGeysers(BaseLocation & baseToUpdate);
};
......@@ -14,6 +14,23 @@ void BuildingPlacer::onStart()
m_reserveMap = std::vector< std::vector<bool> >(m_bot.Map().width(), std::vector<bool>(m_bot.Map().height(), false));
}
void BuildingPlacer::updateReserved(const std::vector<Unit> & units)
{
freeAllTiles();
for (Unit unit : units) {
reserveTiles(unit.getTilePosition().x, unit.getTilePosition().y, unit.getType().tileWidth(), unit.getType().tileHeight());
}
}
void BuildingPlacer::freeAllTiles() {
for (int x = 0; x < m_reserveMap.size(); x++) {
for (int y = 0; y < m_reserveMap[x].size(); y++) {
m_reserveMap[x][y] = false;
}
}
}
bool BuildingPlacer::isInResourceBox(int tileX, int tileY) const
{
return m_bot.Bases().getPlayerStartingBaseLocation(Players::Self)->isInResourceBox(tileX, tileY);
......@@ -27,12 +44,17 @@ bool BuildingPlacer::canBuildHere(int bx, int by, const UnitType & type) const
return false;
}
// check the reserve map
for (int x = bx; x < bx + type.tileWidth(); x++)
int width = type.tileWidth();
int height = type.tileHeight();
int xdelta = (int)std::ceil((width - 1.0) / 2);
int ydelta = (int)std::ceil((height - 1.0) / 2);
// check the reserve map, that it is a valid tile and not a wall
for (int x = bx - xdelta; x < bx + width - xdelta; x++)
{
for (int y = by; y < by + type.tileHeight(); y++)
for (int y = by - ydelta; y < by + height - ydelta; y++)
{
if (!m_bot.Map().isValidTile(x, y) || m_reserveMap[x][y])
if (!m_bot.Map().isValidTile(x, y) || isReserved(x, y) || !m_bot.Map().isWalkable(x, y))
{
return false;
}
......@@ -48,6 +70,59 @@ bool BuildingPlacer::canBuildHere(int bx, int by, const UnitType & type) const
return true;
}
bool BuildingPlacer::canBuildHereWithSize(int bx, int by, int width, int height)
{
if (isInResourceBox(bx, by))
{
return false;
}
int xdelta = (int)std::ceil((width - 1.0) / 2);
int ydelta = (int)std::ceil((height - 1.0) / 2);
// check the reserve map, that it is a valid tile and not a wall
for (int x = bx - xdelta; x < bx + width - xdelta; x++)
{
for (int y = by - ydelta; y < by + height - ydelta; y++)
{
if (!m_bot.Map().isValidTile(x, y) || isReserved(x, y) || !m_bot.Map().isWalkable(x, y))
{
return false;
}
}
}
// Check so it doesn't overlap with a baselocation
// dimensions of the proposed location
int tx1 = bx - xdelta;
int ty1 = by - ydelta;
int tx2 = tx1 + width - xdelta;
int ty2 = ty1 + height - ydelta;
// for each base location
for (const BaseLocation * base : m_bot.Bases().getBaseLocations())
{
// dimensions of the base location
int bx1 = (int)base->getDepotPosition().x;
int by1 = (int)base->getDepotPosition().y;
int bx2 = bx1 + Util::GetTownHall(m_bot.GetPlayerRace(Players::Self), m_bot).tileWidth();
int by2 = by1 + Util::GetTownHall(m_bot.GetPlayerRace(Players::Self), m_bot).tileHeight();
// conditions for non-overlap are easy
bool noOverlap = (tx2 < bx1) || (tx1 > bx2) || (ty2 < by1) || (ty1 > by2);
// If there is overlap, return false
if (!noOverlap)
{
return false;
}
}
// otherwise there is no overlap
return true;
}
//returns true if we can build this type of unit here with the specified amount of space.
bool BuildingPlacer::canBuildHereWithSpace(int bx, int by, const UnitType & type, int buildDist) const
{
......@@ -59,24 +134,33 @@ bool BuildingPlacer::canBuildHereWithSpace(int bx, int by, const UnitType & type
// height and width of the building
int width = type.tileWidth();
int height = type.tileHeight();
int xdelta = (int)std::ceil((width - 1.0) / 2);
int ydelta = (int)std::ceil((height - 1.0) / 2);
// TODO: make sure we leave space for add-ons. These types of units can have addons:
// define the rectangle of the building spot
int startx = bx - buildDist;
int starty = by - buildDist;
int endx = bx + width + buildDist;
int endy = by + height + buildDist;
int startx = bx - buildDist - xdelta;
int starty = by - buildDist - ydelta;
int endx = bx + width + buildDist - xdelta;
int endy = by + height + buildDist - ydelta;
// TODO: recalculate start and end positions for addons
// if this rectangle doesn't fit on the map we can't build here
if (startx < 0 || starty < 0 || endx > m_bot.Map().width() || endx < bx + width || endy > m_bot.Map().height())
if (startx < 0 || starty < 0 || endx > m_bot.Map().width() || endx < bx + width - xdelta || endy > m_bot.Map().height() || endy < by + height - ydelta)
{
return false;
}
if (!m_bot.Map().canBuildTypeAtPosition(bx, by, type))
{
return false;
}
// if we can't build here, or space is reserved, or it's in the resource box, we can't build here
for (int x = startx; x < endx; x++)
{
......@@ -84,7 +168,7 @@ bool BuildingPlacer::canBuildHereWithSpace(int bx, int by, const UnitType & type
{
if (!type.isRefinery())
{
if (!buildable(type, x, y) || m_reserveMap[x][y])
if (!buildable(type, x, y) || isReserved(x, y) || !m_bot.Map().isWalkable(x, y)) // added isWalkabale in order to check towards walls
{
return false;
}
......@@ -95,7 +179,9 @@ bool BuildingPlacer::canBuildHereWithSpace(int bx, int by, const UnitType & type
return true;
}
CCTilePosition BuildingPlacer::getBuildLocationNear(const CCTilePosition & p, const UnitType & t, int buildDist) const
// BuildDist is the distance from the position where the building is gonna be placed.
CCTilePosition BuildingPlacer::getBuildLocationNear(const CCTilePosition & p, const UnitType & t, int buildDist, size_t search_count) const
{
//Timer t;
//t.start();
......@@ -106,7 +192,7 @@ CCTilePosition BuildingPlacer::getBuildLocationNear(const CCTilePosition & p, co
//double ms1 = t.getElapsedTimeInMilliSec();
// iterate through the list until we've found a suitable location
for (size_t i(0); i < closestToBuilding.size() && i < 1000; ++i)
for (size_t i(0); i < closestToBuilding.size() && (search_count == 0 || i < search_count); ++i)
{
auto & pos = closestToBuilding[i];
......@@ -121,6 +207,7 @@ CCTilePosition BuildingPlacer::getBuildLocationNear(const CCTilePosition & p, co
//double ms = t.getElapsedTimeInMilliSec();
//printf("Building Placer Failure: %s - Took %lf ms\n", b.type.getName().c_str(), ms);
std::cout << "Warning! Could not find valid placement for " << t.getName() << " near (" << p.x << ", " << p.y << "). Returning (0, 0) instead.";
return CCTilePosition(0, 0);
}
......@@ -134,10 +221,14 @@ bool BuildingPlacer::tileOverlapsBaseLocation(int x, int y, UnitType type) const
}
// dimensions of the proposed location
int tx1 = x;
int ty1 = y;
int tx2 = tx1 + type.tileWidth();
int ty2 = ty1 + type.tileHeight();
int xdelta = (int)std::ceil((type.tileWidth() - 1.0) / 2);
int ydelta = (int)std::ceil((type.tileHeight() - 1.0) / 2);
int tx1 = x - xdelta;
int ty1 = y - ydelta;
int tx2 = tx1 + type.tileWidth() - xdelta;
int ty2 = ty1 + type.tileHeight() - ydelta;
// for each base location
for (const BaseLocation * base : m_bot.Bases().getBaseLocations())
......@@ -165,7 +256,7 @@ bool BuildingPlacer::tileOverlapsBaseLocation(int x, int y, UnitType type) const
bool BuildingPlacer::buildable(const UnitType & type, int x, int y) const
{
// TODO: does this take units on the map into account?
if (!m_bot.Map().isValidTile(x, y) || !m_bot.Map().canBuildTypeAtPosition(x, y, type))
if (!m_bot.Map().isValidTile(x, y))
{
return false;
}
......@@ -177,11 +268,16 @@ bool BuildingPlacer::buildable(const UnitType & type, int x, int y) const
void BuildingPlacer::reserveTiles(int bx, int by, int width, int height)
{
// THIS is never called, that's why spacing doesnt work correctly
int rwidth = (int)m_reserveMap.size();
int rheight = (int)m_reserveMap[0].size();
for (int x = bx; x < bx + width && x < rwidth; x++)
int xdelta = (int)std::ceil((width - 1.0) / 2);
int ydelta = (int)std::ceil((height - 1.0) / 2);
for (int x = bx - xdelta; x < bx + width - xdelta && x < rwidth; x++)
{
for (int y = by; y < by + height && y < rheight; y++)
for (int y = by - ydelta; y < by + height - ydelta && y < rheight; y++)
{
m_reserveMap[x][y] = true;
}
......@@ -190,6 +286,7 @@ void BuildingPlacer::reserveTiles(int bx, int by, int width, int height)
void BuildingPlacer::drawReservedTiles()
{
// Why is there a return here? Should we not use the function? /Hannes Jmtner
return;
int rwidth = (int)m_reserveMap.size();
int rheight = (int)m_reserveMap[0].size();
......@@ -211,9 +308,12 @@ void BuildingPlacer::freeTiles(int bx, int by, int width, int height)
int rwidth = (int)m_reserveMap.size();
int rheight = (int)m_reserveMap[0].size();
for (int x = bx; x < bx + width && x < rwidth; x++)
int xdelta = (int)std::ceil((width - 1.0) / 2);
int ydelta = (int)std::ceil((height - 1.0) / 2);
for (int x = bx - xdelta; x < bx + width - xdelta && x < rwidth; x++)
{
for (int y = by; y < by + height && y < rheight; y++)
for (int y = by - ydelta; y < by + height - ydelta && y < rheight; y++)
{
m_reserveMap[x][y] = false;
}
......@@ -226,8 +326,12 @@ CCTilePosition BuildingPlacer::getRefineryPosition()
double minGeyserDistanceFromHome = std::numeric_limits<double>::max();
CCPosition homePosition = m_bot.GetStartLocation();
for (auto & unit : m_bot.GetAllUnits())
{
UnitType refinery = Util::GetRefinery(m_bot.GetPlayerRace(Players::Self), m_bot);
if (!unit.getType().isGeyser())
{
continue;
......@@ -235,6 +339,13 @@ CCTilePosition BuildingPlacer::getRefineryPosition()
CCPosition geyserPos(unit.getPosition());
// can't build a refinery on top of another
if (!m_bot.Map().canBuildTypeAtPosition((int)geyserPos.x, (int)geyserPos.y, refinery))
{
continue;
}
// check to see if it's next to one of our depots
bool nearDepot = false;
for (auto & unit : m_bot.UnitInfo().getUnits(Players::Self))
......
......@@ -5,6 +5,7 @@
class IDABot;
class BaseLocation;
class UnitType;
class Unit;
class BuildingPlacer
{
......@@ -23,13 +24,16 @@ public:
BuildingPlacer(IDABot & bot);
void onStart();
void updateReserved(const std::vector<Unit> & units);
void freeAllTiles();
// determines whether we can build at a given location
bool canBuildHere(int bx, int by, const UnitType & type) const;
bool canBuildHereWithSize(int bx, int by, int width, int height);
bool canBuildHereWithSpace(int bx, int by, const UnitType & type, int buildDist) const;
// returns a build location near a building's desired location
CCTilePosition getBuildLocationNear(const CCTilePosition & p, const UnitType & type, int buildDist) const;
CCTilePosition getBuildLocationNear(const CCTilePosition & p, const UnitType & type, int buildDist, size_t search_count = 1000) const;
void drawReservedTiles();
......
......@@ -2,13 +2,13 @@
file(GLOB BOT_SOURCES "*.cpp" "*.h" "*.hpp")
include_directories(SYSTEM
${PROJECT_SOURCE_DIR}/lib/s2client-api/include
${PROJECT_SOURCE_DIR}/lib/s2client-api/contrib/protobuf/src
${PROJECT_BINARY_DIR}/lib/s2client-api/generated
${PROJECT_SOURCE_DIR}/lib/cpp-sc2/include
${PROJECT_SOURCE_DIR}/lib/cpp-sc2/contrib/protobuf/src
${PROJECT_BINARY_DIR}/lib/cpp-sc2/generated
${PROJECT_SOURCE_DIR}/lib/json/include
)
link_directories(${PROJECT_BINARY_DIR}/s2client-api/bin)
link_directories(${PROJECT_BINARY_DIR}/cpp-sc2/bin)
# Enable compilation of the SC2 version of the bot.
add_definitions(-DSC2API)
......
......@@ -24,10 +24,11 @@ typedef sc2::Tag CCUnitID;
typedef sc2::Race CCRace;
typedef float CCHealth;
typedef float CCPositionType;
typedef sc2::BuffID CCBuff;
typedef size_t CCPlayer;
namespace Players
{
enum {Self = 0u, Enemy = 1u, Neutral = 2u, Ally = 3u, Size = 4u, None = 5u};
}
\ No newline at end of file
}