Guide: Adding Bot Mode to Your Phaser Games
A step-by-step guide to implementing autonomous bot players in Phaser.js — flee steering, wander behavior, edge avoidance, and LLM integration for automated game testing.
Every game on this site with a bot mode (?bot=true) shares one code pattern. The bots feed an LLM feedback loop — they play against themselves, generating telemetry that compares model performance across games. This guide walks through that pattern so you can add it to your own Phaser.js games.
The Bot Mode Pattern
Bot mode adds a simple question to your game loop: “Is a human or AI controlling the player?” Everything below branches on that answer.
Step 1: URL Parameter Detection
Read the bot parameter from the URL in create():
const params = new URLSearchParams(window.location.search);
this.botMode = params.get('bot') === 'true';
this.botTimer = 0;
this.botDir = { x: 0, y: 0 };
When the page loads as /game/?bot=true, this.botMode is true and the bot AI takes over. Otherwise the keyboard controls work normally.
Step 2: Flee Steering
The core of bot movement is flee steering — find the nearest enemy and move away from it. This runs in update():
update(time) {
if (this.botMode) {
this.botTimer += 16; // ~60fps per tick
this.runBotAI();
} else {
// normal keyboard controls
}
}
runBotAI() {
if (!this.player || !this.player.active) return;
let fleeX = 0, fleeY = 0;
let closestDist = 300; // panic distance
// Find nearest enemy
this.enemies.getChildren().forEach(enemy => {
if (!enemy.active) return;
const dx = this.player.x - enemy.x;
const dy = this.player.y - enemy.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < closestDist && dist > 0) {
closestDist = dist;
fleeX = dx / dist;
fleeY = dy / dist;
}
});
// Flee from nearest threat
const speed = 200;
let vx = fleeX * speed;
let vy = fleeY * speed;
this.player.setVelocity(vx, vy);
}
The flee vector points away from the closest enemy. The bot only reacts when an enemy is within closestDist (300px). Outside that range it has no fear response — that’s where wander behavior comes in.
Step 3: Wander Behavior
When no enemy is close, the bot shouldn’t sit still. Add wander steering that changes direction periodically:
runBotAI() {
let vx = 0, vy = 0;
let threatDetected = false;
let closestDist = 300;
// Check for nearby enemies
this.enemies.getChildren().forEach(enemy => {
if (!enemy.active) return;
const dx = this.player.x - enemy.x;
const dy = this.player.y - enemy.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < closestDist && dist > 0) {
threatDetected = true;
closestDist = dist;
vx = (dx / dist) * 200;
vy = (dy / dist) * 200;
}
});
if (!threatDetected) {
// Wander — change direction every ~1 second
if (this.botTimer > 1000) {
this.botDir = {
x: (Math.random() - 0.5) * 2,
y: (Math.random() - 0.5) * 2
};
this.botTimer = 0;
}
vx = this.botDir.x * 100;
vy = this.botDir.y * 100;
}
// Apply edge avoidance
const margin = 50;
if (this.player.x < margin) vx += 150;
if (this.player.x > 630) vx -= 150;
if (this.player.y < margin) vy += 150;
if (this.player.y > 430) vy -= 150;
this.player.setVelocity(vx, vy);
}
The bot picks a random direction every second and moves that way. When an enemy gets within 300px, it switches to flee mode. Simple, effective, and the pattern every working bot in our 18-model benchmark used.
Step 4: Edge Avoidance
The edge-avoidance lines at the bottom push the bot back toward the center when it drifts to the game boundary. Without this, bots get stuck on walls — enemies pin them at the edge and they have no room to flee.
The margin value (50px) is the distance from each wall where repulsion kicks in. Tune it for your arena size. For a 680×480 canvas, 50px gives 80% of the arena as safe space and 20% as buffer zones.
Step 5: Toggle Controls
In create(), set up keyboard controls conditionally:
create() {
// ... setup player, enemies, etc.
if (!this.botMode) {
this.cursors = this.input.keyboard.createCursorKeys();
this.wasd = this.input.keyboard.addKeys({
up: 'W', down: 'S', left: 'A', right: 'D'
});
}
}
In update(), branch on this.botMode:
if (this.botMode) {
this.botTimer += this.game.loop.delta;
this.runBotAI();
} else {
// keyboard movement
this.player.setVelocity(0, 0);
if (this.cursors.left.isDown || this.wasd.left.isDown) vx = -160;
if (this.cursors.right.isDown || this.wasd.right.isDown) vx = 160;
if (this.cursors.up.isDown || this.wasd.up.isDown) vy = -160;
if (this.cursors.down.isDown || this.wasd.down.isDown) vy = 160;
this.player.setVelocity(vx, vy);
}
Step 6: Add the Link
Add a bot mode link to your game’s controls section:
<div class="controls">
Move with <kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> ·
Press <kbd>R</kbd> to restart ·
<a href="?bot=true" style="color:#00d4ff;">Bot mode</a>
</div>
Clicking the link reloads the page with ?bot=true appended. The create() method detects it, skips keyboard setup, and the bot takes over.
Step 7: LLM Bot (Optional)
You can swap the math-based bot for a real LLM. The game sends the current game state (player position, enemy positions, score) to a Cloudflare Pages Function at /api/bot-brain, which calls an LLM API and returns the next movement direction.
The function receives:
{
"player": {"x": 340, "y": 240},
"enemies": [{"x": 400, "y": 200}, {"x": 100, "y": 300}],
"score": 42
}
And returns:
{"dir": {"x": 1, "y": -0.5}}
The bot applies this as a normalized movement vector. This is how our LLM benchmark variants work — each model plays the game through its own reasoning, not a hardcoded flee algorithm.
Why Bot Mode Matters
Three use cases:
- Automated testing — Run bot mode for N minutes, record whether the game crashes or hits errors. Catches regressions before deploy.
- LLM feedback loop — Every model that builds a game on this site also builds a bot for it. The bot’s performance (survival time, score) becomes a benchmark metric that feeds back into model selection.
- Content generation — Bot-vs-bot matches produce consistent gameplay footage without a human at the keyboard.
Key Takeaways
- Flee steering (move away from nearest enemy) is the simplest working bot AI — about 10 lines of logic.
- Edge avoidance prevents wall-stuck scenarios. Always include it.
- Wander fills the gap when no threat is nearby.
- The
?bot=trueURL parameter pattern costs nothing and makes your game playable without human input. - An LLM bot variant turns each model into a player — useful for benchmarks and demos.
Further Reading
- DeepSeek API docs — the model behind most bots on this site
- Phaser 3 Arcade Physics — velocity, collision, and overlap docs
- Cloudflare Pages Functions — serverless backend for LLM bot calls
- Our 18-model LLM benchmark — how bot mode powered a real model comparison