3.2 - The Game Loop
Your bot's code does not run all at once. It's an event-driven program that reacts to the game's state. The BotAI
class provides a "three-act structure" for your bot's life within a match, giving you specific entry points to insert your logic. Understanding this flow is essential for building a well-structured AI.
The Execution Flow
This diagram illustrates the guaranteed order of execution for the primary lifecycle methods.
(Game Client Loads)
|
v
.--- on_start() --.
| (Runs ONCE) |
`-----------------`
|
v
.--- on_step() ---.
| (Loops) | <------.
| - Perceive | |
| - Decide | | (Runs ~22 times/sec)
| - Act | |
`-----------------`--------`
|
| (Game Ends)
v
.--- on_end() ----.
| (Runs ONCE) |
`-----------------`
|
v
(Program Exits)
The Lifecycle Methods: A Comparison
Method | async def on_start(self) | async def on_step(self, iteration: int) | async def on_end(self, game_result: Result) |
---|---|---|---|
When It Runs | Once, at the very beginning of the match. | Repeatedly, on every game tick (~22.4 times/sec). | Once, after the match has concluded. |
Core Purpose | Initialization. Set up state and perform one-time calculations. | Execution. The main loop for all real-time decision-making. | Analysis & Cleanup. Save data and log the final outcome. |
Key Parameters | self | iteration : The game frame counter. | game_result : The final outcome (Victory, Defeat). |
Common Uses | - Print bot strategy - Analyze map layout - Set initial state ( self.is_attacking = False ) | - Manage economy - Control the army - Scout the enemy - Execute build orders | - Log win/loss rates - Save learned data to a file - Analyze replay data |
Code Example: A Lifecycle in Action
This bot demonstrates the full lifecycle, logs its state at each phase, and uses a time-based trigger to end the game.
Checklist for This Bot:
- Use
on_start
to set up an initial state. - Use
on_step
with a time-based condition to perform a primary action. - Use
on_end
to analyze and report the final result.
# lifecycle_bot.py
from sc2.main import run_game
from sc2.bot_ai import BotAI
from sc2.data import Result, Race
from sc2.player import Bot, Computer, Difficulty
from sc2 import maps
# Constants are better than magic numbers in your code.
GAME_DURATION_SECONDS = 45
class LifecycleBot(BotAI):
"""A bot to demonstrate the on_start, on_step, and on_end lifecycle."""
async def on_start(self):
"""Setup actions at the start of the game."""
self.start_time = self.time
print(f"[{self.time_formatted}] Lifecycle: on_start() initiated.")
print(f"INFO: This game will run for {GAME_DURATION_SECONDS} seconds.")
async def on_step(self, iteration: int):
"""Core logic, executed on every game tick."""
if iteration == 0:
print(f"[{self.time_formatted}] Lifecycle: on_step() first iteration.")
# The main action: leave the game after a set time has passed.
if self.time - self.start_time > GAME_DURATION_SECONDS:
print(
f"[{self.time_formatted}] ACTION: Game duration reached. Leaving game..."
)
# 'self.client.leave()' is an async action, so it must be awaited.
await self.client.leave()
async def on_end(self, game_result: Result):
"""Cleanup and analysis at the end of the game."""
print(f"[{self.time_formatted}] Lifecycle: on_end() initiated.")
print(f"FINAL RESULT: {game_result}")
if game_result == Result.Victory:
print("CONCLUSION: Mission accomplished.")
# The bot leaves, so the result is a Defeat if the opponent is still alive.
elif game_result == Result.Defeat:
print("CONCLUSION: Test concluded as planned.")
if __name__ == "__main__":
run_game(
maps.get("AbyssalReefLE"),
[Bot(Race.Protoss, LifecycleBot()), Computer(Race.Zerg, Difficulty.Easy)],
realtime=False,
)