LESSON 07 / ONE THOUSAND INDEPENDENT LEARNERS

Learning across an agent population

Train 1,000 independent hockey agents against random peers. Evaluate each against a fixed predictive policy and inspect the distribution of performance.

Ready

Prerun data contains actual recorded benchmarks, not a scripted animation. Its viewer does not include 1,000 model checkpoints, so start a fresh live run to train. Live training runs in a worker; browser tabs may still slow when the device is busy.

Load a run or start training

One round gives every agent one peer match. Benchmarks happen every 10 rounds.

The first checkpoint measures untrained agents at round 0.
Independent agents1,000
Median return rate—
Bottom / top decile—
Reference vs itself—

Every square is one persistent agent

Agent IDs stay in the same place across time. Click a square, or use the agent-number control, to inspect its trajectory.

Agent 1Agent 2Agent 3Agent 4Agent 5Agent 6Agent 7Agent 8Agent 9Agent 10Agent 11Agent 12Agent 13Agent 14Agent 15Agent 16Agent 17Agent 18Agent 19Agent 20Agent 21Agent 22Agent 23Agent 24Agent 25Agent 26Agent 27Agent 28Agent 29Agent 30Agent 31Agent 32Agent 33Agent 34Agent 35Agent 36Agent 37Agent 38Agent 39Agent 40Agent 41Agent 42Agent 43Agent 44Agent 45Agent 46Agent 47Agent 48Agent 49Agent 50Agent 51Agent 52Agent 53Agent 54Agent 55Agent 56Agent 57Agent 58Agent 59Agent 60Agent 61Agent 62Agent 63Agent 64Agent 65Agent 66Agent 67Agent 68Agent 69Agent 70Agent 71Agent 72Agent 73Agent 74Agent 75Agent 76Agent 77Agent 78Agent 79Agent 80Agent 81Agent 82Agent 83Agent 84Agent 85Agent 86Agent 87Agent 88Agent 89Agent 90Agent 91Agent 92Agent 93Agent 94Agent 95Agent 96Agent 97Agent 98Agent 99Agent 100Agent 101Agent 102Agent 103Agent 104Agent 105Agent 106Agent 107Agent 108Agent 109Agent 110Agent 111Agent 112Agent 113Agent 114Agent 115Agent 116Agent 117Agent 118Agent 119Agent 120Agent 121Agent 122Agent 123Agent 124Agent 125Agent 126Agent 127Agent 128Agent 129Agent 130Agent 131Agent 132Agent 133Agent 134Agent 135Agent 136Agent 137Agent 138Agent 139Agent 140Agent 141Agent 142Agent 143Agent 144Agent 145Agent 146Agent 147Agent 148Agent 149Agent 150Agent 151Agent 152Agent 153Agent 154Agent 155Agent 156Agent 157Agent 158Agent 159Agent 160Agent 161Agent 162Agent 163Agent 164Agent 165Agent 166Agent 167Agent 168Agent 169Agent 170Agent 171Agent 172Agent 173Agent 174Agent 175Agent 176Agent 177Agent 178Agent 179Agent 180Agent 181Agent 182Agent 183Agent 184Agent 185Agent 186Agent 187Agent 188Agent 189Agent 190Agent 191Agent 192Agent 193Agent 194Agent 195Agent 196Agent 197Agent 198Agent 199Agent 200Agent 201Agent 202Agent 203Agent 204Agent 205Agent 206Agent 207Agent 208Agent 209Agent 210Agent 211Agent 212Agent 213Agent 214Agent 215Agent 216Agent 217Agent 218Agent 219Agent 220Agent 221Agent 222Agent 223Agent 224Agent 225Agent 226Agent 227Agent 228Agent 229Agent 230Agent 231Agent 232Agent 233Agent 234Agent 235Agent 236Agent 237Agent 238Agent 239Agent 240Agent 241Agent 242Agent 243Agent 244Agent 245Agent 246Agent 247Agent 248Agent 249Agent 250Agent 251Agent 252Agent 253Agent 254Agent 255Agent 256Agent 257Agent 258Agent 259Agent 260Agent 261Agent 262Agent 263Agent 264Agent 265Agent 266Agent 267Agent 268Agent 269Agent 270Agent 271Agent 272Agent 273Agent 274Agent 275Agent 276Agent 277Agent 278Agent 279Agent 280Agent 281Agent 282Agent 283Agent 284Agent 285Agent 286Agent 287Agent 288Agent 289Agent 290Agent 291Agent 292Agent 293Agent 294Agent 295Agent 296Agent 297Agent 298Agent 299Agent 300Agent 301Agent 302Agent 303Agent 304Agent 305Agent 306Agent 307Agent 308Agent 309Agent 310Agent 311Agent 312Agent 313Agent 314Agent 315Agent 316Agent 317Agent 318Agent 319Agent 320Agent 321Agent 322Agent 323Agent 324Agent 325Agent 326Agent 327Agent 328Agent 329Agent 330Agent 331Agent 332Agent 333Agent 334Agent 335Agent 336Agent 337Agent 338Agent 339Agent 340Agent 341Agent 342Agent 343Agent 344Agent 345Agent 346Agent 347Agent 348Agent 349Agent 350Agent 351Agent 352Agent 353Agent 354Agent 355Agent 356Agent 357Agent 358Agent 359Agent 360Agent 361Agent 362Agent 363Agent 364Agent 365Agent 366Agent 367Agent 368Agent 369Agent 370Agent 371Agent 372Agent 373Agent 374Agent 375Agent 376Agent 377Agent 378Agent 379Agent 380Agent 381Agent 382Agent 383Agent 384Agent 385Agent 386Agent 387Agent 388Agent 389Agent 390Agent 391Agent 392Agent 393Agent 394Agent 395Agent 396Agent 397Agent 398Agent 399Agent 400Agent 401Agent 402Agent 403Agent 404Agent 405Agent 406Agent 407Agent 408Agent 409Agent 410Agent 411Agent 412Agent 413Agent 414Agent 415Agent 416Agent 417Agent 418Agent 419Agent 420Agent 421Agent 422Agent 423Agent 424Agent 425Agent 426Agent 427Agent 428Agent 429Agent 430Agent 431Agent 432Agent 433Agent 434Agent 435Agent 436Agent 437Agent 438Agent 439Agent 440Agent 441Agent 442Agent 443Agent 444Agent 445Agent 446Agent 447Agent 448Agent 449Agent 450Agent 451Agent 452Agent 453Agent 454Agent 455Agent 456Agent 457Agent 458Agent 459Agent 460Agent 461Agent 462Agent 463Agent 464Agent 465Agent 466Agent 467Agent 468Agent 469Agent 470Agent 471Agent 472Agent 473Agent 474Agent 475Agent 476Agent 477Agent 478Agent 479Agent 480Agent 481Agent 482Agent 483Agent 484Agent 485Agent 486Agent 487Agent 488Agent 489Agent 490Agent 491Agent 492Agent 493Agent 494Agent 495Agent 496Agent 497Agent 498Agent 499Agent 500Agent 501Agent 502Agent 503Agent 504Agent 505Agent 506Agent 507Agent 508Agent 509Agent 510Agent 511Agent 512Agent 513Agent 514Agent 515Agent 516Agent 517Agent 518Agent 519Agent 520Agent 521Agent 522Agent 523Agent 524Agent 525Agent 526Agent 527Agent 528Agent 529Agent 530Agent 531Agent 532Agent 533Agent 534Agent 535Agent 536Agent 537Agent 538Agent 539Agent 540Agent 541Agent 542Agent 543Agent 544Agent 545Agent 546Agent 547Agent 548Agent 549Agent 550Agent 551Agent 552Agent 553Agent 554Agent 555Agent 556Agent 557Agent 558Agent 559Agent 560Agent 561Agent 562Agent 563Agent 564Agent 565Agent 566Agent 567Agent 568Agent 569Agent 570Agent 571Agent 572Agent 573Agent 574Agent 575Agent 576Agent 577Agent 578Agent 579Agent 580Agent 581Agent 582Agent 583Agent 584Agent 585Agent 586Agent 587Agent 588Agent 589Agent 590Agent 591Agent 592Agent 593Agent 594Agent 595Agent 596Agent 597Agent 598Agent 599Agent 600Agent 601Agent 602Agent 603Agent 604Agent 605Agent 606Agent 607Agent 608Agent 609Agent 610Agent 611Agent 612Agent 613Agent 614Agent 615Agent 616Agent 617Agent 618Agent 619Agent 620Agent 621Agent 622Agent 623Agent 624Agent 625Agent 626Agent 627Agent 628Agent 629Agent 630Agent 631Agent 632Agent 633Agent 634Agent 635Agent 636Agent 637Agent 638Agent 639Agent 640Agent 641Agent 642Agent 643Agent 644Agent 645Agent 646Agent 647Agent 648Agent 649Agent 650Agent 651Agent 652Agent 653Agent 654Agent 655Agent 656Agent 657Agent 658Agent 659Agent 660Agent 661Agent 662Agent 663Agent 664Agent 665Agent 666Agent 667Agent 668Agent 669Agent 670Agent 671Agent 672Agent 673Agent 674Agent 675Agent 676Agent 677Agent 678Agent 679Agent 680Agent 681Agent 682Agent 683Agent 684Agent 685Agent 686Agent 687Agent 688Agent 689Agent 690Agent 691Agent 692Agent 693Agent 694Agent 695Agent 696Agent 697Agent 698Agent 699Agent 700Agent 701Agent 702Agent 703Agent 704Agent 705Agent 706Agent 707Agent 708Agent 709Agent 710Agent 711Agent 712Agent 713Agent 714Agent 715Agent 716Agent 717Agent 718Agent 719Agent 720Agent 721Agent 722Agent 723Agent 724Agent 725Agent 726Agent 727Agent 728Agent 729Agent 730Agent 731Agent 732Agent 733Agent 734Agent 735Agent 736Agent 737Agent 738Agent 739Agent 740Agent 741Agent 742Agent 743Agent 744Agent 745Agent 746Agent 747Agent 748Agent 749Agent 750Agent 751Agent 752Agent 753Agent 754Agent 755Agent 756Agent 757Agent 758Agent 759Agent 760Agent 761Agent 762Agent 763Agent 764Agent 765Agent 766Agent 767Agent 768Agent 769Agent 770Agent 771Agent 772Agent 773Agent 774Agent 775Agent 776Agent 777Agent 778Agent 779Agent 780Agent 781Agent 782Agent 783Agent 784Agent 785Agent 786Agent 787Agent 788Agent 789Agent 790Agent 791Agent 792Agent 793Agent 794Agent 795Agent 796Agent 797Agent 798Agent 799Agent 800Agent 801Agent 802Agent 803Agent 804Agent 805Agent 806Agent 807Agent 808Agent 809Agent 810Agent 811Agent 812Agent 813Agent 814Agent 815Agent 816Agent 817Agent 818Agent 819Agent 820Agent 821Agent 822Agent 823Agent 824Agent 825Agent 826Agent 827Agent 828Agent 829Agent 830Agent 831Agent 832Agent 833Agent 834Agent 835Agent 836Agent 837Agent 838Agent 839Agent 840Agent 841Agent 842Agent 843Agent 844Agent 845Agent 846Agent 847Agent 848Agent 849Agent 850Agent 851Agent 852Agent 853Agent 854Agent 855Agent 856Agent 857Agent 858Agent 859Agent 860Agent 861Agent 862Agent 863Agent 864Agent 865Agent 866Agent 867Agent 868Agent 869Agent 870Agent 871Agent 872Agent 873Agent 874Agent 875Agent 876Agent 877Agent 878Agent 879Agent 880Agent 881Agent 882Agent 883Agent 884Agent 885Agent 886Agent 887Agent 888Agent 889Agent 890Agent 891Agent 892Agent 893Agent 894Agent 895Agent 896Agent 897Agent 898Agent 899Agent 900Agent 901Agent 902Agent 903Agent 904Agent 905Agent 906Agent 907Agent 908Agent 909Agent 910Agent 911Agent 912Agent 913Agent 914Agent 915Agent 916Agent 917Agent 918Agent 919Agent 920Agent 921Agent 922Agent 923Agent 924Agent 925Agent 926Agent 927Agent 928Agent 929Agent 930Agent 931Agent 932Agent 933Agent 934Agent 935Agent 936Agent 937Agent 938Agent 939Agent 940Agent 941Agent 942Agent 943Agent 944Agent 945Agent 946Agent 947Agent 948Agent 949Agent 950Agent 951Agent 952Agent 953Agent 954Agent 955Agent 956Agent 957Agent 958Agent 959Agent 960Agent 961Agent 962Agent 963Agent 964Agent 965Agent 966Agent 967Agent 968Agent 969Agent 970Agent 971Agent 972Agent 973Agent 974Agent 975Agent 976Agent 977Agent 978Agent 979Agent 980Agent 981Agent 982Agent 983Agent 984Agent 985Agent 986Agent 987Agent 988Agent 989Agent 990Agent 991Agent 992Agent 993Agent 994Agent 995Agent 996Agent 997Agent 998Agent 999Agent 1000
0%100%

The full population over time

The chart starts at round 0 once data is available.

Band: 10th–90th percentile. Cyan: median. Amber: selected agent. Dashed: reference playing itself. Match points = (wins + ½ draws) / 16; 50% indicates parity on this suite.

Agent 1

Select a recorded run to inspect this agent.

What does “1,000 players learning together” mean?

There are 1,000 persistent agents, each with its own randomly initialized 7 → 8 → 3 tanh Q-network: 91 parameters per brain. Every matchmaking round shuffles the IDs and creates 500 peer matches. Each agent plays exactly once per round. There is no cloning of winners, replacement of weak agents, shared policy, or shared replay memory.

The worker computes these matches sequentially to keep resource use manageable. It is a league of random pairings, not a thousand paddles occupying one rink. Longer rallies give their participants more moves and updates, so equal match counts do not mean identical training exposure.

All agents use learning rate 0.02, exploration 20%, a 128-experience replay buffer, and two sampled updates per move. The game and reward shaping are the same as in the earlier hockey lesson. Compact networks and buffers let the full population run locally.

The algorithmic reference player

The reference predicts where the puck will reach its paddle, including reflections at the top and bottom walls. It obeys the same paddle speed and collision rules as the learners. On the final approach, it chooses among legal moves to return the puck toward a location far from the opponent’s current paddle.

T=⌈xpaddle−xpuckvx⌉,yhit=reflect⁡(ypuck+Tvy)T=\left\lceil\frac{x_{\rm paddle}-x_{\rm puck}}{v_x}\right\rceil,\qquad y_{\rm hit}=\operatorname{reflect}(y_{\rm puck}+Tv_y)

This is a strong predictive controller, not a proven optimal strategy. It has exact state information, just like the coordinate-based learners, but uses a hand-written physics model instead of a learned policy. The dashed chart line is its measured result against itself on the same benchmark suite.

Evaluation stays separate from training

At round 0 and every 10 rounds, each agent plays 16 deterministic benchmark rallies against the fixed reference, with both playing sides and serve directions balanced. Exploration and learning are off during evaluation. The benchmark does not alter the agent’s weights, replay buffer, training RNG, or the peer-match RNG.

Si=winsi+12drawsi16,Ri=successful returnsiincoming shotsiS_i=\frac{\text{wins}_i+\tfrac12\text{draws}_i}{16},\qquad R_i=\frac{\text{successful returns}_i}{\text{incoming shots}_i}

Match points measure success against the reference; return rate measures defensive skill. A learner can improve its defense while still rarely beating the reference. The fixed suite makes successive measurements comparable, but it is a small set of scenarios. It cannot establish universal skill or permanent stagnation.

The population band contains the 10th–90th percentiles at each checkpoint. It does not track the same agents at its edges: individuals can move through the ranking. Select an agent to follow a fixed identity.

How “flat recently” is determined

Compare each agent with itself two checkpoints earlier. A change of at least 10 percentage points in either return rate or match points is flagged as improvement or regression. If the metrics move strongly in opposite directions, the label is mixed. If neither crosses that threshold, the label is flat recently.

A flat agent may already be near the reference’s performance ceiling, may need more experience, or may be in a temporary plateau. Check its absolute score and training moves. These labels describe this 20-round window; they do not predict that an agent will never learn. Repeated seeds and longer runs are needed for stronger conclusions.

Saved data and reproducibility

“Load prerun data” fetches a recorded 200-round run with all 1,000 agents and 21 benchmark checkpoints, including the untrained population. The history player only reveals those saved measurements; it does not train or invent intermediate results.

The population file stores per-agent outcomes and training-move counts, not the weights of every agent at every checkpoint. Loaded population results are therefore for inspection. “Start fresh” creates a new live population. Earlier lessons’ loaders include final model weights so those trained players can be watched immediately; their replay memories restart if training is resumed.

The generation script is scripts/generate-population.ts, run with bun scripts/generate-population.ts 200 1000 public/preruns/hockey-population.json. It uses seed 42. The JSON includes the run configuration, generation time, and measured duration. Download the recorded results.

Reference controller, pairing, and evaluation: source
import { HockeyAgent, HockeyMatch, createMatchStats, PADDLE_HALF, PUCK_RADIUS, type Rink, type Side } from './pong-hockey';
import { randomSource } from './star-training';

export const POPULATION_SIZE = 1000;
export const BENCHMARK_RALLIES = 16;
export const POPULATION_CONFIG = { seed: 42, rate: .02, exploration: .2, width: 8, replay: 128, updates: 2, evaluateEvery: 10 };
const clampPaddle = (y: number) => Math.max(PADDLE_HALF, Math.min(1 - PADDLE_HALF, y));
export function reflectedHeight(y: number) {
  const length = 1 - 2 * PUCK_RADIUS, period = length * 2;
  const folded = ((y - PUCK_RADIUS) % period + period) % period;
  return PUCK_RADIUS + (folded <= length ? folded : period - folded);
}

// Fixed predictive controller. It obeys the same movement limits as learners.
export function referenceAction(rink: Rink, side: Side): number {
  const incoming = side === 0 ? rink.vx < 0 : rink.vx > 0;
  const paddle = rink.paddles[side];
  if (!incoming) {
    return [0, 1, 2].reduce((best, action) => Math.abs(clampPaddle(paddle + (action - 1) * .035) - .5) < Math.abs(clampPaddle(paddle + (best - 1) * .035) - .5) ? action : best, 1);
  }
  const line = side === 0 ? .065 : .935;
  const moves = Math.max(1, Math.ceil((line - rink.x) / rink.vx));
  const landing = reflectedHeight(rink.y + rink.vy * moves);
  let best = 1, bestScore = -Infinity;
  for (const action of [1, 0, 2]) {
    const next = clampPaddle(paddle + (action - 1) * .035);
    const error = Math.abs(next - landing);
    let score = -error;
    if (moves === 1 && error <= PADDLE_HALF + PUCK_RADIUS) {
      const velocity = (landing - next) / (PADDLE_HALF + PUCK_RADIUS) * .05 + (action - 1) * .005;
      const flight = Math.ceil(.87 / Math.min(.055, Math.abs(rink.vx) * 1.07));
      const destination = reflectedHeight(landing + velocity * flight);
      score = 100 + Math.abs(destination - rink.paddles[1 - side]);
    }
    if (score > bestScore) { bestScore = score; best = action; }
  }
  return best;
}

export type Benchmark = { wins: number; draws: number; losses: number; returns: number; shots: number };
export type PopulationCheckpoint = { round: number; agents: Benchmark[]; moves: number[] };
export type PopulationData = { version: 1; kind: 'hockey-population'; size: number; config: typeof POPULATION_CONFIG; benchmarkRallies: number; reference: Benchmark; checkpoints: PopulationCheckpoint[]; generatedAt?: string; durationSeconds?: number };
export const defense = (result: Benchmark) => result.shots ? result.returns / result.shots : 0;
export const points = (result: Benchmark) => (result.wins + .5 * result.draws) / (result.wins + result.draws + result.losses || 1);
const greedy = (agent: HockeyAgent, input: number[]) => { const q = agent.network.forward(input).values; return q.indexOf(Math.max(...q)); };

export function benchmark(agent: HockeyAgent | null, arena: HockeyMatch, count = BENCHMARK_RALLIES): Benchmark {
  const result: Benchmark = { wins: 0, draws: 0, losses: 0, returns: 0, shots: 0 };
  // The same held-out serves and balanced side/serve combinations at every check.
  arena.random = randomSource(90421); arena.serves = 0;
  for (let trial = 0; trial < count; trial++) {
    arena.newRally(); const score = createMatchStats();
    const side = (Math.floor(trial / 2) % 2) as Side;
    while (!arena.terminal) {
      const actions: [number, number] = [referenceAction(arena.rink, 0), referenceAction(arena.rink, 1)];
      if (agent) actions[side] = greedy(agent, arena.observation(side));
      arena.advance(actions, score);
    }
    result.returns += score.returns[side]; result.shots += score.shots[side];
    if (arena.winner === null) result.draws++;
    else if (arena.winner === side) result.wins++;
    else result.losses++;
  }
  return result;
}

export class HockeyPopulation {
  agents: HockeyAgent[];
  random: () => number;
  arena = new HockeyMatch();
  evaluator = new HockeyMatch();
  order: number[];
  pair = 0;
  round = 0;
  pairStarted = false;
  activeIds = [0, 1];
  pairScore = createMatchStats();
  data: PopulationData;
  evaluating: PopulationCheckpoint | null = null;
  evaluationIndex = 0;
  constructor(public size = POPULATION_SIZE, seed = 42) {
    if (size < 2 || size % 2) throw new Error('Population must have an even number of agents.');
    this.random = randomSource(seed + 7001);
    this.arena.random = randomSource(seed + 1231);
    this.agents = Array.from({ length: size }, (_, id) => new HockeyAgent('state', seed + 1009 * (id + 1), 1, { memoryLimit: 128, replaySamples: 2, width: 8 }));
    this.order = Array.from({ length: size }, (_, id) => id);
    this.data = { version: 1, kind: 'hockey-population', size, config: { ...POPULATION_CONFIG, seed }, benchmarkRallies: BENCHMARK_RALLIES, reference: benchmark(null, this.evaluator), checkpoints: [] };
    this.shuffle(); this.beginEvaluation();
  }
  shuffle() {
    for (let i = this.order.length - 1; i > 0; i--) { const j = Math.floor(this.random() * (i + 1)); [this.order[i], this.order[j]] = [this.order[j], this.order[i]]; }
  }
  beginEvaluation() {
    this.evaluating = { round: this.round, agents: [], moves: this.agents.map(agent => agent.steps) }; this.evaluationIndex = 0;
  }
  // One small work unit: one benchmark agent or one simultaneous peer-game move.
  // A worker schedules these units and yields between batches.
  work(): boolean {
    if (this.evaluating) {
      this.evaluating.agents.push(benchmark(this.agents[this.evaluationIndex++], this.evaluator));
      if (this.evaluationIndex === this.size) {
        this.data.checkpoints.push(this.evaluating); this.evaluating = null; return true;
      }
      return false;
    }
    if (!this.pairStarted) { this.arena.newRally(); this.pairScore = createMatchStats(); this.pairStarted = true; this.activeIds = this.order.slice(this.pair * 2, this.pair * 2 + 2); }
    const ids = [this.order[this.pair * 2], this.order[this.pair * 2 + 1]];
    const states = [this.arena.observation(0), this.arena.observation(1)];
    const actions = ids.map((id, side) => this.agents[id].choose(states[side], this.data.config.exploration)) as [number, number];
    this.arena.advance(actions, this.pairScore);
    ids.forEach((id, side) => this.agents[id].learn({ state: states[side], action: actions[side], reward: this.arena.rewards[side], next: this.arena.observation(side as Side), done: this.arena.terminal }, this.data.config.rate));
    if (this.arena.terminal) {
      this.pairStarted = false; this.pair++;
      if (this.pair === this.size / 2) {
        this.pair = 0; this.round++; this.shuffle();
        if (this.round % this.data.config.evaluateEvery === 0) this.beginEvaluation();
      }
    }
    return false;
  }
}

export type Trend = 'early' | 'improving' | 'flat' | 'regressing' | 'mixed';
export function agentTrend(checkpoints: PopulationCheckpoint[], index: number, id: number): Trend {
  if (index < 2) return 'early';
  const before = checkpoints[index - 2].agents[id], after = checkpoints[index].agents[id];
  const changes = [defense(after) - defense(before), points(after) - points(before)];
  const up = changes.some(value => value >= .1), down = changes.some(value => value <= -.1);
  return up && down ? 'mixed' : up ? 'improving' : down ? 'regressing' : 'flat';
}
Background: independent learners and fixed benchmarks

Each agent here is an independent Q-learner: the other players are simply part of its environment. That is the simplest way to extend deep Q-learning to several agents, and theoretical guarantees for such multiagent learning are scarce (Tampuu et al., 2017). The classic convergence proof for Q-learning assumes a Markovian environment and one stored value per state and action (Watkins & Dayan, 1992). Neither holds here: each opponent keeps changing as it learns, and values come from a 91-parameter network. Plateaus and regressions are therefore possible outcomes, not necessarily bugs.

Tampuu and colleagues compared Pong agents trained against another learning agent with agents trained against a fixed, hand-written opponent, and found that the latter generalized poorly to new opponents (Tampuu et al., 2017). This experiment uses the fixed controller differently: agents train only against peers, and the reference is used only for measurement. Because no agent trains against the reference, the results cannot say whether peer training generalizes better.

The Pong hockey history covers earlier programs that learned from games against opponents, from Samuel’s checkers player to TD-Gammon.

References and further reading

References

  1. Ardi Tampuu, Tambet Matiisen, Dorian Kodelja, Ilya Kuzovkin, Kristjan Korjus, Juhan Aru, Jaan Aru & Raul Vicente (2017). Multiagent cooperation and competition with deep reinforcement learning. PLoS ONE 12(4), e0172395.Open access · Read: Full text read: abstract, introduction, materials and methods, and the opening of the results · Checked 2026-09-13
  2. Christopher J. C. H. Watkins & Peter Dayan (1992). Q-learning. Machine Learning 8(3–4), 279–292.Publisher record; full text may require access · open copy · Read: Author copy: abstract, introduction and statement of the convergence conditions · Checked 2026-09-13

Further reading

  • Richard S. Sutton & Andrew G. Barto (2018). Reinforcement Learning: An Introduction. MIT Press, 2nd ed.The standard textbook: Q-learning in chapter 6, instability with function approximation in chapter 11, and TD-Gammon and DQN in chapter 16.Library record · Checked 2026-09-13