SC2API
An API for AI for StarCraft II
Tutorial 3: Building Marines and Scouting

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.

  1. Building a Barracks.
  2. Building Marines from said Barracks.
  3. 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.

1 {C++}
2 virtual void OnStep() final {
3  TryBuildSupplyDepot();
4 
5  TryBuildBarracks();
6 }

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.

1 {C++}
2 size_t CountUnitType(UNIT_TYPEID unit_type) {
3  return Observation()->GetUnits(Unit::Alliance::Self, IsUnit(unit_type)).size();
4 }

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.

1 {C++}
2 bool TryBuildBarracks() {
3  const ObservationInterface* observation = Observation();
4 
5  if (CountUnitType(UNIT_TYPEID::TERRAN_SUPPLYDEPOT) < 1) {
6  return false;
7  }
8 
9  if (CountUnitType(UNIT_TYPEID::TERRAN_BARRACKS) > 0) {
10  return false;
11  }
12 
13  return TryBuildStructure(ABILITY_ID::BUILD_BARRACKS);
14 }

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:

1 {C++}
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);
6  break;
7  }
8  case UNIT_TYPEID::TERRAN_SCV: {
9  const Unit* mineral_target = FindNearestMineralPatch(unit->pos);
10  if (!mineral_target) {
11  break;
12  }
13  Actions()->UnitCommand(unit, ABILITY_ID::SMART, mineral_target);
14  break;
15  }
16  case UNIT_TYPEID::TERRAN_BARRACKS: {
17  Actions()->UnitCommand(unit, ABILITY_ID::TRAIN_MARINE);
18  break;
19  }
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());
23  break;
24  }
25  default: {
26  break;
27  }
28  }
29 }

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 -

1 {C++}
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());
5  break;
6 }

How fun, build and run and you can watch marines endlessly walk to their death.

Exercises

  1. Try building and producing marines from three barracks instead of one.
  2. (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

1 {C++}
2 #include <sc2api/sc2_api.h>
3 
4 #include <iostream>
5 
6 using namespace sc2;
7 
8 class Bot : public Agent {
9 public:
10  virtual void OnGameStart() final {
11  std::cout << "Hello, World!" << std::endl;
12  }
13 
14  virtual void OnStep() final {
15  TryBuildSupplyDepot();
16 
17  TryBuildBarracks();
18  }
19 
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);
24  break;
25  }
26  case UNIT_TYPEID::TERRAN_SCV: {
27  const Unit* mineral_target = FindNearestMineralPatch(unit->pos);
28  if (!mineral_target) {
29  break;
30  }
31  Actions()->UnitCommand(unit, ABILITY_ID::SMART, mineral_target);
32  break;
33  }
34  case UNIT_TYPEID::TERRAN_BARRACKS: {
35  Actions()->UnitCommand(unit, ABILITY_ID::TRAIN_MARINE);
36  break;
37  }
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());
41  break;
42  }
43  default: {
44  break;
45  }
46  }
47  }
48 private:
49  size_t CountUnitType(UNIT_TYPEID unit_type) {
50  return Observation()->GetUnits(Unit::Alliance::Self, IsUnit(unit_type)).size();
51  }
52 
53  bool TryBuildStructure(ABILITY_ID ability_type_for_structure, UNIT_TYPEID unit_type = UNIT_TYPEID::TERRAN_SCV) {
54  const ObservationInterface* observation = Observation();
55 
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) {
63  return false;
64  }
65  }
66 
67  if (unit->unit_type == unit_type) {
68  unit_to_build = unit;
69  }
70  }
71 
72  float rx = GetRandomScalar();
73  float ry = GetRandomScalar();
74 
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));
78 
79  return true;
80  }
81 
82  bool TryBuildSupplyDepot() {
83  const ObservationInterface* observation = Observation();
84 
85  // If we are not supply capped, don't build a supply depot.
86  if (observation->GetFoodUsed() <= observation->GetFoodCap() - 2)
87  return false;
88 
89  // Try and build a depot. Find a random SCV and give it the order.
90  return TryBuildStructure(ABILITY_ID::BUILD_SUPPLYDEPOT);
91  }
92 
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);
100  if (d < distance) {
101  distance = d;
102  target = u;
103  }
104  }
105  }
106  return target;
107  }
108 
109  bool TryBuildBarracks() {
110  const ObservationInterface* observation = Observation();
111 
112  if (CountUnitType(UNIT_TYPEID::TERRAN_SUPPLYDEPOT) < 1) {
113  return false;
114  }
115 
116  if (CountUnitType(UNIT_TYPEID::TERRAN_BARRACKS) > 0) {
117  return false;
118  }
119 
120  return TryBuildStructure(ABILITY_ID::BUILD_BARRACKS);
121  }
122 };
123 
124 int main(int argc, char* argv[]) {
125  Coordinator coordinator;
126  coordinator.LoadSettings(argc, argv);
127 
128  Bot bot;
129  coordinator.SetParticipants({
130  CreateParticipant(Race::Terran, &bot),
131  CreateComputer(Race::Zerg)
132  });
133 
134  coordinator.LaunchStarcraft();
135  coordinator.StartGame(sc2::kMapBelShirVestigeLE);
136 
137  while (coordinator.Update()) {
138  }
139 
140  return 0;
141 }