151 lines
5.3 KiB
HTML
151 lines
5.3 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Combandit demo</title>
|
|
<link rel="icon" href="favicon.ico" type="image/x-icon" />
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
padding: 0;
|
|
width: 500px;
|
|
margin: 50px auto;
|
|
background-color: #f0f0f0;
|
|
}
|
|
|
|
h1, h2 {
|
|
text-align: center;
|
|
}
|
|
|
|
.wrapper {
|
|
margin: 0 auto;
|
|
width: 320px;
|
|
height: 320px;
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
grid-gap: 10px;
|
|
grid-auto-rows: minmax(100px, auto);
|
|
}
|
|
|
|
.gridcell, .gridcell-highlighted {
|
|
background-color: #fafafa;
|
|
border: 1px solid #ddd;
|
|
}
|
|
.gridcell-selected {
|
|
background-color: #d4380d88;
|
|
}
|
|
.gridcell-unselected {
|
|
background-color: #44444422;
|
|
}
|
|
.indicator {
|
|
margin-left: 50%;
|
|
margin-top: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 15%;
|
|
height: 15%;
|
|
}
|
|
.gridcell-highlighted .indicator {
|
|
border: 2px solid #d4380d;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
#avg-reward {
|
|
text-align: center;
|
|
font-size: large;
|
|
}
|
|
</style>
|
|
<script type="module">
|
|
import { shuffle, randint } from "./random.js";
|
|
import { ExpCombandit } from "./combandit.js";
|
|
import Utils from "./utils.js";
|
|
|
|
const combandit = new ExpCombandit({});
|
|
let combanditState = combandit.getInitialState();
|
|
|
|
const handler = {
|
|
nTrials: 0,
|
|
avgReward: 0,
|
|
|
|
getCell(idx) {
|
|
return document.querySelector(`#gridcell-${idx}`);
|
|
},
|
|
|
|
getCellList() {
|
|
const query = '.gridcell, .gridcell-highlighted, .gridcell-selected, .gridcell-unselected'
|
|
return Array.from(document.querySelectorAll(query));
|
|
},
|
|
|
|
init() {
|
|
this.orders = shuffle(Utils.range(4));
|
|
this.currentIdx = 0;
|
|
this.action = Array(4).fill(false);
|
|
|
|
document.querySelector('#prompt').innerHTML = ' ';
|
|
this.getCellList().forEach(cell => cell.className = 'gridcell');
|
|
this.highlightCurrent();
|
|
|
|
this.completed = false;
|
|
},
|
|
|
|
highlightCurrent() {
|
|
const { orders, currentIdx } = this;
|
|
const cell = this.getCell(orders[currentIdx]);
|
|
cell.className = 'gridcell-highlighted';
|
|
},
|
|
|
|
onChoice(choice) {
|
|
const { orders, currentIdx, completed } = this;
|
|
const cell = this.getCell(orders[currentIdx]);
|
|
|
|
this.action[orders[currentIdx]] = choice;
|
|
cell.className = choice ? 'gridcell-selected' : 'gridcell-unselected';
|
|
if (currentIdx < orders.length - 1) {
|
|
this.currentIdx++;
|
|
const nextCell = this.getCell(orders[this.currentIdx]);
|
|
nextCell.className = 'gridcell-highlighted';
|
|
} else if (currentIdx === orders.length - 1) {
|
|
let reward;
|
|
this.completed = true;
|
|
({ reward, nextState: combanditState } = combandit.step(combanditState, this.action));
|
|
this.avgReward = (this.avgReward * this.nTrials + reward) / (this.nTrials + 1);
|
|
this.nTrials++;
|
|
|
|
const rewardText = reward.toFixed(0).replace('-', '−');
|
|
const avgRewardText = this.avgReward.toFixed(2).replace('-', '−');
|
|
document.querySelector('#prompt').innerHTML = `You got ${rewardText} points!`;
|
|
document.querySelector('#avg-reward').innerHTML = `Avg reward: ${avgRewardText}, Trials: ${this.nTrials}`;
|
|
setTimeout(() => this.init(), 2000);
|
|
}
|
|
},
|
|
}
|
|
|
|
const keysHeld = new Set();
|
|
document.addEventListener('DOMContentLoaded', () => handler.init());
|
|
document.addEventListener('keydown', function(event) {
|
|
const keys = ['f', 'j', 'F', 'J'];
|
|
if (keys.includes(event.key) && !keysHeld.has(event.key) && !handler.completed) {
|
|
keysHeld.add(event.key);
|
|
handler.onChoice(event.key.toLowerCase() === 'f');
|
|
}
|
|
});
|
|
document.addEventListener('keyup', function(event) {
|
|
if (event.key.match(/[a-z]/i)) {
|
|
keysHeld.delete(event.key);
|
|
}
|
|
});
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<h1>Press <span style="color: #d4380d;">F</span> or <span style="color: #444444;">J</span></h1>
|
|
<h2 id="prompt"> </h2>
|
|
|
|
<div class="wrapper" id="grid">
|
|
<div class="gridcell" id="gridcell-0"><div class="indicator"></div></div>
|
|
<div class="gridcell" id="gridcell-1"><div class="indicator"></div></div>
|
|
<div class="gridcell" id="gridcell-2"><div class="indicator"></div></div>
|
|
<div class="gridcell" id="gridcell-3"><div class="indicator"></div></div>
|
|
</div>
|
|
|
|
<p id="avg-reward">Avg reward: 0</p>
|
|
</body>
|
|
</html> |