GAME DESIGN
April 2026
How We Built Territory Capture AI for Grid.io
Building multiplayer territory games presents a classic challenge: how do you make AI opponents feel intelligent without burning the CPU? In Grid.io, we handle 7 simultaneous AI agents, each running their own expansion logic at 60fps in the browser. Here's how we pulled it off.
The Core Loop
Each AI agent operates on a simple state machine: Expand → Capture → Defend. When an agent is safely inside its own territory, it explores outward. When it senses a rival trail is nearby, it either retreats or attempts a cut-off maneuver. The key insight was that reactive behavior looks smarter than planned behavior at this scale.
function decideDirection(agent, grid) {
const threat = detectNearestRival(agent, grid);
if (threat && threat.distance < DANGER_RADIUS) {
return evadeOrCutoff(agent, threat);
}
return expandGreedily(agent, grid);
}
Flood Fill for Territory
When a player closes a loop, we use a canvas-based flood fill starting from the center of the enclosed region. This runs in O(n) where n is the enclosed area, and on modern hardware it's imperceptible even for large captures. The animation — where the color washes across claimed cells — is simply the flood fill visualized in real time at 4 cells per frame.
Performance Budget
Each AI agent gets a 2ms CPU budget per frame. We use a simple priority queue so the most "active" agent (highest threat/opportunity score) gets computed first. If the budget runs out, remaining agents skip their decision and repeat their last direction. Players never notice because AI decisions are cheap and the game runs at 60fps.
💡
Tip: Flood fill territory validation is 10× faster when you maintain a dirty-rect list instead of scanning the full grid every frame.
TUTORIAL
March 2026
Designing a Typing Game That Feels Great: Lessons from Asteroid Typer
A typing game lives or dies on one thing: does every keystroke feel meaningful? In Asteroid Typer, each key you press fires a bullet. Miss a letter, miss the shot. This single design decision changed everything about how we structured the game's feedback loop.
Word Selection Algorithm
We curate words by length-to-difficulty ratio. Short words spawn on small asteroids (low threat, high speed). Long words appear on massive slow-moving rocks (high threat, forgiving pace). This creates a natural flow where players feel constantly busy but never overwhelmed — a state game designers call flow state.
function getWordForAsteroid(radius) {
const tier = Math.floor(radius / 10); // 0-3
const pools = [shortWords, medWords, hardWords, expertWords];
const pool = pools[Math.min(tier, 3)];
return pool[Math.floor(Math.random() * pool.length)];
}
The Powerup Design Philosophy
Each of our four powerups addresses a different player pain state. Time Warp helps when the screen is too crowded. Solar Flare clears when you're about to die. Breaker targets the single most stressful threat. Shield rewards players who are already doing well by removing the anxiety of one mistake ending the run.
Procedural Difficulty Curve
We model difficulty as a function of sector number: spawn rate increases by 12% per sector, minimum word length grows by 0.3 characters per sector, and asteroid speed scales by a square root curve (fast early gains, slower later). This creates the classic "easy to learn, hard to master" feel without any handcrafted level data.
🎮
Design Note: Never punish fast typists by spawning words faster than they can clear. Cap the simultaneous word count at 8 and let difficulty come from word complexity instead.
TECHNICAL
February 2026
Building a Bullet-Hell Shooter in Pure Canvas: Shoot.io Post-Mortem
Shoot.io started as an experiment: how complex can a space shooter get before the HTML5 Canvas starts to choke? The answer surprised us. With careful batching and object pooling, we sustain 60fps with hundreds of simultaneous projectiles, enemies, and particle effects on mid-range mobile hardware.
Object Pooling — The #1 Performance Win
JavaScript's garbage collector is the enemy of smooth gameplay. Every time you create a new object with new Bullet() and then let it get collected, you risk a GC pause mid-game. Object pools pre-allocate a fixed number of objects at startup and recycle them. When a bullet "dies," it goes back to the pool. When a new bullet is needed, it's pulled from the pool and reset — no allocation, no collection.
class ObjectPool {
constructor(factory, size = 100) {
this.pool = Array.from({ length: size }, factory);
this.active = [];
}
acquire() {
const obj = this.pool.pop() || this.factory();
this.active.push(obj);
return obj;
}
release(obj) {
const i = this.active.indexOf(obj);
if (i > -1) this.active.splice(i, 1);
obj.reset();
this.pool.push(obj);
}
}
Canvas Draw Call Batching
Instead of drawing each enemy individually (with separate save(), translate(), restore() calls), we group enemies by type and draw them in a single path where possible. For simple shapes like triangles and hexagons, this cuts draw calls by up to 70%. We also only call clearRect on dirty regions when performance is critical.
Mobile Touch Controls
Portrait-mode mobile games need horizontal drag controls that feel natural. We track the delta between touch start and current position, then apply it as a velocity rather than absolute position — this lets players steer without constantly re-anchoring their thumb. We add 15% easing so the ship glides rather than snapping.
⚡
Performance Tip: Profile your Canvas game with Chrome DevTools' Performance tab. The "Rendering" flame chart will show exactly which draw calls are eating your frame budget.