dilemmap/index.html
2026-07-25 23:04:05 +02:00

355 lines
No EOL
9.8 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Strategy Grid</title>
<style>
body {
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
margin-top: 30px;
}
#controls {
margin-bottom: 15px;
}
#stepBtn, #resetBtn {
font-size: 14px;
padding: 6px 16px;
cursor: pointer;
margin-right: 8px;
}
#container {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 30px;
}
#grid {
display: grid;
gap: 1px;
background-color: #333;
border: 1px solid #333;
}
.cell {
width: 12px;
height: 12px;
}
#legend {
display: flex;
flex-direction: column;
gap: 10px;
padding-top: 4px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
white-space: nowrap;
}
.legend-item input[type="checkbox"] {
cursor: pointer;
}
.legend-color {
width: 14px;
height: 14px;
display: inline-block;
border: 1px solid #333;
flex-shrink: 0;
}
#payoffs {
margin-top: 20px;
display: flex;
flex-direction: column;
gap: 8px;
}
.payoff-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
font-size: 14px;
}
.payoff-item input {
width: 50px;
font-size: 14px;
}
#explanation {
max-width: 700px;
margin-top: 40px;
font-size: 14px;
line-height: 1.6;
}
#explanation h2 {
font-size: 18px;
margin-bottom: 6px;
}
#explanation h3 {
font-size: 15px;
margin-bottom: 4px;
}
#explanation ul {
margin-top: 4px;
padding-left: 20px;
}
#explanation li {
margin-bottom: 4px;
}
</style>
</head>
<body>
<h1>Strategy Grid</h1>
<div id="controls">
<button id="stepBtn">Step</button>
<button id="resetBtn">Reset</button>
</div>
<div id="container">
<div id="grid"></div>
<div id="legend">
<div id="legendItems"></div>
<div id="payoffs">
<div class="payoff-item">
<label for="inputT">T (temptation)</label>
<input id="inputT" type="number" value="5">
</div>
<div class="payoff-item">
<label for="inputR">R (reward)</label>
<input id="inputR" type="number" value="3">
</div>
<div class="payoff-item">
<label for="inputP">P (punishment)</label>
<input id="inputP" type="number" value="1">
</div>
<div class="payoff-item">
<label for="inputS">S (sucker)</label>
<input id="inputS" type="number" value="0">
</div>
</div>
</div>
</div>
<div id="explanation">
<h2>Explanation</h2>
<p>
This grid simulates an iterated prisoner's dilemma played across a population of cells.
Each cell represents an agent applying a strategy and playing against its immediate
neighbors at every step ("Step"). Depending on the results obtained, a cell may adopt
the strategy of a better-performing neighbor, which causes the distribution of colors
to evolve over successive steps.
<p></p>
Moreover, one cell randomly change strategy in order to see the emergence of new cluster when the parameters are changed.
</p>
<h3>Parameters (payoff matrix)</h3>
<ul>
<li><strong>T (temptation)</strong>: payoff for defecting against an opponent who cooperates.</li>
<li><strong>R (reward)</strong>: payoff when both players cooperate.</li>
<li><strong>P (punishment)</strong>: payoff when both players defect.</li>
<li><strong>S (sucker)</strong>: payoff for cooperating against an opponent who defects.</li>
</ul>
<p>
For the dilemma to be valid, the order T &gt; R &gt; P &gt; S must hold. But it doesn't prevent you to have fun with those.
</p>
<h3>Strategies</h3>
<ul>
<li><strong>Always Cooperate</strong>: cooperates systematically, regardless of the opponent's behavior.</li>
<li><strong>Always Defect</strong>: defects systematically.</li>
<li><strong>Random</strong>: chooses randomly between cooperation and defection at each interaction.</li>
<li><strong>Tit-for-Tat</strong>: cooperates on the first move, then repeats the opponent's last move.</li>
<li><strong>Suspicious Tit-for-Tat</strong>: identical to Tit-for-Tat, but defects on the first move.</li>
<li><strong>Grim Trigger</strong>: cooperates as long as the opponent cooperates, but defects permanently after the opponent's first defection.</li>
<li><strong>Majority</strong>: reproduces the opponent's majority behavior over the history of previous moves.</li>
</ul>
<h3>Usage</h3>
<ul>
<li>The checkboxes in the legend select which strategies are included on "Reset".</li>
<li>The "Step" button advances the simulation by one step (holding it down repeats the operation automatically).</li>
<li>The "Reset" button reinitializes the grid with the selected strategies.</li>
</ul>
<h3>Why cooperation emerges here</h3>
<p>
In the classic single-shot, two-player prisoner's dilemma, defection is the dominant
strategy: whatever the opponent does, defecting yields a better payoff, so rational
players end up at the mutual-defection outcome (P, P) even though mutual cooperation
(R, R) would leave both better off. This grid nonetheless shows sizable pockets of
cooperation surviving and even spreading. Two features of the setup explain the
difference. First, the game is repeated rather than played once, so strategies such
as Tit-for-Tat or Grim Trigger can punish defection in future rounds, which removes
the incentive to defect that exists in a single, isolated encounter. Second, agents
only interact with their local neighbors and imitate nearby strategies that perform
well, so clusters of cooperators can support each other and outcompete an isolated
defector, even though a defector still beats an individual cooperator in a single
pairwise match.
</p>
</div>
<script src="main.js"></script>
<script>
// ---- Color / function mapping ----
const COLORS = {
allwaysCooperate: "#2ecc71", // green
allwaysDefect: "#e74c3c", // red
randomChoice: "#95a5a6", // gray
titForThat: "#5dade2", // light blue
suspiciousTitForTat: "#2874a6", // dark blue
grimTrigger: "#8e44ad", // purple
majority: "#f39c12" // orange
};
const LABELS = {
allwaysCooperate: "Always Cooperate",
allwaysDefect: "Always Defect",
randomChoice: "Random",
titForThat: "Tit-for-Tat",
suspiciousTitForTat: "Suspicious Tit-for-Tat",
grimTrigger: "Grim Trigger",
majority: "Majority"
};
// maps each legend key to the actual strategy function defined in main.js
const STRATEGY_FUNCTIONS = {
allwaysCooperate: ALLC,
allwaysDefect: ALLD,
randomChoice: RAND,
titForThat: TFT,
suspiciousTitForTat: STFT,
grimTrigger: GRIM,
majority: MAJ
};
// ---- Rendering ----
function renderGrid(party) {
const gridEl = document.getElementById("grid");
gridEl.innerHTML = "";
gridEl.style.gridTemplateColumns = `repeat(${party.length}, 12px)`;
for (let i = 0; i < party.length; i++) {
for (let j = 0; j < party.length; j++) {
const fn = party.grid[i][j];
const cell = document.createElement("div");
cell.className = "cell";
cell.style.backgroundColor = COLORS[fn.name] || "#000";
cell.title = LABELS[fn.name] || fn.name;
gridEl.appendChild(cell);
}
}
}
function renderLegend() {
const legendEl = document.getElementById("legendItems");
for (const key in COLORS) {
const item = document.createElement("div");
item.className = "legend-item";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.id = `checkbox-${key}`;
checkbox.checked = true;
const colorBox = document.createElement("span");
colorBox.className = "legend-color";
colorBox.style.backgroundColor = COLORS[key];
const label = document.createElement("span");
label.textContent = LABELS[key];
item.appendChild(checkbox);
item.appendChild(colorBox);
item.appendChild(label);
legendEl.appendChild(item);
}
}
// returns the list of strategy functions whose checkbox is currently checked
function getSelectedFunctions() {
const selected = [];
for (const key in STRATEGY_FUNCTIONS) {
const checkbox = document.getElementById(`checkbox-${key}`);
if (checkbox && checkbox.checked) {
selected.push(STRATEGY_FUNCTIONS[key]);
}
}
return selected;
}
// ---- Execution ----
let party = new Party(60);
renderGrid(party);
renderLegend();
function doStep() {
const T = Number(document.getElementById("inputT").value);
const R = Number(document.getElementById("inputR").value);
const P = Number(document.getElementById("inputP").value);
const S = Number(document.getElementById("inputS").value);
party.step(T, R, P, S);
renderGrid(party);
}
function doReset() {
const selected = getSelectedFunctions();
party.reset(selected); // if selected is empty, Party keeps the previous selection
renderGrid(party);
}
// single click: one step
document.getElementById("stepBtn").addEventListener("click", doStep);
// held click: repeat steps automatically, as if the button were being spammed
const stepBtn = document.getElementById("stepBtn");
let holdInterval = null;
const HOLD_DELAY = 300; // ms before auto-repeat kicks in
const HOLD_INTERVAL = 10; // ms between repeated steps while held
function startHold() {
stopHold(); // safety, avoid stacking intervals
holdInterval = setTimeout(() => {
holdInterval = setInterval(doStep, HOLD_INTERVAL);
}, HOLD_DELAY);
}
function stopHold() {
clearTimeout(holdInterval);
clearInterval(holdInterval);
holdInterval = null;
}
stepBtn.addEventListener("mousedown", startHold);
stepBtn.addEventListener("mouseup", stopHold);
stepBtn.addEventListener("mouseleave", stopHold);
stepBtn.addEventListener("touchstart", (e) => { e.preventDefault(); startHold(); });
stepBtn.addEventListener("touchend", stopHold);
document.getElementById("resetBtn").addEventListener("click", doReset);
</script>
</body>
</html>