A modern, modular Haskell framework for developing StarCraft II bots with a clean separation between low-level API and high-level strategic systems.
The project is organized into three distinct packages:
The foundation for all bots. Provides everything needed to write a basic bot without opinions about strategy or tactics.
Key modules:
Agent— IO-based agent typeclass (agentStep :: a -> ResponseObservation -> UnitAbilities -> IO (a, StepPlan))Actions— Unit commands and actionsSC2.Launcher.*— Game orchestration (BotConfig, LoadConfig, Participant, Game)SC2.Proto.*— Protocol buffer definitionsSC2.Client— Connection managementSC2.Ids.*— Ability, Unit Type, Upgrade IDsGeometry— Point math and spatial utilitiesUnitAbilities— Ability availability and querying
Example:
data MyBot = MyBot { /* your state */ }
instance Agent MyBot where
agentStep bot obs abilities = do
let actions = [ ... ] -- plan actions
return (bot', StepPlan actions [])An opinionated, high-level framework built on StepMonad for strategic and tactical systems. Optional—use only what you need.
Key modules:
StepMonad— Core monad stack (Reader/State/Writer) for stateful computationsStepMonadUtils— Utilities and helpersObservation— Unit/resource queries, clustering, updatesUnits— Unit analysis and queriesSC2.Grid.*— Unboxed grid, pathfinding, chokepoint detectionSC2.Geometry— Spatial utilitiesSC2.Army.*— High-level unit orchestrationSC2.Squad.*— Squad FSM and micro managementSC2.TechTree— Tech tree (TH-generated from data.json)SC2.Utils— Common utilities
Example:
import StepMonad
buildPylons :: StepMonad ()
buildPylons = do
probes <- unitsOfType ProtossProbe
nexus <- unitsOfType ProtossNexus
-- ... strategic logicA complete Protoss bot example using both sc2api and sc2monad.
Modules:
Main— Game loop orchestrationTestBot— Agent implementation with multi-phase logicBotDynamicState— Mutable bot state (grid, army, observation)ArmyLogic— Macro logic (squad forming, unit production)AgentBulidUtils— Build order utilities and placement finding
- GHC 9.10.1+ (via Stack)
- StarCraft II client running locally or remote
- bot-config.yaml with map and connection settings
stack buildBuild a specific package:
stack build sc2api # Just the API
stack build sc2monad # Framework + tests
stack build lambdarookie01 # Example botstack teststack run lambdarookie01The bot reads configuration from bot-config.yaml and connects to StarCraft II.
Use sc2api alone if you want complete control:
import Agent
import SC2.Launcher.Game
import SC2.Launcher.Participant
data YourBot = YourBot { /* state */ }
instance Agent YourBot where
agentStep bot obs abilities = do
let actions = yourDecisionLogic obs
return (bot, StepPlan actions [])
main :: IO ()
main = playMatch (buildHostRuntime (Player yourBotInstance) runtimeConfig)Use sc2monad for higher-level abstractions:
import StepMonad
import Agent
data YourBot = YourBot { state :: DynamicState }
instance Agent YourBot where
agentStep bot obs abilities = do
let (newState, plan) = runStepMonad strats obs abilities (bot.state)
return (bot { state = newState }, plan)
strats :: StepMonad ()
strats = do
defenders <- defendBase
attackers <- formAttackSquad
-- ... more logicCreate bot-config.yaml:
# Bot name
botName: YourBotName
# Map info
map:
filename: "StarCraftIIPath/Maps/YourMap.SC2Map"
# Connection
port: 5000
serverAddress: localhostRun with:
stack run lambdarookie01 -- --config bot-config.yamlEvery bot implements the Agent typeclass with agentStep:
- Input: current state, observation, available abilities
- Output: updated state + StepPlan (actions, debug commands, chat)
A Reader/State/Writer stack for stateful computations:
StepMonad r s w a = Reader AgentStatic -> State DynamicState -> Writer (StepPlan) -> a- Read: Static game info (unit types, abilities, tech tree)
- State: Dynamic bot state (units, grid, army)
- Write: Accumulate actions and debug info into StepPlan
The bot's decision output:
data StepPlan = StepPlan
{ actions :: [Action] -- Unit commands
, debug :: [DebugCommand] -- Debug visuals
}The framework includes advanced map analysis capabilities in SC2.Grid.Algo, enabling strategic positioning and terrain-aware decision making.
Automatically identify narrow passages and strategic control points using raycasting algorithms:
-- Find a chokepoint from a starting position
findChokePoint :: Grid -> Int -> TilePos -> Maybe [TilePos]
-- Find all significant chokepoints on the map
findAllChokePoints :: Grid -> ([Ray], Grid)How it works:
- Casts rays in multiple directions (0°, 45°, 90°, 135°, 180°) from each open tile
- Detects narrow passages where opposing rays meet within a threshold distance
- Validates that chokepoints separate regions of sufficient volume (configurable minimum)
- Returns ray coordinates representing the chokepoint geometry
Use cases:
- Position defensive units at chokepoints
- Wall-off narrow passages with buildings
- Identify attack/retreat paths through narrow terrain
- Plan expansion placement away from vulnerable chokepoints
Partition the map into distinct regions connected by chokepoints:
-- Segment map into distinct regions
gridSegment :: Grid -> [(RegionId, Region)]
-- Build adjacency graph of regions
buildRegionGraph :: [(RegionId, Region)] -> RegionLookup -> RegionGraph
-- Lookup which region a tile belongs to
buildRegionLookup :: [(RegionId, Region)] -> RegionLookupHow it works:
- Uses flood-fill to identify contiguous walkable areas
- Each region is a connected component of open tiles
- Builds graph showing which regions are adjacent (connected through chokepoints)
- Provides O(1) lookup from tile position to region ID
Use cases:
- Strategic map control: identify which regions you control vs enemy
- Pathfinding: find sequence of regions to traverse from A to B
- Expansion planning: identify isolated regions suitable for bases
- Threat assessment: determine if enemy can reach your base without crossing chokepoints
Navigate between regions using BFS on the region graph:
-- Find shortest path (in regions) from start to end
regionGraphBfs :: RegionGraph -> RegionId -> RegionId -> [RegionId]Use cases:
- High-level strategic pathfinding (region-to-region, then detailed within region)
- Identify how many chokepoints separate two locations
- Plan attack routes that minimize chokepoint traversals
- Predict enemy attack vectors
import SC2.Grid.Algo
analyzeMap :: Grid -> (RegionGraph, RegionLookup)
analyzeMap grid = (regionGraph, regionLookup)
where
-- Segment map into regions
regions = gridSegment grid
-- Build lookup table
regionLookup = buildRegionLookup regions
-- Build region adjacency graph
regionGraph = buildRegionGraph regions regionLookup
-- Find path from your base to enemy base
findAttackPath :: TilePos -> TilePos -> RegionGraph -> RegionLookup -> [RegionId]
findAttackPath myBase enemyBase rg rl =
regionGraphBfs rg startRegion endRegion
where
Just startRegion = HashMap.lookup myBase rl
Just endRegion = HashMap.lookup enemyBase rl
-- Identify all chokepoints
findDefensivePositions :: Grid -> [Ray]
findDefensivePositions grid = chokepoints
where
(chokepoints, _) = findAllChokePoints gridAdditional grid analysis functions:
gridBfs— Breadth-first search with custom transitions and goal predicatesgridRaycastTile— Cast ray from origin in direction until obstaclegridSplitByRay— Split grid into regions separated by a ray (chokepoint)checkVolumes— Verify regions separated by ray meet minimum size requirementscomplementRegionLookup— Extend region lookup to tiles near region boundaries
- Performance: Unboxed grids (
Data.Vector.Unboxed) for cache-efficient operations - Precision: All algorithms work on tile-level granularity (grid cells, not game coordinates)
- Validation: Chokepoint detection includes volume checks to avoid false positives in open areas
- Configurability: Minimum region sizes and chokepoint thresholds are parameterized
Tests cover:
- Grid algorithms (pathfinding, chokepoints, placement)
- Unit queries and clustering
- TechTree queries
- StepMonad operations
- Observation updates
Run with:
stack testMIT
- StarCraft II API Documentation
- SC2HS GitHub
- Aiurgaze
- Haskell Resources: Haskell.org