0. StarCraft II Python Bot: Introduction

# Python library for StarCraft II

«An easy-to-use library for writing AI Bots for StarCraft II in Python 3. The ultimate goal is simplicity and ease of use, while still preserving all functionality. A really simple worker rush bot should be no more than twenty lines of code, not two hundred. However, this library intends to provide both high and low level abstractions.»


You can find how to install in the previous link.

# About this tutorial

I structured this tutorial in three parts. From the beginning to the end it adds more and more things your bot can do. Every chapter will add a new functionality and when you finish your (Terran) bot, it will have a basic behaviour: gather, build an army, attack.

This tutorial has two parts to get you started on programming a StarCraft II Bot using python libraries and the StarCraft II game.

The structure is the next:
  •     0. This introduction
  •     1. Gathering resources
  •     2. Building units

# Starcraft Race of the bot

- Terran

The terrans (or humans) are a young species with psionic potential. The terrans of the Koprulu sector descend from the survivors of a disastrous 23rd century colonization mission from Earth.



1. StarCraft II Python Bot: Gathering resources

 # Command Center

The Command Center is the main building you need. On it you build your workers. You start with one the match and can expand your territory with it.

# Building the workers

async def buildWorkers(self):
for commandcenter in self.units(UnitTypeId.COMMANDCENTER).ready.noqueue:
if self.can_afford(UnitTypeId.SCV) and self.workers.amount < self.units(UnitTypeId.COMMANDCENTER).amount * 14 + 4:
await self.do(commandcenter.train(UnitTypeId.SCV))
This is simply a function to tell the Terran Command Center to create SCVs. We need to use it in the class, so let’s put it in the class and call it in the on_sept() function. The final script of this part should look like this:
import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.ids.unit_typeid import *
class AlanBot(sc2.BotAI):
async def on_step(self, iteration):
await self.buildWorkers()
async def buildWorkers(self):
for commandcenter in self.units(UnitTypeId.COMMANDCENTER).ready.noqueue:
if self.can_afford(UnitTypeId.SCV) and self.workers.amount < self.units(UnitTypeId.COMMANDCENTER).amount * 14 + 4:
await self.do(commandcenter.train(UnitTypeId.SCV))
run_game(maps.get("AbyssalReefLE"), [
Bot(Race.Terran, AlanBot()),
Computer(Race.Zerg, Difficulty.Hard)
], realtime=True)

# Gatering minerals

The developers of this library have already implemented a function to distribute_workers(), it will gather the minerals and Vespene Gas, but it won’t create create the Vespene Refinery (in Terran case). We need to create the Refineries too.
import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.ids.unit_typeid import *
class AlanBot(sc2.BotAI):
async def on_step(self, iteration):
await self.buildWorkers()
await self.distribute_workers()
async def buildWorkers(self):
for commandcenter in self.units(UnitTypeId.COMMANDCENTER).ready.noqueue:
if self.can_afford(UnitTypeId.SCV) and self.workers.amount < self.units(UnitTypeId.COMMANDCENTER).amount * 14 + 4:
await self.do(commandcenter.train(UnitTypeId.SCV))
run_game(maps.get("AbyssalReefLE"), [
Bot(Race.Terran, AlanBot()),
Computer(Race.Zerg, Difficulty.Hard)
], realtime=True)

# Gathering vesper gas

In order to collect the vespene gas, we need refineries (or Extractors or Assimlators for other Races) for the Terran SVCs.
async def buildRefineries(self):
for commandcenter in self.units(UnitTypeId.COMMANDCENTER).ready:
vespenes = self.state.vespene_geyser.closer_than(18.0, commandcenter)
for vespene in vespenes:
if not self.can_afford(UnitTypeId.REFINERY):
break
worker = self.select_build_worker(vespene.position)
if worker is None:
break
if not self.units(UnitTypeId.REFINERY).closer_than(1.0, vespene).exists:
await self.do(worker.build(UnitTypeId.REFINERY, vespene))

Write this as a class function of our bot and call it on the on_step() function:
import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.ids.unit_typeid import *
class AlanBot(sc2.BotAI):
async def on_step(self, iteration):
await self.buildWorkers()
await self.distribute_workers()
await self.buildRefineries()
async def buildWorkers(self):
for commandcenter in self.units(UnitTypeId.COMMANDCENTER).ready.noqueue:
if self.can_afford(UnitTypeId.SCV) and self.workers.amount < self.units(UnitTypeId.COMMANDCENTER).amount * 14 + 4:
await self.do(commandcenter.train(UnitTypeId.SCV))
async def buildRefineries(self):
for commandcenter in self.units(UnitTypeId.COMMANDCENTER).ready:
vespenes = self.state.vespene_geyser.closer_than(18.0, commandcenter)
for vespene in vespenes:
if not self.can_afford(UnitTypeId.REFINERY):
break
worker = self.select_build_worker(vespene.position)
if worker is None:
break
if not self.units(UnitTypeId.REFINERY).closer_than(1.0, vespene).exists:
await self.do(worker.build(UnitTypeId.REFINERY, vespene))
run_game(maps.get("AbyssalReefLE"), [
Bot(Race.Terran, AlanBot()),
Computer(Race.Zerg, Difficulty.Hard)
], realtime=True)

You should now have workers being created and gathering minerals and vespene gas for you.

2. StarCraft II Python Bot: Building units

 You need to check different things. When you want to create one Barrack you need:

And when you want to create a Marine you need:

  • Enough minerals
  • At least one Barrack
  • A limit of units to create

This is why we create two different functions: buildStructure() and buildOffensiveUnit().

- buildStructure()

In this function we are going to receive two parameters: the structure that we want to build, and the amount of structures that we want of it.

A good place to start building some Supply Depots and Barracks is around your first Command Center. So we are going to save the position in a variable:
commandCenter = self.units(UnitTypeId.COMMANDCENTER).ready.random
nearCC = await self.find_placement(UnitTypeId.SUPPLYDEPOT, commandcenter.position, placement_step=2)
But for the Barracks is a little more special: we need to check if there are already Supply Depots (see the Terran Tech Tree), if we have reached the limit of Barracks that we want, and if we can afford to built it.

The complete function is the following:
async def buildStructure(self, structureName, amount):
if self.units(UnitTypeId.COMMANDCENTER).ready.exists:
commandCenter = self.units(UnitTypeId.COMMANDCENTER).ready.random
workers = self.workers.gathering
nearCC = await self.find_placement(UnitTypeId.SUPPLYDEPOT, commandCenter.position, placement_step=2)
if structureName == 'supplydepot':
if self.supply_left < 6 and self.can_afford(UnitTypeId.SUPPLYDEPOT):
if workers:
w = workers.furthest_to(workers.center)
if nearCC:
await self.do(w.build(UnitTypeId.SUPPLYDEPOT, nearCC))
if structureName == 'barracks':
if self.units.of_type([UnitTypeId.SUPPLYDEPOT, UnitTypeId.SUPPLYDEPOTLOWERED, UnitTypeId.SUPPLYDEPOTDROP]).ready.exists
and self.units(UnitTypeId.BARRACKS).amount + self.already_pending(UnitTypeId.BARRACKS) < amount
and self.can_afford(UnitTypeId.BARRACKS):
if workers:
w = workers.furthest_to(workers.center)
if nearCC:
await self.do(w.build(UnitTypeId.BARRACKS, nearCC))
The nearCC variable store a location that match a free place for the Supply Depots size. You can add a second location that find a place for the size of the Barracks later.

- buildOffensiveUnit()

The offensive unit is the way of the program to spawn our Terrans. We pass it a name, where to build it, and how many of them.
async def buildOffensiveUnit(self, unitName, structureName, maxAmount):
structure = self.unitSelector(structureName)
unit = self.unitSelector(unitName)
if self.units(structure).ready.exists:
for struct in self.units(structure).ready.noqueue:
if self.can_afford(unit) and self.supply_left > 0 and self.units(unit).amount < maxAmount:
await self.do(struct.train(unit))

- Call in all in on_step()

For the bot to do things you need to put all your functions in the on_step() function. That way the program takes control on the synchs, and will launch everything written in the function. Maybe a good thing would be making a FSM machine, but this case is very simple.

You can give it a bit of logic for some amounts of units.
async def on_step(self, iteration):
await self.buildWorkers()
await self.distribute_workers()
await self.buildRefineries()
await self.buildStructure('supplydepot', 2000)
await self.buildStructure('barracks', 4)
await self.buildOffensiveUnit('marine', 'barracks', 27)
if self.units(UnitTypeId.MARINE).amount > 17:
await self.move('marine', True)
else:
await self.move('marine', False)