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);
}

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> &middot;
  Press <kbd>R</kbd> to restart &middot;
  <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=true URL 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