GA: StarCraft 2 Build Order Optimizer

Evolving StarCraft 2 build orders with a genetic algorithm — a Python port of SCFusion

Overview

GA is my Python port of SCFusion, an older C++ program that used a genetic algorithm to search for StarCraft 2 build orders. The premise is what sold me on porting it: a build order is just a sequence of commands — train a unit, construct a building, research an upgrade — which makes it a natural chromosome. Generate a population of random build orders, simulate each one under the game's timing and resource rules, score it against whatever you're optimizing for (a unit count, a timing), then breed and mutate the best performers and repeat. Nobody designs the strategy; it falls out of the search.

The port wraps all of this in a PySide6 desktop app called SpearOfAdun, with a build order editor and live progress while the algorithm runs. All three races are supported. There's also an NN/ directory meant for neural-network fitness evaluation — that part is planned and not built yet.

How It Works

The SC2 side is a simulation engine, not the actual game. It tracks minerals, gas, supply, units, buildings, and research dependencies, and works out exactly when each command in a chromosome can execute. That gives the fitness function precise timings to score against instead of guesses.

The GA side has the usual machinery plus a few things that matter in practice: elitism so the best solutions survive each generation, mutation rates that adapt to population diversity, and stagnation detection so a run that has stopped improving doesn't just spin. For longer runs there's the Village system — multiple populations evolving in parallel on separate threads, each with its own parameters. There's also an A* pathfinding module for searching build order decision trees directly rather than evolving them.

Module Purpose Key Components
GA Engine Genetic algorithm core Population management, evolution, fitness evaluation
SC2 Simulation Game state simulation Unit tracking, resource management, timing analysis
GUI Framework User interface Main window, child windows, resource management
A* Pathfinding Strategic pathfinding Build order optimization, decision trees

The rough shape of the code:

SpearOfAdunApp (Application)
├── MainWindow (GUI Container)
│   └── ChildWindow (Build Order Editor)
├── GAEngine (Genetic Algorithm)
│   ├── GAPopulation (Population Management)
│   ├── GAChromosome (Build Order Representation)
│   └── GAConfiguration (Algorithm Parameters)
└── SC2Engine (Game Simulation)
    ├── SC2State (Game State)
    ├── RaceManager (Race-Specific Logic)
    └── CommandProcessor (Action Execution)

Technology

Python 3.13 and the usual scientific stack, with Poetry for packaging and Ruff, MyPy, and Pytest keeping me honest.

Technology Version Purpose
Python 3.13+ Core programming language
PySide6 6.9.1+ Cross-platform GUI framework
NumPy 2.3.2+ Numerical computations
SciPy 1.16.0+ Scientific computing
Pandas 2.3.1+ Data manipulation
Matplotlib 3.10.3+ Data visualization

Usage

# Clone the repository
git clone [repository-url]
cd GA

# Install dependencies using Poetry
poetry install

# Run the application
poetry run spearofadun

The GUI is the main way in: pick a race, set the GA parameters, say what you're optimizing for, and let it run. But the engine works fine from code too:

from ga_sc.GA.configuration import GAConfiguration
from ga_sc.GA.engine import GAEngine

# Create custom configuration
config = GAConfiguration(
    population_size=100,
    generations=50,
    mutation_rate=0.1,
    crossover_rate=0.8,
    elitism_count=5
)

# Initialize engine
engine = GAEngine(config)

# Run optimization
best_chromosome = engine.optimize()

Parallel runs go through the Village system:

from ga_sc.GA.Village import VillageManager

# Create multi-village optimization
manager = VillageManager(config, stagnation_limit=10)

# Add villages with different parameters
village1 = manager.add_village(population_limit=50, initial_population=25)
village2 = manager.add_village(population_limit=75, initial_population=35)

# Run parallel optimization
manager.start_all_villages()

Fitness functions are pluggable, so you can weight whatever you actually care about:

def custom_fitness(chromosome, game_state):
    """Custom fitness evaluation function."""
    timing_score = evaluate_timing(chromosome)
    resource_efficiency = evaluate_resources(chromosome)
    unit_composition = evaluate_composition(chromosome)
    
    return (timing_score * 0.4 + 
            resource_efficiency * 0.3 + 
            unit_composition * 0.3)

If you want to poke around the source, this is the layout:

GA/
├── ga_sc/                    # Main package
│   ├── application.py        # Application entry point
│   ├── main_window.py        # Main GUI window
│   ├── child_window.py       # Build order editor
│   ├── GA/                   # Genetic algorithm core
│   │   ├── engine.py         # Main GA engine
│   │   ├── population.py     # Population management
│   │   ├── chromosome.py     # Chromosome representation
│   │   ├── configuration.py  # GA parameters
│   │   ├── Village/          # Multi-threading system
│   │   ├── NN/              # Neural network integration
│   │   └── calc/            # Fitness calculators
│   ├── SC2/                 # StarCraft 2 simulation
│   │   ├── engine.py        # Game simulation engine
│   │   ├── state.py         # Game state management
│   │   ├── race_manager.py  # Race-specific logic
│   │   ├── unit.py          # Unit definitions
│   │   ├── building.py      # Building definitions
│   │   └── command.py       # Command processing
│   ├── AStar/               # Pathfinding algorithms
│   ├── ui/                  # GUI components
│   ├── resources/           # Asset management
│   └── base/                # Utility functions
├── tests/                   # Test suite
├── debug_scripts/           # Development tools
├── pyproject.toml           # Project configuration
└── README.md               # Documentation