So far we've been focused on some of the simpler macro abilities required to make a bot. You've gotten your bot to build scv's and supply depots when it's running near the supply cap. In this tutorial we'll learn the following.
- Building a Barracks.
- Building Marines from said Barracks.
- Scouting Your Opponent.
This tutorial builds on the previous so make sure the code from the last tutorial is copy pasted into tutorial.cc and you can build/run it.
Building a Barracks
The first step to any good Terran build is to build marines, and to build marines we must first build a barracks. We've already seen how to construct supply depots so lets create some similar code for constructing a barracks. Modify your OnStep function to the following.
2 virtual void OnStep() final {
We'll implement TryBuildBarracks shortly.
First, we have some constraints we must satisfy to build a barracks, primarily, we need a supply depot. We'd also like to only build one for this this tutorial so lets create a helper function for counting unit types and we'll use that in TryBuildBarracks in order to determine if we should build one or not.
2 size_t CountUnitType(UNIT_TYPEID unit_type) {
3 return Observation()->GetUnits(Unit::Alliance::Self, IsUnit(unit_type)).size();
That function is counting the number of a certain unit type the player owns. GetUnits takes a Filter parameter that allows you to remove units that don't meet a certain condition. In this case that condition is that the units are of the desired unit_type.
We now have the necessary helper functions to implement TryBuildBarracks.
2 bool TryBuildBarracks() {
3 const ObservationInterface* observation = Observation();
5 if (CountUnitType(UNIT_TYPEID::TERRAN_SUPPLYDEPOT) < 1) {
9 if (CountUnitType(UNIT_TYPEID::TERRAN_BARRACKS) > 0) {
13 return TryBuildStructure(ABILITY_ID::BUILD_BARRACKS);
You can build and run your code at this point, if you'd like, you should see your bot building a barracks after it completes its first supply depot. We'd now like that barracks to actually do something. Recall we've overwritten a OnUnitIdle event in an earlier tutorial, completion of the barracks should trigger that event!
Building Marines
Similar to how we construct SCVs we can now produce marines. Add the following code to the switch case in OnUnitIdle. The entire function should look like the following, the new code is the UNIT_TYPE::TERRAN_BARRACKS case:
2 virtual void OnUnitIdle(const Unit* unit) final {
3 switch (unit->unit_type.ToType()) {
4 case UNIT_TYPEID::TERRAN_COMMANDCENTER: {
5 Actions()->UnitCommand(unit, ABILITY_ID::TRAIN_SCV);
8 case UNIT_TYPEID::TERRAN_SCV: {
9 const Unit* mineral_target = FindNearestMineralPatch(unit->pos);
10 if (!mineral_target) {
13 Actions()->UnitCommand(unit, ABILITY_ID::SMART, mineral_target);
16 case UNIT_TYPEID::TERRAN_BARRACKS: {
17 Actions()->UnitCommand(unit, ABILITY_ID::TRAIN_MARINE);
20 case UNIT_TYPEID::TERRAN_MARINE: {
21 const GameInfo& game_info = Observation()->GetGameInfo();
22 Actions()->UnitCommand(unit, ABILITY_ID::ATTACK_ATTACK, game_info.enemy_start_locations.front());
Notice how easy that is! In general, OnUnitIdle is an excellent function to add code to to control unit production and orders. At this point if you build and run the code your bot should build a barracks and start producing marines with them. Our last step should be to scout the enemy.
Scouting Your Opponent
In a normal match when the game begins the minimap is pinged with all possible starting locations of enemies, the api contains that same information in the ObservationInterface. You can retrieve it via GetGameInfo(). Lets use that function in our OnUnitIdle so a newly spawned marine will attack move towards the enemy as soon as it's spawned. It will be fun to see countless marines walk to their demise.
In your OnUnitIdle add the following code to your switch case -
2 case UNIT_TYPEID::TERRAN_MARINE: {
3 const GameInfo& game_info = Observation()->GetGameInfo();
4 Actions()->UnitCommand(unit, ABILITY_ID::ATTACK_ATTACK, game_info.enemy_start_locations.front());
How fun, build and run and you can watch marines endlessly walk to their death.
Exercises
- Try building and producing marines from three barracks instead of one.
- (Challenging) Perform a simple rush, from your three barracks wait until you've gathered 10-20 marines then attack move to your enemy.
Full Source Code
2 #include <sc2api/sc2_api.h>
8 class Bot : public Agent {
10 virtual void OnGameStart() final {
11 std::cout << "Hello, World!" << std::endl;
14 virtual void OnStep() final {
15 TryBuildSupplyDepot();
20 virtual void OnUnitIdle(const Unit* unit) final {
21 switch (unit->unit_type.ToType()) {
22 case UNIT_TYPEID::TERRAN_COMMANDCENTER: {
23 Actions()->UnitCommand(unit, ABILITY_ID::TRAIN_SCV);
26 case UNIT_TYPEID::TERRAN_SCV: {
27 const Unit* mineral_target = FindNearestMineralPatch(unit->pos);
28 if (!mineral_target) {
31 Actions()->UnitCommand(unit, ABILITY_ID::SMART, mineral_target);
34 case UNIT_TYPEID::TERRAN_BARRACKS: {
35 Actions()->UnitCommand(unit, ABILITY_ID::TRAIN_MARINE);
38 case UNIT_TYPEID::TERRAN_MARINE: {
39 const GameInfo& game_info = Observation()->GetGameInfo();
40 Actions()->UnitCommand(unit, ABILITY_ID::ATTACK_ATTACK, game_info.enemy_start_locations.front());
49 size_t CountUnitType(UNIT_TYPEID unit_type) {
50 return Observation()->GetUnits(Unit::Alliance::Self, IsUnit(unit_type)).size();
53 bool TryBuildStructure(ABILITY_ID ability_type_for_structure, UNIT_TYPEID unit_type = UNIT_TYPEID::TERRAN_SCV) {
54 const ObservationInterface* observation = Observation();
56 // If a unit already is building a supply structure of this type, do nothing.
57 // Also get an scv to build the structure.
58 const Unit* unit_to_build = nullptr;
59 Units units = observation->GetUnits(Unit::Alliance::Self);
60 for (const auto& unit : units) {
61 for (const auto& order : unit->orders) {
62 if (order.ability_id == ability_type_for_structure) {
67 if (unit->unit_type == unit_type) {
72 float rx = GetRandomScalar();
73 float ry = GetRandomScalar();
75 Actions()->UnitCommand(unit_to_build,
76 ability_type_for_structure,
77 Point2D(unit_to_build->pos.x + rx * 15.0f, unit_to_build->pos.y + ry * 15.0f));
82 bool TryBuildSupplyDepot() {
83 const ObservationInterface* observation = Observation();
85 // If we are not supply capped, don't build a supply depot.
86 if (observation->GetFoodUsed() <= observation->GetFoodCap() - 2)
89 // Try and build a depot. Find a random SCV and give it the order.
90 return TryBuildStructure(ABILITY_ID::BUILD_SUPPLYDEPOT);
93 const Unit* FindNearestMineralPatch(const Point2D& start) {
94 Units units = Observation()->GetUnits(Unit::Alliance::Neutral);
95 float distance = std::numeric_limits<float>::max();
96 const Unit* target = nullptr;
97 for (const auto& u : units) {
98 if (u->unit_type == UNIT_TYPEID::NEUTRAL_MINERALFIELD) {
99 float d = DistanceSquared2D(u->pos, start);
109 bool TryBuildBarracks() {
110 const ObservationInterface* observation = Observation();
112 if (CountUnitType(UNIT_TYPEID::TERRAN_SUPPLYDEPOT) < 1) {
116 if (CountUnitType(UNIT_TYPEID::TERRAN_BARRACKS) > 0) {
120 return TryBuildStructure(ABILITY_ID::BUILD_BARRACKS);
124 int main(int argc, char* argv[]) {
125 Coordinator coordinator;
126 coordinator.LoadSettings(argc, argv);
129 coordinator.SetParticipants({
130 CreateParticipant(Race::Terran, &bot),
131 CreateComputer(Race::Zerg)
134 coordinator.LaunchStarcraft();
135 coordinator.StartGame(sc2::kMapBelShirVestigeLE);
137 while (coordinator.Update()) {