Skip to content
AI360XpertBeta

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.

Naive self-play spins Rock to Paper to Scissors forever, while fictitious play answers the average 0.5, 0.3 and 0.2 with Paper at 0.30 and spirals to Nash
Naive self-play spins Rock to Paper to Scissors forever, while fictitious play answers the average 0.5, 0.3 and 0.2 with Paper at 0.30 and spirals to Nash

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:

πt+1≈arg⁡max⁡π  E[R(π,πt)]\pi_{t+1} \approx \arg\max_{\pi} \; \mathbb{E}\left[ R(\pi, \pi_t) \right]

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 πt\pi_t, 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 πˉt\bar{\pi}_t, 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 MM where rows are your actions, columns are the opponent actions, and entries are your reward. The opponent gets −M-M in a zero-sum game. Let πˉt\bar{\pi}_t be the empirical frequency of opponent actions through round tt. The fictitious play reply is a best response to that average:

at+1∈arg⁡max⁡a  (Mπˉt)aa_{t+1} \in \arg\max_a \; (M \bar{\pi}_t)_a

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 1/t1/t and the average spirals inward instead of jumping.

Why the latest checkpoint spins

Naive self-play sets πˉt\bar{\pi}_t 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:

M=(0−1110−1−110)M = \begin{pmatrix} 0 & -1 & 1 \\ 1 & 0 & -1 \\ -1 & 1 & 0 \end{pmatrix}

After 10 rounds the opponent played Rock 5 times, Paper 3 times, Scissors 2 times, so:

πˉ=[0.50,0.30,0.20]\bar{\pi} = [0.50, 0.30, 0.20]

Score each reply by MπˉM \bar{\pi}:

  • Rock: 0×0.50+(−1)×0.30+1×0.20=−0.100 \times 0.50 + (-1) \times 0.30 + 1 \times 0.20 = -0.10.
  • Paper: 1×0.50+0×0.30+(−1)×0.20=+0.301 \times 0.50 + 0 \times 0.30 + (-1) \times 0.20 = +0.30.
  • Scissors: (−1)×0.50+1×0.30+0×0.20=−0.20(-1) \times 0.50 + 1 \times 0.30 + 0 \times 0.20 = -0.20.

Best response is Paper at +0.30+0.30. 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 1/111/11, 1/121/12, and so on. By a few thousand rounds both averages sit near [1/3,1/3,1/3][1/3, 1/3, 1/3], 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.3

Index 1 is Paper at +0.30+0.30, 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 πt+1≈arg⁡max⁡πE[R(π,πt)]\pi_{t+1} \approx \arg\max_{\pi} \mathbb{E}[R(\pi, \pi_t)].
  • Answering only the latest checkpoint cycles in non-transitive games: Rock to Paper to Scissors forever.
  • Fictitious play answers the empirical average πˉt\bar{\pi}_t instead, and the average converges to minimax Nash in finite zero-sum games.
  • Against [0.50,0.30,0.20][0.50, 0.30, 0.20], replies score −0.10-0.10, +0.30+0.30 and −0.20-0.20, so Paper is best at +0.30+0.30.
  • 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.