Case study · 2026

RL Soccer Agent

A tabular Q-learning agent that learns to play 2-a-side soccer in your browser — and the playable proof that RL is more subtle than the tutorials suggest.

playableTypeScriptQ-learningReactCanvas

rl-agent ~ training
Watch the AI learn to play soccer, then challenge it yourself
Episode: 0
Goals: 0
Success: 0%
Skill: Beginner
AI  Ball  Defender
Lr
0.15
Cur
1.00
Live — the agent trains in your browser. Switch to 1v1 to play against it.

The problem

Most RL demos stop at CartPole. I wanted an agent I could actually play against — something with enough state structure that the interesting parts of reinforcement learning (exploration decay, reward shaping, curriculum) would stop being abstract.

The game: 2-a-side soccer on a 10×7 grid. The agent sees the ball, both players, and the goals; it picks one of five actions per tick; it trains live in the browser tab you're reading this in.

How it works

The agent is tabular Q-learning with an ε-greedy policy — no neural network, on purpose. When the function approximator is a lookup table, you can watch every update happen and debug by inspection:

# the whole idea, in one line
Q[s][a] += alpha * (reward + gamma * max(Q[s']) - Q[s][a])
  • State (~discretized): ball position, agent position, opponent position, ball ownership
  • Reward: goal scored, plus small shaping terms (moving the ball toward goal, stealing possession)
  • Training: self-play against a scripted bot, ε decaying from 1.0 → 0.01, ~5,000 episodes to competence

What I learned

The first version learned to camp its own goal. Shaping rewards that looked sensible — "reward moving toward the opponent's goal" — created a local optimum where never risking possession beat trying to score. The fix was a curriculum: train scoring against an empty net first, then introduce the defender. That ordering mattered more than any hyperparameter I touched.

The full writeup of what broke is in Learning Blogs.