const explainerText = "The expected maximum: the expected value of the maximum sample (averaged over many runs).";
const g = x => x.querySelector.bind(x);
const $ = g(document);
const range = n => {
let res = [];
for (let i = 1; i <= n; i++) {
res.push(i);
}
return res;
};
const counts = range(9).map(c => `.count-${c}`);
const randShift = _ => shifts[Math.floor(shifts.length * Math.random())];
const parentBoard = x =>
/\bpegboard\b/.test(x.className) ? x : parentBoard(x.parentNode);
const incrs = count => e => {
if (e.animationName != "bounceY") return;
e.target.remove();
e.target.removeEventListener("animationend", incrs);
count.innerHTML = +count.innerHTML + 1;
count.classList.add("hit");
const max = g(parentBoard(count))(".max");
const c = +count.nextElementSibling.innerText.match(/.?\d+\.?\d*/)[0];
max.innerHTML = max.innerHTML === "" ? c : Math.max(max.innerHTML, c);
};
const maxListener = (onRemove, onAdd) => rs => {
[].forEach.call(rs, r => {
r.removedNodes.length && onRemove(r.target);
r.addedNodes[0] &&
onAdd(r.target, Math.round((+r.addedNodes[0].data * 3) / 2));
});
};
const colorize = maxListener(
max => max.classList.remove("positive", "negative"),
(max, d) =>
(d > 0 && max.classList.add("positive")) ||
(d < 0 && max.classList.add("negative"))
);
const colorListener = new MutationObserver(colorize);
const σs = max => [].slice.call(parentBoard(max).querySelectorAll(".sigma"));
const off = max => +parentBoard(max).className.match(/offset(.?\d)/)[1];
const progress = maxListener(
max => σs(max).forEach(e => e.classList.remove("achieved")),
(max, d) =>
σs(max)
.slice(0, d + 5 - off(max))
.forEach(e => e.classList.add("achieved"))
);
const progListener = new MutationObserver(progress);
const mutObs = max =>
[colorListener, progListener].forEach(l =>
l.observe(max, { childList: true })
);
const createBall = pb => s => {
let b = document.createElement("div");
b.classList.add("ball");
b.style.animation = `bounceX${s
.toString()
.replace(/,/g, "")
.replace("-", "m")} 1.2s linear forwards`;
b.addEventListener(
"animationend",
incrs(g(pb)(counts[(s[s.length - 1] + 8) / 2]))
);
return b;
};
const updateSigmas = (e, offset) => {
σText = range(9)
.map(i => ((i + offset - 5) * 2) / 3)
.map(i => (i === 0 ? "0" : i.toFixed(1) + "σ"));
[].forEach.call(
e.querySelectorAll(".sigma"),
(σ, i) => (σ.innerText = σText[i])
);
};
const newBoard = (b => {
const d = document.createElement("div");
return (c, offset) => {
var offset = offset || 0;
d.innerHTML = b;
const e = d.firstChild;
e.classList.remove("offset0");
e.classList.add(`offset${offset}`);
updateSigmas(e, offset);
e.id = c;
mutObs(g(e)(".max"));
return e;
};
})($(".pegboard").outerHTML);
const state = {};
const toOffset = x => Math.round((x * 3) / 2);
const toggleVis = e => evt => {
evt.preventDefault();
e.classList[e.classList.contains("no-show") ? "remove" : "add"]("no-show");
};
const asterisk = explainer => {
const e = document.createElement("a");
e.href = "#";
e.innerText = "*";
e.onclick = toggleVis(explainer);
return e;
};
const createBar = x => {
const d = document.createElement("div");
d.classList.add("bar");
d.style.left = `${x}em`;
const explainer = document.createElement("div");
explainer.innerText = explainerText;
explainer.classList.add("no-show", "explainer");
const s = document.createElement("span");
s.innerText = "Expected maximum";
s.classList.add("bar-text");
s.appendChild(asterisk(explainer));
d.append(s, explainer);
return d;
};
const sdsToEm = sds => ((sds * 3) / 2) * 8 + 35.7;
const addRow = (ss, lor) => {
$(`#${lor ? "left" : "right"}-runs tbody`).innerHTML += `<tr><td>${ss}</td>${
lor
? ""
: `<td>${+$("#rboards .pegboard:last-child").id.match(/\d+/)[0]}</td>`
}<td>${+$(`#${lor ? "l" : "r"}boards .pegboard:last-child .max`)
.innerHTML}</td></tr>`;
};
const runNext = (brd, es, ss) => {
const id = brd.id.replace(/\d/, n => +n + 1);
const n = newBoard(id, toOffset(g(brd)(".max").innerText));
brd.parentNode.appendChild(n);
run(`#${id}`, es, ss);
};
const run = (brd, es, ss) => {
state[brd] = state[brd] || {};
const s = state[brd];
s.timeouts = s.timeouts || [];
s.bs = s.bs || [];
const pb = $(brd);
// cleanup
s.timeouts.forEach(clearTimeout);
counts.forEach(c => {
g(pb)(c).innerHTML = 0;
g(pb)(c).classList.remove("hit");
});
s.bs.forEach(el => el.remove());
g(pb)(".max").innerHTML = "";
// again
const shifts = range(es).map(randShift);
s.bs = shifts.map(createBall(pb));
s.bs.forEach((c, i) => {
s.timeouts.push(setTimeout(_ => pb.prepend(c), i * 150));
});
const cb = evt => {
evt.target.removeEventListener("animationend", cb);
s.timeouts.push(
setTimeout(
_ => (ss != null && ss > 1 ? runNext(pb, es, ss - 1) : addRow(es, !ss)),
0
)
);
};
s.bs[s.bs.length - 1].addEventListener("animationend", cb);
};
$("#beanputs").addEventListener("submit", e => {
e.preventDefault();
$("#rboards").innerHTML = "";
$("#rboards").appendChild(newBoard("pegboard-right-1"));
[].forEach.call(document.querySelectorAll(".bar"), e => e.remove());
$("#lboards").prepend(
createBar(sdsToEm(snlook[+$("[name=beans]").value + 1]))
);
$("#rboards").prepend(
createBar(
sdsToEm($("[name=stages]").value * snlook[+$("[name=per]").value + 1])
)
);
run("#pegboard-left", $("[name=beans]").value);
run("#pegboard-right-1", $("[name=per]").value, $("[name=stages]").value);
});
mutObs($(".max"));