One round of the dilemma is bleak. But what if you play again and again with the same opponent, remembering what they did? Pick a strategy and a rival below, run the rounds, and watch the scores stack up. Lower total years served is better.
<p class="hint">Pick a strategy for <b>You</b> and an <b>Opponent</b>, then run the rounds.
Each round both choose Cooperate (stay silent) or Defect (betray). Years served are
added up — <b>fewer total years is better</b>. One round rewards betrayal; many rounds
can reward trust.</p>
<div class="picks">
<label>You
<select id="me">
<option value="titfortat">Tit-for-tat</option>
<option value="alld">Always defect</option>
<option value="allc">Always cooperate</option>
<option value="grudger">Grudger</option>
</select>
</label>
<label>Opponent
<select id="opp">
<option value="alld">Always defect</option>
<option value="titfortat">Tit-for-tat</option>
<option value="allc">Always cooperate</option>
<option value="grudger">Grudger</option>
</select>
</label>
<label>Rounds <input id="rounds" type="number" min="1" max="30" value="10"></label>
</div>
<div class="btns">
<button id="run" type="button">Run tournament</button>
<button id="reset" type="button" class="ghost">Clear</button>
</div>
<div class="scores">
<div class="score"><span class="lbl">You</span><span id="syou">0</span><span class="u">yrs</span></div>
<div class="score"><span class="lbl">Opponent</span><span id="sopp">0</span><span class="u">yrs</span></div>
</div>
<div id="log" class="log"></div>
<div class="status" id="status">Choose players and run.</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 .7rem; line-height: 1.45; }
.picks { display: flex; gap: .8rem; flex-wrap: wrap; margin: .4rem 0 .7rem; }
label { font: 600 13px system-ui, sans-serif; color: #1d3557; display: flex; flex-direction: column; gap: .25rem; }
select, input { font: 500 14px system-ui, sans-serif; padding: .35rem .5rem; border: 1px solid #cdd9e3;
border-radius: 7px; background: #fff; }
input { width: 80px; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
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; }
.scores { display: flex; gap: .8rem; margin: .2rem 0 .6rem; }
.score { flex: 1; background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .5rem .7rem; display: flex; align-items: baseline; gap: .4rem; }
.score .lbl { font: 600 12px system-ui; color: #1d3557; text-transform: uppercase; letter-spacing: .03em; }
.score span:nth-child(2) { font: 800 22px ui-monospace, monospace; color: #c92f3c; margin-left: auto; }
.score .u { font: 500 12px system-ui; color: #687; }
.log { display: flex; flex-direction: column; gap: 3px; margin: .3rem 0; max-height: 200px; overflow: auto; }
.row { display: flex; align-items: center; gap: 6px; font: 600 13px ui-monospace, monospace; }
.row .n { color: #888; width: 26px; }
.tag { padding: 1px 7px; border-radius: 6px; min-width: 30px; text-align: center; }
.C { background: #d6f0df; color: #0a7d33; }
.D { background: #fbd9dd; color: #c92f3c; }
.row .pts { color: #555; margin-left: auto; font-weight: 500; }
.status { font-size: .95rem; font-weight: 600; margin: .5rem 0; min-height: 1.3em; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Iterated Prisoner's Dilemma. Each round both players choose C (cooperate)
// or D (defect). We score in YEARS SERVED (lower is better):
// both C -> 1 year each (reward)
// both D -> 3 years each (punishment)
// I D, you C -> I serve 0, you serve 5 (temptation / sucker)
// Defect is a dominant move in one round; tit-for-tat shines over many rounds.
const YEARS = { CC:[1,1], CD:[5,0], DC:[0,5], DD:[3,3] }; // [self, other] from "my move + your move"
function makeStrategy(kind){
let betrayed = false;
return {
// move(myHist, oppHist) -> 'C' or 'D'
move(myHist, oppHist){
const last = oppHist[oppHist.length-1];
switch(kind){
case 'alld': return 'D';
case 'allc': return 'C';
case 'titfortat': return oppHist.length===0 ? 'C' : last;
case 'grudger':
if(last==='D') betrayed = true;
return betrayed ? 'D' : 'C';
}
}
};
}
function play(meKind, oppKind, rounds){
const me = makeStrategy(meKind), opp = makeStrategy(oppKind);
const myHist=[], oppHist=[]; let myYears=0, oppYears=0; const log=[];
for(let r=0;r<rounds;r++){
const a = me.move(myHist, oppHist);
const b = opp.move(oppHist, myHist);
const [ys, yo] = YEARS[a+b];
myYears += ys; oppYears += yo;
myHist.push(a); oppHist.push(b);
log.push({a,b,ys,yo});
}
return {myYears, oppYears, log};
}
function render(res){
document.getElementById('syou').textContent = res.myYears;
document.getElementById('sopp').textContent = res.oppYears;
const box = document.getElementById('log'); box.innerHTML='';
res.log.forEach((x,i)=>{
const row=document.createElement('div'); row.className='row';
row.innerHTML = fmt('<span class="n">%i%%v%</span><span class="tag %a%">%a%</span><span class="tag %b%">%b%</span><span class="pts">you +%ys% / / opp +%yo%</span>', { i: i, v: 1, a: x.a, b: x.b, ys: x.ys, yo: x.yo });
box.appendChild(row);
});
const s = document.getElementById('status');
if(res.myYears < res.oppYears) s.textContent='You served fewer years this match.';
else if(res.myYears > res.oppYears) s.textContent='Your opponent served fewer years.';
else s.textContent=fmt('A tie — both served %myYears% years.', { myYears: res.myYears });
}
function run(){
const meKind=document.getElementById('me').value;
const oppKind=document.getElementById('opp').value;
let n=parseInt(document.getElementById('rounds').value,10);
if(!(n>=1)) n=1; if(n>30) n=30;
const res = play(meKind, oppKind, n);
render(res);
console.log(fmt('You(%meKind%)=%myYears%yrs Opp(%oppKind%)=%oppYears%yrs over %n% rounds', { meKind: meKind, myYears: res.myYears, oppKind: oppKind, oppYears: res.oppYears, n: n }));
}
function reset(){
document.getElementById('log').innerHTML='';
document.getElementById('syou').textContent='0';
document.getElementById('sopp').textContent='0';
document.getElementById('status').textContent='Choose players and run.';
}
document.getElementById('run').addEventListener('click', run);
document.getElementById('reset').addEventListener('click', reset);
<p class="hint">Pick a strategy for <b>You</b> and an <b>Opponent</b>, then run the rounds.
Each round both choose Cooperate (stay silent) or Defect (betray). Years served are
added up — <b>fewer total years is better</b>. One round rewards betrayal; many rounds
can reward trust.</p>
<div class="picks">
<label>You
<select id="me">
<option value="titfortat">Tit-for-tat</option>
<option value="alld">Always defect</option>
<option value="allc">Always cooperate</option>
<option value="grudger">Grudger</option>
</select>
</label>
<label>Opponent
<select id="opp">
<option value="alld">Always defect</option>
<option value="titfortat">Tit-for-tat</option>
<option value="allc">Always cooperate</option>
<option value="grudger">Grudger</option>
</select>
</label>
<label>Rounds <input id="rounds" type="number" min="1" max="30" value="10"></label>
</div>
<div class="btns">
<button id="run" type="button">Run tournament</button>
<button id="reset" type="button" class="ghost">Clear</button>
</div>
<div class="scores">
<div class="score"><span class="lbl">You</span><span id="syou">0</span><span class="u">yrs</span></div>
<div class="score"><span class="lbl">Opponent</span><span id="sopp">0</span><span class="u">yrs</span></div>
</div>
<div id="log" class="log"></div>
<div class="status" id="status">Choose players and run.</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 .7rem; line-height: 1.45; }
.picks { display: flex; gap: .8rem; flex-wrap: wrap; margin: .4rem 0 .7rem; }
label { font: 600 13px system-ui, sans-serif; color: #1d3557; display: flex; flex-direction: column; gap: .25rem; }
select, input { font: 500 14px system-ui, sans-serif; padding: .35rem .5rem; border: 1px solid #cdd9e3;
border-radius: 7px; background: #fff; }
input { width: 80px; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
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; }
.scores { display: flex; gap: .8rem; margin: .2rem 0 .6rem; }
.score { flex: 1; background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .5rem .7rem; display: flex; align-items: baseline; gap: .4rem; }
.score .lbl { font: 600 12px system-ui; color: #1d3557; text-transform: uppercase; letter-spacing: .03em; }
.score span:nth-child(2) { font: 800 22px ui-monospace, monospace; color: #c92f3c; margin-left: auto; }
.score .u { font: 500 12px system-ui; color: #687; }
.log { display: flex; flex-direction: column; gap: 3px; margin: .3rem 0; max-height: 200px; overflow: auto; }
.row { display: flex; align-items: center; gap: 6px; font: 600 13px ui-monospace, monospace; }
.row .n { color: #888; width: 26px; }
.tag { padding: 1px 7px; border-radius: 6px; min-width: 30px; text-align: center; }
.C { background: #d6f0df; color: #0a7d33; }
.D { background: #fbd9dd; color: #c92f3c; }
.row .pts { color: #555; margin-left: auto; font-weight: 500; }
.status { font-size: .95rem; font-weight: 600; margin: .5rem 0; min-height: 1.3em; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Iterated Prisoner's Dilemma. Each round both players choose C (cooperate)
// or D (defect). We score in YEARS SERVED (lower is better):
// both C -> 1 year each (reward)
// both D -> 3 years each (punishment)
// I D, you C -> I serve 0, you serve 5 (temptation / sucker)
// Defect is a dominant move in one round; tit-for-tat shines over many rounds.
const YEARS = { CC:[1,1], CD:[5,0], DC:[0,5], DD:[3,3] }; // [self, other] from "my move + your move"
function makeStrategy(kind){
let betrayed = false;
return {
// move(myHist, oppHist) -> 'C' or 'D'
move(myHist, oppHist){
const last = oppHist[oppHist.length-1];
switch(kind){
case 'alld': return 'D';
case 'allc': return 'C';
case 'titfortat': return oppHist.length===0 ? 'C' : last;
case 'grudger':
if(last==='D') betrayed = true;
return betrayed ? 'D' : 'C';
}
}
};
}
function play(meKind, oppKind, rounds){
const me = makeStrategy(meKind), opp = makeStrategy(oppKind);
const myHist=[], oppHist=[]; let myYears=0, oppYears=0; const log=[];
for(let r=0;r<rounds;r++){
const a = me.move(myHist, oppHist);
const b = opp.move(oppHist, myHist);
const [ys, yo] = YEARS[a+b];
myYears += ys; oppYears += yo;
myHist.push(a); oppHist.push(b);
log.push({a,b,ys,yo});
}
return {myYears, oppYears, log};
}
function render(res){
document.getElementById('syou').textContent = res.myYears;
document.getElementById('sopp').textContent = res.oppYears;
const box = document.getElementById('log'); box.innerHTML='';
res.log.forEach((x,i)=>{
const row=document.createElement('div'); row.className='row';
row.innerHTML = fmt('<span class="n">%i%%v%</span><span class="tag %a%">%a%</span><span class="tag %b%">%b%</span><span class="pts">you +%ys% / / opp +%yo%</span>', { i: i, v: 1, a: x.a, b: x.b, ys: x.ys, yo: x.yo });
box.appendChild(row);
});
const s = document.getElementById('status');
if(res.myYears < res.oppYears) s.textContent='You served fewer years this match.';
else if(res.myYears > res.oppYears) s.textContent='Your opponent served fewer years.';
else s.textContent=fmt('A tie — both served %myYears% years.', { myYears: res.myYears });
}
function run(){
const meKind=document.getElementById('me').value;
const oppKind=document.getElementById('opp').value;
let n=parseInt(document.getElementById('rounds').value,10);
if(!(n>=1)) n=1; if(n>30) n=30;
const res = play(meKind, oppKind, n);
render(res);
console.log(fmt('You(%meKind%)=%myYears%yrs Opp(%oppKind%)=%oppYears%yrs over %n% rounds', { meKind: meKind, myYears: res.myYears, oppKind: oppKind, oppYears: res.oppYears, n: n }));
}
function reset(){
document.getElementById('log').innerHTML='';
document.getElementById('syou').textContent='0';
document.getElementById('sopp').textContent='0';
document.getElementById('status').textContent='Choose players and run.';
}
document.getElementById('run').addEventListener('click', run);
document.getElementById('reset').addEventListener('click', reset);
Try tit-for-tat (cooperate first, then copy your opponent's last move) against the others. It never beats anyone head-to-head, yet it almost never falls far behind — exactly why it won Robert Axelrod's famous 1980 computer tournaments. Then pit always-defect against itself and watch both players sink to the worst stable outcome, round after round.
Comments
Loading comments...