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