can select strategies
This commit is contained in:
parent
268e073a4a
commit
87abce2ffc
2 changed files with 236 additions and 97 deletions
289
index.html
289
index.html
|
|
@ -4,64 +4,89 @@
|
|||
<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;
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
#controls {
|
||||
margin-bottom: 15px;
|
||||
#controls {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#stepBtn {
|
||||
font-size: 14px;
|
||||
padding: 6px 16px;
|
||||
cursor: pointer;
|
||||
#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;
|
||||
#container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
#grid {
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
background-color: #333;
|
||||
border: 1px solid #333;
|
||||
#grid {
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
background-color: #333;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.cell {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
.cell {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
#legend {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding-top: 4px;
|
||||
#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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: inline-block;
|
||||
border: 1px solid #333;
|
||||
flex-shrink: 0;
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
|
@ -69,87 +94,177 @@
|
|||
|
||||
<h1>Strategy Grid</h1>
|
||||
<div id="controls">
|
||||
<button id="stepBtn">Step</button>
|
||||
<button id="stepBtn">Step</button>
|
||||
<button id="resetBtn">Reset</button>
|
||||
</div>
|
||||
<div id="container">
|
||||
<div id="grid"></div>
|
||||
<div id="legend"></div>
|
||||
<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>
|
||||
|
||||
<script src="main.js"></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 ----
|
||||
|
||||
const COLORS = {
|
||||
allwaysCooperate: "#2ecc71", // green
|
||||
allwaysDefect: "#e74c3c", // red
|
||||
randomChoice: "#95a5a6", // gray
|
||||
titForThat: "#3498db", // blue
|
||||
grimTrigger: "#8e44ad", // purple
|
||||
majority: "#f39c12" // orange
|
||||
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",
|
||||
grimTrigger: "Grim Trigger",
|
||||
majority: "Majority"
|
||||
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)`;
|
||||
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);
|
||||
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("legend");
|
||||
for (const key in COLORS) {
|
||||
const item = document.createElement("div");
|
||||
item.className = "legend-item";
|
||||
const legendEl = document.getElementById("legendItems");
|
||||
for (const key in COLORS) {
|
||||
const item = document.createElement("div");
|
||||
item.className = "legend-item";
|
||||
|
||||
const colorBox = document.createElement("span");
|
||||
colorBox.className = "legend-color";
|
||||
colorBox.style.backgroundColor = COLORS[key];
|
||||
const checkbox = document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.id = `checkbox-${key}`;
|
||||
checkbox.checked = true;
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.textContent = LABELS[key];
|
||||
const colorBox = document.createElement("span");
|
||||
colorBox.className = "legend-color";
|
||||
colorBox.style.backgroundColor = COLORS[key];
|
||||
|
||||
item.appendChild(colorBox);
|
||||
item.appendChild(label);
|
||||
legendEl.appendChild(item);
|
||||
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 ----
|
||||
|
||||
const party = new Party(60);
|
||||
let party = new Party(60);
|
||||
renderGrid(party);
|
||||
renderLegend();
|
||||
|
||||
document.getElementById("stepBtn").addEventListener("click", () => {
|
||||
party.step();
|
||||
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>
|
||||
|
|
|
|||
44
main.js
44
main.js
|
|
@ -20,6 +20,13 @@ const TFT = function titForThat(foeHistory) {
|
|||
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) {
|
||||
if (foeHistory.some(e => e === false)) {
|
||||
return false;
|
||||
|
|
@ -45,29 +52,46 @@ const MAJ = function majority(foeHistory) {
|
|||
}
|
||||
|
||||
class Party {
|
||||
constructor(length) {
|
||||
constructor(length, functions) {
|
||||
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 = [];
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
for (let i = 0; i < this.length; 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);
|
||||
this.grid[i][j] = this.functions[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
step() {
|
||||
const T = 5;
|
||||
const R = 3;
|
||||
const P = 1;
|
||||
const S = 0;
|
||||
reset(functions) {
|
||||
// if a new list of active strategies is passed, update it;
|
||||
// otherwise keep the current selection
|
||||
if (functions && functions.length > 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;
|
||||
|
||||
let scores = [];
|
||||
|
|
@ -130,7 +154,7 @@ class Party {
|
|||
}
|
||||
}
|
||||
|
||||
// changing strarts
|
||||
// changing strats
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
for (let j = 0; j < this.length; j++) {
|
||||
let bestScore = scores[i][j];
|
||||
|
|
|
|||
Loading…
Reference in a new issue