Introduction

Imagine two commanders, each with a fixed army of 100 soldiers. Ahead lies a row of five battlefields, each worth one victory point. Simultaneously — without seeing the enemy's plan — each commander decides how many troops to send to each battlefield. The battlefield goes to whoever sends more troops there; ties are shared. The commander who wins more battlefields wins the war.

The rules fit on a cocktail napkin. Yet Colonel Blotto, as this game is known, has fascinated game theorists, economists, and military strategists since Borel formulated it in 1921. The reason: no pure strategy is safe. Whatever fixed allocation you announce, I can exploit it — send overwhelming force to the battlefields you left thin, and let you dominate the ones you flooded. The only rational play is to randomize: choose your allocation from a carefully designed probability distribution so your opponent can never predict where you will be strong.

Finding that optimal random strategy — the Nash equilibrium of the game — turns out to be surprisingly hard to characterize when the number of battlefields grows. For two battlefields the answer is trivial; for three it took decades of careful work; for many battlefields the full picture is still being studied today.

Try It: Face an Optimal Blotto Strategy

You have 100 troops and 5 battlefields. Fill in how many troops to send to each battlefield (they must sum to 100). The computer opponent uses a known near-optimal mixed strategy — it samples a random allocation from a distribution that no fixed human plan can reliably beat.

<p class="hint">You have <b>100 troops</b> and 5 battlefields (each worth 1 point). Enter your allocation below — numbers must sum to exactly 100. The computer plays a <b>near-optimal mixed strategy</b>: it samples from the Roberson (2006) equilibrium distribution where every battlefield’s marginal is Uniform[0, 40].</p>
<div id="inputs-area">
  <div class="field-row" id="fields"></div>
  <div class="sum-display">Troops assigned: <span id="sum-display">0</span> / 100</div>
</div>
<div class="btns">
  <button id="fight" type="button">Fight!</button>
  <button id="run100" type="button">Run 100 rounds</button>
  <button id="reset-btn" type="button" class="ghost">Reset</button>
</div>
<div class="status" id="status"></div>
<div id="result-area" class="hidden">
  <table id="battle-table">
    <thead><tr><th>Battlefield</th><th>You</th><th>Computer</th><th>Winner</th></tr></thead>
    <tbody id="battle-body"></tbody>
    <tfoot><tr><th>Battlefields won</th><th id="you-total">—</th><th id="cpu-total">—</th><th id="outcome">—</th></tr></tfoot>
  </table>
</div>
<div id="stats-area" class="hidden">
  <div class="stats-row">
    <span>Your wins: <b id="stat-wins">0</b></span>
    <span>Computer wins: <b id="stat-losses">0</b></span>
    <span>Ties: <b id="stat-ties">0</b></span>
  </div>
  <div class="bar-wrap"><div class="bar-fill" id="win-bar"></div></div>
  <div class="bar-label" id="bar-label"></div>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.field-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: .4rem; }
.field-box { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.field-box label { font-size: .75rem; font-weight: 600; color: #1d3557; }
.field-box input { width: 58px; height: 40px; border: 1.5px solid #adb1b8; border-radius: 8px;
                   font: 700 16px ui-monospace, monospace; text-align: center; color: #1d3557; }
.field-box input:focus { outline: none; border-color: #1d3557; }
.sum-display { font-size: .85rem; color: #555; margin-bottom: .6rem; }
.sum-display.over { color: #c92f3c; font-weight: 700; }
.sum-display.exact { color: #0a7d33; font-weight: 700; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
         background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
.status { font-size: 1rem; font-weight: 600; min-height: 1.4em; margin-bottom: .5rem; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.tie { color: #b56a00; }
.hidden { display: none; }
table { border-collapse: collapse; width: 100%; font-size: .88rem; margin-top: .4rem; }
th, td { padding: .35rem .55rem; border: 1px solid #d0d7e0; text-align: center; }
thead th { background: #e8eef3; color: #1d3557; font-weight: 700; }
tfoot th { background: #e8eef3; font-weight: 700; }
.win-you { background: #d4edda; }
.win-cpu { background: #fde8e8; }
.tie-row { background: #fef9e7; }
.stats-row { display: flex; gap: 1.2rem; flex-wrap: wrap; font-size: .88rem; margin: .6rem 0 .3rem; }
.bar-wrap { height: 10px; background: #e0e6ec; border-radius: 5px; overflow: hidden; margin-bottom: .25rem; }
.bar-fill { height: 100%; background: #2a7f4f; border-radius: 5px; transition: width .4s; }
.bar-label { font-size: .78rem; color: #555; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Colonel Blotto demo — 5 battlefields, 100 troops each.
//
// CPU strategy: Roberson (2006) equilibrium for symmetric Blotto with n=5, T=100.
// Reference: Roberson, B. (2006). The Colonel Blotto game. Economic Theory 29(1), 1-24.
//
// In the equilibrium each battlefield’s marginal distribution is Uniform[0, 2T/n] = U[0,40].
// We sample using rejection: draw a,b,c,d ~ U[0,40] independently, set e = 100-a-b-c-d,
// accept if 0 <= e <= 40 (acceptance rate ~60%). Then shuffle all 5 positions.
// This yields exactly U[0,40] marginals and correct independence structure.
//
// Consequence: any FIXED human allocation wins around 45-50% at best.
// Concentrated allocations (e.g. 100-0-0-0-0) lose almost every round.

const N = 5;
const T = 100;
const MAX_PER = (2 * T / N) | 0; // 40
const inputs = [];
let stats = { wins: 0, losses: 0, ties: 0 };

// Build input fields
const fieldsDiv = document.getElementById('fields');
for (let i = 0; i < N; i++) {
  const box = document.createElement('div');
  box.className = 'field-box';
  const lbl = document.createElement('label');
  lbl.textContent = fmt('BF %i%%v%', { i: i, v: 1 });
  const inp = document.createElement('input');
  inp.type = 'number';
  inp.min = '0';
  inp.max = '100';
  inp.value = '20';
  box.appendChild(lbl);
  box.appendChild(inp);
  fieldsDiv.appendChild(box);
  inputs.push(inp);
  inp.addEventListener('input', updateSum);
}

function getPlayerAlloc() {
  return inputs.map(inp => Math.max(0, parseInt(inp.value) || 0));
}

function updateSum() {
  const s = getPlayerAlloc().reduce((a, b) => a + b, 0);
  const disp = document.getElementById('sum-display');
  disp.textContent = s;
  disp.parentElement.className = 'sum-display' + (s > T ? ' over' : s === T ? ' exact' : '');
}

// Sample CPU allocation from the Roberson equilibrium (rejection sampling)
function cpuAlloc() {
  let raw;
  do {
    const a = Math.random() * MAX_PER;
    const b = Math.random() * MAX_PER;
    const c = Math.random() * MAX_PER;
    const d = Math.random() * MAX_PER;
    const e = T - a - b - c - d;
    if (e >= 0 && e <= MAX_PER) raw = [a, b, c, d, e];
  } while (!raw);
  // Shuffle
  for (let i = N - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [raw[i], raw[j]] = [raw[j], raw[i]];
  }
  // Round to integers summing to T
  let alloc = raw.map(v => Math.round(v));
  let diff = T - alloc.reduce((a, b) => a + b, 0);
  if (diff > 0) { const idx = alloc.indexOf(Math.min(...alloc)); alloc[idx] += diff; }
  else if (diff < 0) { const idx = alloc.indexOf(Math.max(...alloc)); alloc[idx] += diff; }
  return alloc;
}

function battle(player, cpu) {
  return player.map((p, i) => {
    const c = cpu[i];
    return { player: p, cpu: c, winner: p > c ? 1 : c > p ? -1 : 0 };
  });
}

function showResult(results) {
  const area = document.getElementById('result-area');
  area.classList.remove('hidden');
  const tbody = document.getElementById('battle-body');
  tbody.innerHTML = '';
  let yW = 0, cW = 0;
  results.forEach((r, i) => {
    const tr = document.createElement('tr');
    tr.className = r.winner === 1 ? 'win-you' : r.winner === -1 ? 'win-cpu' : 'tie-row';
    const winLabel = r.winner === 1 ? 'You' : r.winner === -1 ? 'CPU' : 'Tie';
    tr.innerHTML = fmt('<td><b>BF %i%%v%</b></td><td>%player%</td><td>%cpu%</td><td>%winLabel%</td>', { i: i, v: 1, player: r.player, cpu: r.cpu, winLabel: winLabel });
    tbody.appendChild(tr);
    if (r.winner === 1) yW++;
    else if (r.winner === -1) cW++;
  });
  document.getElementById('you-total').textContent = yW;
  document.getElementById('cpu-total').textContent = cW;
  const outcomeEl = document.getElementById('outcome');
  if (yW > cW) { outcomeEl.textContent = 'You win!'; setStatus(fmt('You win %yW%-%cW%!', { yW: yW, cW: cW }), 'ok'); stats.wins++; }
  else if (cW > yW) { outcomeEl.textContent = 'CPU wins'; setStatus(fmt('CPU wins %cW%-%yW%. No fixed plan beats the equilibrium!', { cW: cW, yW: yW }), 'bad'); stats.losses++; }
  else { outcomeEl.textContent = 'Tie'; setStatus(fmt('It’s a tie %yW%-%cW%!', { yW: yW, cW: cW }), 'tie'); stats.ties++; }
}

function setStatus(t, cls) {
  const s = document.getElementById('status');
  s.textContent = t; s.className = 'status ' + (cls || '');
}

function updateStatsUI() {
  const total = stats.wins + stats.losses + stats.ties;
  if (total === 0) return;
  document.getElementById('stats-area').classList.remove('hidden');
  document.getElementById('stat-wins').textContent = stats.wins;
  document.getElementById('stat-losses').textContent = stats.losses;
  document.getElementById('stat-ties').textContent = stats.ties;
  const pct = Math.round(stats.wins / total * 100);
  document.getElementById('win-bar').style.width = pct + '%';
  document.getElementById('bar-label').textContent =
    fmt('Your win rate: %pct%% over %total% rounds — equilibrium prediction: ~45% for any fixed allocation', { pct: pct, total: total });
}

document.getElementById('fight').addEventListener('click', () => {
  const player = getPlayerAlloc();
  const s = player.reduce((a, b) => a + b, 0);
  if (s !== T) { setStatus(fmt('Troops must sum to exactly 100 (currently %s%).', { s: s }), 'bad'); return; }
  const cpu = cpuAlloc();
  showResult(battle(player, cpu));
  updateStatsUI();
});

document.getElementById('run100').addEventListener('click', () => {
  const player = getPlayerAlloc();
  const s = player.reduce((a, b) => a + b, 0);
  if (s !== T) { setStatus(fmt('Troops must sum to exactly 100 (currently %s%).', { s: s }), 'bad'); return; }
  for (let i = 0; i < 100; i++) {
    const results = battle(player, cpuAlloc());
    const yW = results.filter(r => r.winner === 1).length;
    const cW = results.filter(r => r.winner === -1).length;
    if (yW > cW) stats.wins++;
    else if (cW > yW) stats.losses++;
    else stats.ties++;
  }
  document.getElementById('result-area').classList.add('hidden');
  setStatus('Simulated 100 rounds against the Roberson equilibrium strategy.', 'tie');
  updateStatsUI();
});

document.getElementById('reset-btn').addEventListener('click', () => {
  inputs.forEach(inp => { inp.value = '20'; });
  stats = { wins: 0, losses: 0, ties: 0 };
  document.getElementById('result-area').classList.add('hidden');
  document.getElementById('stats-area').classList.add('hidden');
  setStatus('', '');
  updateSum();
});

updateSum();
Result
Console

Notice that even a seemingly "smart" allocation (e.g. 30-25-20-15-10) often loses. The computer's randomized strategy is designed so that any fixed plan you submit is beaten on average. The only way to compete is to randomize yourself — and even then the equilibrium payoffs depend on the exact troop counts and battlefield values in ways that took decades to characterize.

The Real Complexity

Colonel Blotto looks like a toy, but its mathematics runs surprisingly deep.

  • Pure strategies always lose. Any fixed allocation can be beaten: concentrate on the thin battlefields, concede the flooded ones. This forces both players into mixed strategies — probability distributions over allocations.
  • The symmetric case was solved by Roberson in 2006. When both players have the same total troops and all battlefields are worth the same, Brian Roberson fully characterized the Nash equilibrium: each battlefield's marginal distribution must be uniform on a certain interval, and the joint distribution must use a clever geometric construction. This was a century after Borel posed the game.
  • Asymmetric and weighted cases are still being studied. When one player has more troops, or some battlefields are worth more than others, or players can choose how many battlefields to contest, the equilibrium structure changes. Recent papers by Kovenock, Roberson, and others push the frontier further, but a complete theory does not yet exist.
  • Computational complexity. Computing best-response strategies in large discrete Blotto games is non-trivial; finding approximate equilibria is an active topic in algorithmic game theory.
  • Connection to Nash equilibrium. The game is a zero-sum two-player game, so a Nash equilibrium always exists (by the minimax theorem). The difficulty is not existence but explicit characterization and computation.

The status: partially solved — the symmetric equal-value case is settled (Roberson, 2006); the general case remains an active open area of research.

Where It Matters

"Divide your limited resources across multiple arenas simultaneously, without knowing your opponent's plan" is one of the most common strategic shapes in the real world:

  • Election campaigns: candidates allocate advertising budgets and campaign stops across swing states. Spending too much in a safe state wastes resources; too little in a contested one concedes it. Formal Blotto models have been used to study electoral competition.
  • Cybersecurity: a defender allocates limited monitoring and patch capacity across network nodes; an attacker chooses which nodes to probe. Each side randomizes to stay unpredictable — a direct Blotto structure.
  • Advertising auctions: competing firms bid across multiple ad slots or markets simultaneously, each trying to win the most valuable impressions without over-spending on any single one.
  • Military logistics and counter-terrorism: allocating patrol routes, checkpoint density, or supply across theaters while the adversary probes for weak points.
  • Sports drafts and fantasy leagues: distributing a salary cap across player positions so that no opponent can predict your vulnerabilities.

Understand Colonel Blotto and you've internalized the logic of simultaneous competitive resource allocation — a model that sits alongside Nash equilibrium and linear programming as a core tool for strategic thinking under conflict.

Conclusion

Colonel Blotto is a reminder that "simple rules" does not mean "easy strategy." Two players, fixed troops, win-more-battlefields — and yet the optimal play requires a carefully engineered randomized distribution that took a century to fully characterize even in the simplest symmetric case.

The next time you see a campaign allocating ad spend across swing states, or a security team spreading patches across servers, or a fantasy manager filling a roster within a salary cap — you are watching Colonel Blotto played in the real world. The math is still catching up.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/colonel-blotto/Content licensed under CC BY-NC 4.0.