3.1 - The Heart of Your Bot
At the center of every project is your main bot class. This class is not just a container for code; it's the brain of your entire operation. To write effective bots, you must first understand the structure of this class and the powerful "API contract" it fulfills by inheriting from the library's BotAI
.
Think of BotAI
as the standard cockpit of a complex vehicle. It provides all the essential instruments, displays, and controls. Your job is to create a pilot—your bot class—that inherits this cockpit and uses it to make intelligent decisions.
The Inheritance Structure
[ burnysc2 Library ] [ Your Code: my_bot.py ]
+---------------------+
| BotAI |
|---------------------| +----------------------+
| - self.state | | MyBot |
| - self.game_info | <-----------+ |----------------------|
| - self.time | (Inherits) | + on_start() |
| - self.minerals | | + on_step() |
| - self.units | | + on_end() |
| ...and many more | +----------------------+
+---------------------+
By writing class MyBot(BotAI):
, you are signing a contract. You agree to implement the logic, and in return, BotAI
provides you with a rich set of tools and guarantees that certain methods in your class (on_start
, on_step
, etc.) will be called at the appropriate times.
The BotAI
Contract: What You Get via self
Inheriting from BotAI
gives your class access to a host of properties and methods through the self
keyword. These can be grouped into two categories: Sensing (reading the game state) and Acting (executing logic at specific times).
Category | Component | How You Use It (Example) | Its Purpose |
---|---|---|---|
Sensing | Core Data Objects | self.state / self.game_info | Provides the raw, unfiltered snapshot of all game data. This is the ultimate source of truth. |
Convenience Helpers | self.units , self.workers , self.enemy_structures | Clean, readable shortcuts to filtered lists of units from self.state . Far easier to use. | |
Resource Properties | self.minerals , self.vespene , self.supply_left | Instant, direct access to the most commonly needed resource values. | |
Time & Iteration | self.time , self.time_formatted , iteration | Track game progress. self.time (in seconds) is best for time-based logic. | |
Acting | Lifecycle Methods | async def on_start(self): | A "hook" that runs once at the very beginning of the game. Perfect for initial setup. |
async def on_step(self, iteration): | The main game loop. Runs repeatedly (many times per second). Your core logic lives here. | ||
async def on_end(self, result): | A "hook" that runs once after the game has finished. Ideal for cleanup or analysis. | ||
Event-Driven Methods | on_unit_created , on_building_construction_complete | Methods that run only when a specific, named event occurs in the game. |
Code Example: A Smarter Skeleton
This example demonstrates how to properly use the BotAI
contract to build a simple, readable bot skeleton. Note the use of self.time
for reliable, time-based checks.
Checklist for This Bot:
- Inherit from
BotAI
. - Use
on_start
for a one-time setup message. - Use
on_step
for continuous, looping logic. - Access helper properties like
self.time
,self.supply_used
, andself.townhalls
.
# smart_skeleton.py
from sc2.main import run_game
from sc2 import maps
from sc2.bot_ai import BotAI
from sc2.ids.unit_typeid import UnitTypeId # A list of all unit IDs
from sc2.player import Bot, Computer
from sc2.data import Difficulty, Race
class SmartBot(BotAI):
"""
A bot skeleton that demonstrates the correct use of the BotAI class.
"""
def __init__(self):
super().__init__()
# Initialize a variable to track the last time we printed status.
self.last_log_time = 0
async def on_start(self):
"""Called once at the start."""
print(f"Game started on map: {self.game_info.map_name}")
async def on_step(self, iteration: int):
"""Called on every game step. This is where the main logic lives."""
# Log current status every 5 seconds of game time.
# Using self.time is more reliable than using the iteration number.
if self.time - self.last_log_time >= 5:
self.last_log_time = self.time
supply_used = self.supply_used
supply_cap = self.supply_cap
townhall_count = self.townhalls.amount
print(
f"Time: {self.time_formatted} | Townhalls: {townhall_count} | Supply: {supply_used}/{supply_cap}"
)
# Example of a simple decision:
if supply_used > supply_cap * 0.7 and self.can_afford(
UnitTypeId.SUPPLYDEPOT
):
print("ACTION: Supply is low, should build a Supply Depot.")
if __name__ == "__main__":
run_game(
maps.get("AbyssalReefLE"),
[
Bot(Race.Terran, SmartBot()),
Computer(Race.Zerg, Difficulty.Easy),
],
realtime=True,
)