6.1 - Macro-management - Expanding and Managing Your Economy
Macro-management ("macro") is the art of economy. It is a high-level, strategic process focused on resource collection, base expansion, and infrastructure development. A bot with superior macro can often defeat a bot with superior unit control ("micro") simply by producing a larger, better-equipped army.
This section moves beyond individual commands and into the realm of building a robust economic engine that will power your bot's war machine.
The Macro Engine: A System of Priorities
A strong macro bot doesn't just perform actions; it manages a system of competing priorities. At any given moment, it must decide what is the most important use of its resources.
+---------------------------------+
| Incoming Resources | (Minerals & Vespene)
| (self.minerals, self.vespene) |
+---------------------------------+
|
v
+---------------------------------+
| The Macro Engine (Your Bot) |
|---------------------------------|
| #1: Manage Supply | <-- Top Priority: Never get supply blocked.
| #2: Produce Workers | <-- The foundation of your economy.
| #3: Expand to New Bases | <-- Increases your income ceiling.
| #4: Build Production/Tech | <-- Unlocks new units and upgrades.
| #5: Build Army Units | <-- Your win condition.
+---------------------------------+
Your on_step
method should be structured to reflect this hierarchy, ensuring that critical economic tasks are always handled before less urgent ones.
The Three Pillars of Economic Management
Pillar | Core Principle | Implementation Checklist |
---|---|---|
1. Resource Velocity | Keep your money moving. A large bank of unspent resources is a sign of inefficiency. Your goal is to have a high rate of spending. | [ ] Is my production always queued?[ ] Am I building tech as soon as I can afford it?[ ] Are my resources consistently low? |
2. Worker Saturation | Build workers constantly, and assign them correctly. The goal is to "saturate" each base with the optimal number of workers. | [ ] Am I training workers from all idle townhalls?[ ] Do I stop at a target worker count (e.g., 66-70)?[ ] Are my vespene geysers fully staffed (3 workers each)? |
3. Base Expansion | Secure more income. To build a larger army, you must expand to new mineral lines. | [ ] Do I have a clear trigger for when to expand? (e.g., at a certain time, supply, or tech level).[ ] Am I using self.expand_now() for easy, reliable expansion? |
Code Architecture: Separating Concerns
As your bot's logic grows, a single, massive on_step
method becomes unmanageable. A professional approach is to separate concerns into different classes or methods. Here, we'll create a MacroManager
to handle all economic decisions.
Code Example: The "Economic Engine" Bot
This bot demonstrates a clean, structured approach to macro. It uses a dedicated MacroManager
class to handle its economy, following a strict priority list to ensure it grows efficiently and robustly.
# economic_engine_bot.py
from sc2 import maps
from sc2.bot_ai import BotAI
from sc2.data import Difficulty, Race
from sc2.main import run_game
from sc2.player import Bot, Computer
from sc2.ids.unit_typeid import UnitTypeId
# A class dedicated to handling all macroeconomic decisions.
class MacroManager:
def __init__(self, bot: BotAI):
self.bot = bot
self.target_worker_count = 70
async def manage(self):
# The order of these calls defines our bot's priorities.
self.manage_supply()
self.manage_worker_production()
await self.manage_gas_collection()
await self.manage_expansion()
self.manage_production_buildings()
self.manage_army_production()
def manage_supply(self):
"""Builds supply depots when needed."""
if self.bot.supply_left < 5 and self.bot.already_pending(UnitTypeId.SUPPLYDEPOT) == 0:
if self.bot.can_afford(UnitTypeId.SUPPLYDEPOT):
worker = self.bot.workers.random_or_none
if worker:
worker.build(UnitTypeId.SUPPLYDEPOT, near=self.bot.start_location.towards(self.bot.game_info.map_center, 5))
def manage_worker_production(self):
"""Trains workers from idle townhalls."""
if self.bot.workers.amount < self.target_worker_count:
for townhall in self.bot.townhalls.idle:
if self.bot.can_afford(UnitTypeId.SCV):
townhall.train(UnitTypeId.SCV)
async def manage_gas_collection(self):
"""Builds refineries and saturates them with workers."""
# Build refineries
for townhall in self.bot.townhalls.ready:
vespene_geysers = self.bot.vespene_geysers.closer_than(10, townhall)
for geyser in vespene_geysers:
if not self.bot.structures.closer_than(1.0, geyser).exists and self.bot.can_afford(UnitTypeId.REFINERY):
worker = self.bot.workers.closest_to(geyser)
if worker:
worker.build(UnitTypeId.REFINERY, geyser)
break # Prevents assigning multiple workers to build the same refinery
# Saturate refineries
for refinery in self.bot.structures(UnitTypeId.REFINERY).ready:
if refinery.assigned_harvesters < refinery.ideal_harvesters:
worker = self.bot.workers.closest_to(refinery)
if worker:
worker.gather(refinery)
async def manage_expansion(self):
"""Expands to a new base when conditions are met."""
if self.bot.townhalls.amount < 3 and self.bot.can_afford(UnitTypeId.COMMANDCENTER):
await self.bot.expand_now()
def manage_production_buildings(self):
"""Builds barracks to produce army units."""
if self.bot.structures(UnitTypeId.SUPPLYDEPOT).ready.exists and self.bot.structures(UnitTypeId.BARRACKS).amount < 5:
if self.bot.can_afford(UnitTypeId.BARRACKS):
worker = self.bot.workers.random_or_none
if worker:
worker.build(UnitTypeId.BARRACKS, near=self.bot.start_location.towards(self.bot.game_info.map_center, 8))
def manage_army_production(self):
"""Trains marines from idle barracks."""
for barracks in self.bot.structures(UnitTypeId.BARRACKS).ready.idle:
if self.bot.can_afford(UnitTypeId.MARINE):
barracks.train(UnitTypeId.MARINE)
# The main bot class now delegates its macro tasks.
class MacroBot(BotAI):
def __init__(self):
self.macro_manager = MacroManager(self)
async def on_step(self, iteration: int):
# On each step, we simply tell our manager to do its job.
await self.macro_manager.manage()
if __name__ == "__main__":
run_game(
maps.get("GresvanAIE"),
[
Bot(Race.Terran, MacroBot()),
Computer(Race.Zerg, Difficulty.Medium)
],
realtime=False,
)