My solution to Zerg rushes wasn’t more code. It was data
•Drekken
Why hello Reader,
There is this moment where you’re staring at your code and you realize… “omg I’ve been solving the wrong problem…”
That was me last month.
PiG_Bot kept dying to Zerg rushes. Zerglings flooding in before I had anything on the map to stop them. So I did what any reasonable dev would do.
I wrote rules.
“If spawning pool is early, flag it.”
“If gas is taken before expansion, flag it.”
“If drone count drops below X, flag it.”
Worked great. Until it didn’t.
A slightly different timing? Missed it. A variation where they sneak gas late? Missed that too. Every time I patched one hole, two more showed up. I was playing whack-a-mole with IF statements and the moles were winning.
More rules wasn’t going to fix this. I was spending more time debugging detection logic than actually improving the bot.
Let’s break down how to get out of this trap
key sign you’re about to get rushed
Taking the Human approach
If you watch pro players handle rushes, they don’t run through a checklist. They read signals. Pool timing. Drone count. Whether there’s an expansion or gas. Each piece tells them something. It’s closer to intuition than logic.
I wanted that for the bot. So I built a weighting system. Each signal the bot scouts gets points. Early pool? Points. No expansion? Points. Low drone count? More points. Hit a threshold of 6 and it calls a rush.
the rule timing for points. Got these from human and bot games
Then the variants showed up. Speedling rushes have an expansion AND gas. My weighting system saw those signals and went “nah that’s not a rush.” Meanwhile 20 zerglings are sprinting across the map with speed upgrades. False positives everywhere. The weights just couldn’t cover it
Rules Stop Working? Let the Data Decide
That’s when I tried something I hadn’t done in an SC2 bot before. Machine Learning, or rather Logistic regression.
Simply, you give it labeled data, it gives you back a number between 0 and 1. That number is a probability.
So I ran PiG_Bot on ladder for 150 games. Every match against Zerg, the bot logged the signals. Pool timing, expansion, gas, drone count. Then I went back and labeled each one. Rush or not rush. Fed it all to scikit-learn.
“Data, Data everywhere not a log to sync” 🎶
The model looked at those games and figured out the weights on its own.
Pool timing? Massive signal. Weighted heavy.
Gas timing? Barely moved the needle.
Drone count? Somewhere in between.
All those weights I spent weeks hand-tuning. The model found better ones in seconds.
Ok, BUT I didn’t throw out the old system. The two work together now. If the model is 55% confident or higher, the bot trusts it. Below that, it falls back on the original weight-based rules. Way fewer false positives. Catches rushes earlier.
Recognize when hand-tuning stops scaling. The rules need a partner. Label some data. Even a tiny dataset.
You don’t have to pick one or the other. Why Not Both?
Wanted to build walls dynamically but ended up just hard coding it
So the rush detector gets all the glory. But you know what almost stopped this whole project? The wall-off.
I tried calculating placements dynamically using choke point coordinates, but the API gives you all of them in the game. Tried the SC2MapAnalyzer. Tried pathing queries. Every map’s choke is a different size and shape. Nothing worked.
I ended up opening each map in the galaxy editor and hardcoding the coordinates myself. Not Ideal… I know lol. 😅
How does your bot handle map-specific positioning? Pathing? On Start up or Pre-generated? Something else entirely?
Hit reply. I’m genuinely looking for a better way.