can select strategies

This commit is contained in:
Didictateur 2026-07-25 22:13:49 +02:00
parent 268e073a4a
commit 87abce2ffc
2 changed files with 236 additions and 97 deletions

View file

@ -4,64 +4,89 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Strategy Grid</title> <title>Strategy Grid</title>
<style> <style>
body { body {
font-family: sans-serif; font-family: sans-serif;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin-top: 30px; margin-top: 30px;
} }
#controls { #controls {
margin-bottom: 15px; margin-bottom: 15px;
} }
#stepBtn { #stepBtn, #resetBtn {
font-size: 14px; font-size: 14px;
padding: 6px 16px; padding: 6px 16px;
cursor: pointer; cursor: pointer;
margin-right: 8px;
} }
#container { #container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: flex-start; align-items: flex-start;
gap: 30px; gap: 30px;
} }
#grid { #grid {
display: grid; display: grid;
gap: 1px; gap: 1px;
background-color: #333; background-color: #333;
border: 1px solid #333; border: 1px solid #333;
} }
.cell { .cell {
width: 12px; width: 12px;
height: 12px; height: 12px;
} }
#legend { #legend {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
padding-top: 4px; padding-top: 4px;
} }
.legend-item { .legend-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
font-size: 14px; font-size: 14px;
white-space: nowrap; white-space: nowrap;
} }
.legend-color { .legend-item input[type="checkbox"] {
width: 14px; cursor: pointer;
height: 14px; }
display: inline-block;
border: 1px solid #333; .legend-color {
flex-shrink: 0; 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;
} }
</style> </style>
</head> </head>
@ -69,87 +94,177 @@
<h1>Strategy Grid</h1> <h1>Strategy Grid</h1>
<div id="controls"> <div id="controls">
<button id="stepBtn">Step</button> <button id="stepBtn">Step</button>
<button id="resetBtn">Reset</button>
</div> </div>
<div id="container"> <div id="container">
<div id="grid"></div> <div id="grid"></div>
<div id="legend"></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>
<script src="main.js"></script> <script src="main.js"></script>
<script> <script>
// This file only handles rendering.
// ALLC, ALLD, RAND, TFT, GRIM, MAJ and the Party class
// are already defined in script.js and are not redefined here.
// ---- Color / function mapping ---- // ---- Color / function mapping ----
const COLORS = { const COLORS = {
allwaysCooperate: "#2ecc71", // green allwaysCooperate: "#2ecc71", // green
allwaysDefect: "#e74c3c", // red allwaysDefect: "#e74c3c", // red
randomChoice: "#95a5a6", // gray randomChoice: "#95a5a6", // gray
titForThat: "#3498db", // blue titForThat: "#5dade2", // light blue
grimTrigger: "#8e44ad", // purple suspiciousTitForTat: "#2874a6", // dark blue
majority: "#f39c12" // orange grimTrigger: "#8e44ad", // purple
majority: "#f39c12" // orange
}; };
const LABELS = { const LABELS = {
allwaysCooperate: "Always Cooperate", allwaysCooperate: "Always Cooperate",
allwaysDefect: "Always Defect", allwaysDefect: "Always Defect",
randomChoice: "Random", randomChoice: "Random",
titForThat: "Tit-for-Tat", titForThat: "Tit-for-Tat",
grimTrigger: "Grim Trigger", suspiciousTitForTat: "Suspicious Tit-for-Tat",
majority: "Majority" 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 ---- // ---- Rendering ----
function renderGrid(party) { function renderGrid(party) {
const gridEl = document.getElementById("grid"); const gridEl = document.getElementById("grid");
gridEl.innerHTML = ""; gridEl.innerHTML = "";
gridEl.style.gridTemplateColumns = `repeat(${party.length}, 12px)`; gridEl.style.gridTemplateColumns = `repeat(${party.length}, 12px)`;
for (let i = 0; i < party.length; i++) { for (let i = 0; i < party.length; i++) {
for (let j = 0; j < party.length; j++) { for (let j = 0; j < party.length; j++) {
const fn = party.grid[i][j]; const fn = party.grid[i][j];
const cell = document.createElement("div"); const cell = document.createElement("div");
cell.className = "cell"; cell.className = "cell";
cell.style.backgroundColor = COLORS[fn.name] || "#000"; cell.style.backgroundColor = COLORS[fn.name] || "#000";
cell.title = LABELS[fn.name] || fn.name; cell.title = LABELS[fn.name] || fn.name;
gridEl.appendChild(cell); gridEl.appendChild(cell);
} }
} }
} }
function renderLegend() { function renderLegend() {
const legendEl = document.getElementById("legend"); const legendEl = document.getElementById("legendItems");
for (const key in COLORS) { for (const key in COLORS) {
const item = document.createElement("div"); const item = document.createElement("div");
item.className = "legend-item"; item.className = "legend-item";
const colorBox = document.createElement("span"); const checkbox = document.createElement("input");
colorBox.className = "legend-color"; checkbox.type = "checkbox";
colorBox.style.backgroundColor = COLORS[key]; checkbox.id = `checkbox-${key}`;
checkbox.checked = true;
const label = document.createElement("span"); const colorBox = document.createElement("span");
label.textContent = LABELS[key]; colorBox.className = "legend-color";
colorBox.style.backgroundColor = COLORS[key];
item.appendChild(colorBox); const label = document.createElement("span");
item.appendChild(label); label.textContent = LABELS[key];
legendEl.appendChild(item);
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 ---- // ---- Execution ----
const party = new Party(60); let party = new Party(60);
renderGrid(party); renderGrid(party);
renderLegend(); renderLegend();
document.getElementById("stepBtn").addEventListener("click", () => { function doStep() {
party.step(); 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); 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> </script>
</body> </body>

44
main.js
View file

@ -20,6 +20,13 @@ const TFT = function titForThat(foeHistory) {
return foeHistory[foeHistory.length - 1]; return foeHistory[foeHistory.length - 1];
} }
const STFT = function suspiciousTitForTat(foeHistory) {
if (foeHistory.length === 0) {
return false;
}
return foeHistory[foeHistory.length - 1];
}
const GRIM = function grimTrigger(foeHistory) { const GRIM = function grimTrigger(foeHistory) {
if (foeHistory.some(e => e === false)) { if (foeHistory.some(e => e === false)) {
return false; return false;
@ -45,29 +52,46 @@ const MAJ = function majority(foeHistory) {
} }
class Party { class Party {
constructor(length) { constructor(length, functions) {
this.length = length; this.length = length;
this.functions = [ALLC, ALLD, RAND, TFT, GRIM, MAJ]; // if a list of active strategies is provided, use it,
// otherwise fall back to all seven by default
this.functions = (functions && functions.length > 0)
? functions
: [ALLC, ALLD, RAND, TFT, STFT, GRIM, MAJ];
this.grid = []; this.grid = [];
for (let i = 0; i < length; i++) { for (let i = 0; i < this.length; i++) {
this.grid[i] = []; this.grid[i] = [];
for (let j = 0; j < length; j++) { for (let j = 0; j < this.length; j++) {
const index = Math.floor(Math.random() * this.functions.length); const index = Math.floor(Math.random() * this.functions.length);
this.grid[i][j] = this.functions[index]; this.grid[i][j] = this.functions[index];
} }
} }
} }
step() { reset(functions) {
const T = 5; // if a new list of active strategies is passed, update it;
const R = 3; // otherwise keep the current selection
const P = 1; if (functions && functions.length > 0) {
const S = 0; this.functions = functions;
}
this.grid = [];
for (let i = 0; i < this.length; i++) {
this.grid[i] = [];
for (let j = 0; j < this.length; j++) {
const index = Math.floor(Math.random() * this.functions.length);
this.grid[i][j] = this.functions[index];
}
}
}
step(T = 5, R = 3, P = 1, S = 0) {
const nbRound = 50; const nbRound = 50;
let scores = []; let scores = [];
@ -130,7 +154,7 @@ class Party {
} }
} }
// changing strarts // changing strats
for (let i = 0; i < this.length; i++) { for (let i = 0; i < this.length; i++) {
for (let j = 0; j < this.length; j++) { for (let j = 0; j < this.length; j++) {
let bestScore = scores[i][j]; let bestScore = scores[i][j];