Skip to main content

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

Methodasync def on_start(self)async def on_step(self, iteration: int)async def on_end(self, game_result: Result)
When It RunsOnce, at the very beginning of the match.Repeatedly, on every game tick (~22.4 times/sec).Once, after the match has concluded.
Core PurposeInitialization. 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 Parametersselfiteration: 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,
)