Below, three players form a team. Set how much each possible coalition is worth — the value of the empty group, of each single player, of each pair, and of all three together. Then the calculator does what Shapley proposed: it imagines the players arriving in every possible order and, each time, credits a player with exactly the extra value they bring when they walk in.
<p class="hint">Set what each <b>coalition</b> is worth. The calculator imagines the three players arriving in every possible order and credits each one with the <b>extra value</b> they add on entry. Averaging over all 6 orders gives the fair Shapley share — and the shares always sum to the value of the full team.</p>
<div class="grid">
<label>v(A) <input id="vA" type="number" value="100"></label>
<label>v(B) <input id="vB" type="number" value="0"></label>
<label>v(C) <input id="vC" type="number" value="0"></label>
<label>v(A,B) <input id="vAB" type="number" value="400"></label>
<label>v(A,C) <input id="vAC" type="number" value="300"></label>
<label>v(B,C) <input id="vBC" type="number" value="200"></label>
<label class="wide">v(A,B,C) <input id="vABC" type="number" value="1000"></label>
</div>
<div class="btns">
<button id="run" type="button">Compute Shapley value</button>
<button id="equal" type="button" class="ghost">Make C a null player</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div id="orders" class="orders"></div>
<div id="result" class="result"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem .7rem; margin-bottom: .8rem; }
label { font: 600 13px ui-monospace, monospace; color: #1d3557; display: flex; flex-direction: column; gap: .2rem; }
label.wide { grid-column: 1 / -1; }
input { font: 600 14px ui-monospace, monospace; padding: .35rem .5rem; border: 1px solid #cdd9e3;
border-radius: 7px; background: #f4f7fa; }
.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; }
.orders { font: 12px ui-monospace, monospace; color: #333; background: #f4f7fa;
border: 1px solid #e0e8ef; border-radius: 8px; padding: .5rem .7rem; margin-bottom: .7rem;
white-space: pre; overflow-x: auto; min-height: 1.2em; }
.result { display: flex; gap: .5rem; flex-wrap: wrap; }
.share { flex: 1; min-width: 90px; text-align: center; padding: .6rem; border-radius: 10px;
background: #e8eef3; border: 1px solid #cdd9e3; }
.share .who { font: 700 14px system-ui; color: #1d3557; }
.share .amt { font: 800 22px ui-monospace, monospace; color: #0a7d33; margin-top: .2rem; }
.sum { width: 100%; font-size: .9rem; font-weight: 600; margin-top: .4rem; }
.sum.ok { color: #0a7d33; } .sum.bad { color: #c92f3c; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// The Shapley value: each player's fair share of a cooperative game's payoff.
// For every order in which the players could arrive, a player is credited with
// the MARGINAL value they add: v(set with them) - v(set without them). Averaging
// that over all n! orders gives the unique fair allocation (Shapley, 1953).
const PLAYERS = ['A', 'B', 'C'];
const IDS = { '': 'empty', 'A': 'vA', 'B': 'vB', 'C': 'vC',
'AB': 'vAB', 'AC': 'vAC', 'BC': 'vBC', 'ABC': 'vABC' };
const key = set => PLAYERS.filter(p => set.has(p)).join(''); // canonical order A<B<C
function v(set){ const k = key(set); if (k === '') return 0;
return Number(document.getElementById(IDS[k]).value) || 0; }
function permutations(arr){ if (arr.length <= 1) return [arr];
const out = []; arr.forEach((x, i) => {
const rest = arr.slice(0, i).concat(arr.slice(i + 1));
permutations(rest).forEach(p => out.push([x, ...p])); });
return out; }
function shapley(){
const orders = permutations(PLAYERS);
const contrib = { A: 0, B: 0, C: 0 };
const lines = [];
for (const order of orders){
const before = new Set();
const parts = [];
for (const p of order){
const withoutP = v(before);
before.add(p);
const withP = v(before);
const marginal = withP - withoutP; // value player p adds joining here
contrib[p] += marginal;
parts.push(p + ':+' + marginal);
}
lines.push(order.join('→') + ' ' + parts.join(' '));
}
const n = orders.length; // 3! = 6
const phi = { A: contrib.A / n, B: contrib.B / n, C: contrib.C / n };
return { phi, lines, total: v(new Set(PLAYERS)) };
}
function fmt(x){ return (Math.round(x * 100) / 100).toString(); }
function run(){
const { phi, lines, total } = shapley();
document.getElementById('orders').textContent = lines.join('\n');
const sum = phi.A + phi.B + phi.C;
const r = document.getElementById('result');
r.innerHTML = i18nFmt('%map%<div class="sum %v%">Shares sum to %fmt% = value of full team (%fmt2%). Efficiency holds.</div>', { map: PLAYERS.map(p =>
'<div class="share"><div class="who">Player ' + p + '</div><div class="amt">' + fmt(phi[p]) + '</div></div>'
).join(''), v: Math.abs(sum - total) < 1e-9 ? 'ok' : 'bad', fmt: fmt(sum), fmt2: fmt(total) });
console.log('Shapley values:', phi, '| sum =', sum, '| total =', total);
}
function makeNull(){
// Make C a null player: adding C never changes any coalition's value.
document.getElementById('vC').value = 0;
document.getElementById('vAC').value = document.getElementById('vA').value;
document.getElementById('vBC').value = document.getElementById('vB').value;
document.getElementById('vABC').value = document.getElementById('vAB').value;
run();
}
function reset(){
const d = { vA:100, vB:0, vC:0, vAB:400, vAC:300, vBC:200, vABC:1000 };
for (const id in d) document.getElementById(id).value = d[id];
document.getElementById('orders').textContent = ''; run();
}
document.getElementById('run').addEventListener('click', run);
document.getElementById('equal').addEventListener('click', makeNull);
document.getElementById('reset').addEventListener('click', reset);
run();
<p class="hint">Set what each <b>coalition</b> is worth. The calculator imagines the three players arriving in every possible order and credits each one with the <b>extra value</b> they add on entry. Averaging over all 6 orders gives the fair Shapley share — and the shares always sum to the value of the full team.</p>
<div class="grid">
<label>v(A) <input id="vA" type="number" value="100"></label>
<label>v(B) <input id="vB" type="number" value="0"></label>
<label>v(C) <input id="vC" type="number" value="0"></label>
<label>v(A,B) <input id="vAB" type="number" value="400"></label>
<label>v(A,C) <input id="vAC" type="number" value="300"></label>
<label>v(B,C) <input id="vBC" type="number" value="200"></label>
<label class="wide">v(A,B,C) <input id="vABC" type="number" value="1000"></label>
</div>
<div class="btns">
<button id="run" type="button">Compute Shapley value</button>
<button id="equal" type="button" class="ghost">Make C a null player</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div id="orders" class="orders"></div>
<div id="result" class="result"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem .7rem; margin-bottom: .8rem; }
label { font: 600 13px ui-monospace, monospace; color: #1d3557; display: flex; flex-direction: column; gap: .2rem; }
label.wide { grid-column: 1 / -1; }
input { font: 600 14px ui-monospace, monospace; padding: .35rem .5rem; border: 1px solid #cdd9e3;
border-radius: 7px; background: #f4f7fa; }
.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; }
.orders { font: 12px ui-monospace, monospace; color: #333; background: #f4f7fa;
border: 1px solid #e0e8ef; border-radius: 8px; padding: .5rem .7rem; margin-bottom: .7rem;
white-space: pre; overflow-x: auto; min-height: 1.2em; }
.result { display: flex; gap: .5rem; flex-wrap: wrap; }
.share { flex: 1; min-width: 90px; text-align: center; padding: .6rem; border-radius: 10px;
background: #e8eef3; border: 1px solid #cdd9e3; }
.share .who { font: 700 14px system-ui; color: #1d3557; }
.share .amt { font: 800 22px ui-monospace, monospace; color: #0a7d33; margin-top: .2rem; }
.sum { width: 100%; font-size: .9rem; font-weight: 600; margin-top: .4rem; }
.sum.ok { color: #0a7d33; } .sum.bad { color: #c92f3c; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// The Shapley value: each player's fair share of a cooperative game's payoff.
// For every order in which the players could arrive, a player is credited with
// the MARGINAL value they add: v(set with them) - v(set without them). Averaging
// that over all n! orders gives the unique fair allocation (Shapley, 1953).
const PLAYERS = ['A', 'B', 'C'];
const IDS = { '': 'empty', 'A': 'vA', 'B': 'vB', 'C': 'vC',
'AB': 'vAB', 'AC': 'vAC', 'BC': 'vBC', 'ABC': 'vABC' };
const key = set => PLAYERS.filter(p => set.has(p)).join(''); // canonical order A<B<C
function v(set){ const k = key(set); if (k === '') return 0;
return Number(document.getElementById(IDS[k]).value) || 0; }
function permutations(arr){ if (arr.length <= 1) return [arr];
const out = []; arr.forEach((x, i) => {
const rest = arr.slice(0, i).concat(arr.slice(i + 1));
permutations(rest).forEach(p => out.push([x, ...p])); });
return out; }
function shapley(){
const orders = permutations(PLAYERS);
const contrib = { A: 0, B: 0, C: 0 };
const lines = [];
for (const order of orders){
const before = new Set();
const parts = [];
for (const p of order){
const withoutP = v(before);
before.add(p);
const withP = v(before);
const marginal = withP - withoutP; // value player p adds joining here
contrib[p] += marginal;
parts.push(p + ':+' + marginal);
}
lines.push(order.join('→') + ' ' + parts.join(' '));
}
const n = orders.length; // 3! = 6
const phi = { A: contrib.A / n, B: contrib.B / n, C: contrib.C / n };
return { phi, lines, total: v(new Set(PLAYERS)) };
}
function fmt(x){ return (Math.round(x * 100) / 100).toString(); }
function run(){
const { phi, lines, total } = shapley();
document.getElementById('orders').textContent = lines.join('\n');
const sum = phi.A + phi.B + phi.C;
const r = document.getElementById('result');
r.innerHTML = i18nFmt('%map%<div class="sum %v%">Shares sum to %fmt% = value of full team (%fmt2%). Efficiency holds.</div>', { map: PLAYERS.map(p =>
'<div class="share"><div class="who">Player ' + p + '</div><div class="amt">' + fmt(phi[p]) + '</div></div>'
).join(''), v: Math.abs(sum - total) < 1e-9 ? 'ok' : 'bad', fmt: fmt(sum), fmt2: fmt(total) });
console.log('Shapley values:', phi, '| sum =', sum, '| total =', total);
}
function makeNull(){
// Make C a null player: adding C never changes any coalition's value.
document.getElementById('vC').value = 0;
document.getElementById('vAC').value = document.getElementById('vA').value;
document.getElementById('vBC').value = document.getElementById('vB').value;
document.getElementById('vABC').value = document.getElementById('vAB').value;
run();
}
function reset(){
const d = { vA:100, vB:0, vC:0, vAB:400, vAC:300, vBC:200, vABC:1000 };
for (const id in d) document.getElementById(id).value = d[id];
document.getElementById('orders').textContent = ''; run();
}
document.getElementById('run').addEventListener('click', run);
document.getElementById('equal').addEventListener('click', makeNull);
document.getElementById('reset').addEventListener('click', reset);
run();
Average that marginal contribution over all six arrival orders and you get each player's fair share. Notice two things: the shares always add up to exactly the total the full team is worth (nothing is lost or invented), and a player who never changes any coalition's value gets zero. That is fairness made mechanical — and it is the only split that obeys those rules at once.
Comments
Loading comments...