Self-Play and Fictitious Play
Self-play trains an agent against itself so the opponent improves automatically. Fictitious play answers the average of past opponents, which breaks cycles.
Why Does This Exist?
Two-player zero-sum games have no fixed curriculum. A hand-built opponent that challenges you at week one is trivial by week ten, and hiring humans for every training step does not scale. Markov games write the rules for these worlds, and multi-agent RL trains the teams, but neither says who you should practice against.
Self-play answers with yourself:
As you improve, your opponent improves at the same pace, so the difficulty calibrates itself. Chess, Go, and StarCraft systems all lean on this loop.
Naive self-play answers only the latest checkpoint , and that breaks in games with circular strengths. Rock beats Scissors, Scissors beats Paper, Paper beats Rock, so no single style stays best. The agent learns Paper to beat Rock, meets Paper, switches to Scissors, meets Scissors, switches back to Rock, and chases its own tail while forgetting basics. Fictitious play (Brown, 1951) fixes the loop: answer the empirical average of all past opponents , not the latest one. The average moves slowly, gimmicks that beat only yesterday fail against history, and time-averaged play converges to a minimax Nash equilibrium in finite zero-sum games (Robinson, 1951). Neural fictitious self-play and league training scale the same idea to poker and StarCraft. For the cooperative side of the same multi-agent world see cooperative versus competitive environments.
Think of It Like This
Sparring only yesterday versus sparring the archive
A boxer spars only against yesterday version of themselves. Yesterday they leaned left, so today they drill a right hook that punishes leaning left. Tomorrow they lean right to dodge that hook, and the day after they forget how to block a jab because yesterday never threw one. They spin in a tight circle and lose to any outsider with fundamentals.
Fictitious play keeps every past version on tape and spars the average: the brawler from January, the counterpuncher from March, the tired version from June. A trick that beats only yesterday version loses to the archive, so only well-rounded skills survive.
Where it stops: boxers tire and forget tapes. Algorithms store exact frequencies, replay buffers, and payoff tables over thousands of checkpoints, and they average with weights instead of memory.
How It Actually Works
Two players share a payoff matrix where rows are your actions, columns are the opponent actions, and entries are your reward. The opponent gets in a zero-sum game. Let be the empirical frequency of opponent actions through round . The fictitious play reply is a best response to that average:
In words: weight each column by how often the opponent played it, score every row, and play the highest-scoring row. Each round adds one observation, so step sizes shrink like and the average spirals inward instead of jumping.
Why the latest checkpoint spins
Naive self-play sets to one point: yesterday action only. In rock-paper-scissors the best response to Rock is Paper, to Paper is Scissors, to Scissors is Rock, so play cycles Rock to Paper to Scissors forever. Nothing accumulates, so nothing converges. History averaging fixes exactly this: the average after Rock, Paper, Scissors in equal counts is uniform, whose best responses tie, which stops the chase.
Neural and league versions in one paragraph each
Neural fictitious self-play (Heinrich and Silver, 2016) keeps two networks for large games where counting is impossible: a Q-network that learns the best response to current average behavior, and a supervised average-policy network trained on a reservoir of its own past best responses. The agent mixes between them, exploring with the first and banking stability with the second.
League training and policy-space response oracles keep a population instead of one average: main agents that play the meta-Nash mixture over the pool, plus exploiters whose job is to find holes in the main agent and force the pool to cover them. AlphaStar used this shape for StarCraft. One average blurs incompatible styles; a population keeps them.
Worked example
Rock-paper-scissors with rows and columns ordered Rock, Paper, Scissors:
After 10 rounds the opponent played Rock 5 times, Paper 3 times, Scissors 2 times, so:
Score each reply by :
- Rock: .
- Paper: .
- Scissors: .
Best response is Paper at . Playing Paper adds one more Paper to your own history, which nudges the opponent average toward Scissors next, then yours toward Rock, but each nudge now weighs , , and so on. By a few thousand rounds both averages sit near , the minimax Nash point. Those three payoffs are what the diagram draws.
Code
Scores the worked example and reads off the best response. No sampling, just the matrix product.
import numpy as np
m = np.array([[0.0, -1.0, 1.0], [1.0, 0.0, -1.0], [-1.0, 1.0, 0.0]])opp = np.array([0.50, 0.30, 0.20])payoffs = m @ oppprint([f"{v:.2f}" for v in payoffs])# -> ['-0.10', '0.30', '-0.20']print(int(np.argmax(payoffs)))# -> 1print(round(float(np.max(payoffs)), 2))# -> 0.3Index 1 is Paper at , matching the hand math. Repeating this reply-against-average loop drives both frequencies toward one third each.
Watch Out For
Training only against the latest checkpoint
The symptom: win rate against yesterday climbs while win rate against last month collapses. In StarCraft terms the agent stops defending cannon rushes because recent opponents stopped rushing. In rock-paper-scissors terms it plays one move forever and loses to the move that beats it.
Keep a pool of past checkpoints and sample opponents from it, not only the newest weights. Mix in legacy versions on purpose. If performance against history falls while performance against the latest rises, the pool is too narrow.
Averaging styles that cannot blend
The symptom: one average policy plays a blurry middle that loses to every specialist. Rushing and turtling average into walking halfway across the map and stopping, which neither parent strategy endorses.
Use a population with separate members instead of one average when styles are incompatible. Let the meta-Nash mixture choose among whole strategies rather than forcing one network to be all of them at once.
The Quick Version
- Self-play answers yourself so difficulty tracks skill, with the update .
- Answering only the latest checkpoint cycles in non-transitive games: Rock to Paper to Scissors forever.
- Fictitious play answers the empirical average instead, and the average converges to minimax Nash in finite zero-sum games.
- Against , replies score , and , so Paper is best at .
- Neural versions split best response and average policy into two networks; leagues keep a population plus exploiters.
- Keep history wide or the agent forgets old threats while chasing new ones.