18
Turing Machine Simulator: Busy Beaver
Tap PROG to switch machines, +/− to change speed
idle
130 lines · vanilla
view source
const ORIG = 256, TLEN = 512, BTN = 44, PAD = 12;
let PROGS, pi, tape, head, state, steps, halted, haltT, acc, sps;
let camX, headAnim, lastRule;
let W = 0, H = 0;
const SCOL = { A: "#ff5d5d", B: "#4dd2ff", C: "#7dff6e", D: "#ffd34d", I: "#d08bff", H: "#ffffff" };
function makePrograms() {
// rules[state][symbol] = [write, move(+1 R / -1 L), nextState]; "H" halts
return [
{ name: "BUSY BEAVER 3-STATE", start: "A", keep: false,
rules: { A: [[1, 1, "B"], [1, 1, "H"]], B: [[0, 1, "C"], [1, 1, "B"]], C: [[1, -1, "C"], [1, -1, "A"]] } },
{ name: "BUSY BEAVER 4-STATE", start: "A", keep: false,
rules: { A: [[1, 1, "B"], [1, -1, "B"]], B: [[1, -1, "A"], [0, -1, "C"]],
C: [[1, 1, "H"], [1, -1, "D"]], D: [[1, 1, "D"], [0, 1, "A"]] } },
{ name: "BINARY COUNTER (+1)", start: "I", keep: true,
rules: { I: [[1, 1, "H"], [0, -1, "I"]] } },
];
}
function reset(k, keep) {
pi = k;
if (!keep || !tape) tape = new Int8Array(TLEN);
head = 0; state = PROGS[k].start; steps = 0;
halted = false; haltT = 0; acc = 0; lastRule = "—";
}
function doStep(time) {
const sym = tape[ORIG + head];
const r = PROGS[pi].rules[state][sym];
lastRule = state + "," + sym + " → " + r[2] + "," + r[0] + "," + (r[1] > 0 ? "R" : "L");
tape[ORIG + head] = r[0];
head += r[1];
if (head < -250) head = -250; if (head > 250) head = 250;
state = r[2]; steps++;
if (state === "H") { halted = true; haltT = time; }
}
function countOnes() {
let n = 0;
for (let i = 0; i < TLEN; i++) n += tape[i];
return n;
}
function inRect(x, y, rx, ry, rw, rh) { return x >= rx && x <= rx + rw && y >= ry && y <= ry + rh; }
function drawBtn(ctx, x, y, w, label, hot) {
ctx.fillStyle = hot ? "rgba(120,180,255,0.85)" : "rgba(0,0,0,0.65)";
ctx.fillRect(x, y, w, BTN);
ctx.strokeStyle = "rgba(255,255,255,0.45)"; ctx.lineWidth = 1;
ctx.strokeRect(x + 0.5, y + 0.5, w - 1, BTN - 1);
ctx.fillStyle = "#fff"; ctx.font = "bold 18px monospace";
ctx.textAlign = "center"; ctx.textBaseline = "middle";
ctx.fillText(label, x + w / 2, y + BTN / 2);
}
function init({ width, height }) {
W = width; H = height;
PROGS = makePrograms();
sps = 3; camX = 0; headAnim = 0; tape = null;
reset(0, false);
}
function tick({ ctx, dt, time, width, height, input }) {
if (width !== W || height !== H) { W = width; H = height; }
const by = H - PAD - BTN;
const plusX = W - PAD - BTN, minusX = plusX - BTN - 8, progW = 92, progX = PAD;
for (const c of input.consumeClicks()) {
if (inRect(c.x, c.y, minusX, by, BTN, BTN)) sps = Math.max(0.5, sps / 2);
else if (inRect(c.x, c.y, plusX, by, BTN, BTN)) sps = Math.min(64, sps * 2);
else if (inRect(c.x, c.y, progX, by, progW, BTN)) reset((pi + 1) % PROGS.length, false);
}
if (!halted) {
acc += dt * sps;
let n = 0;
while (acc >= 1 && n < 200 && !halted) { doStep(time); acc -= 1; n++; }
} else if (time - haltT > 2) {
reset(pi, PROGS[pi].keep);
}
headAnim += (head - headAnim) * Math.min(1, dt * 12);
camX += (headAnim - camX) * Math.min(1, dt * 5);
const ones = countOnes();
ctx.fillStyle = "#0a0d14"; ctx.fillRect(0, 0, W, H);
const cs = Math.max(26, Math.min(54, W / 11));
const ty = H * 0.52;
const half = (W / 2) / cs + 1;
const i0 = Math.max(-250, Math.floor(camX - half)), i1 = Math.min(250, Math.ceil(camX + half));
for (let i = i0; i <= i1; i++) {
const x = W / 2 + (i - camX) * cs;
ctx.fillStyle = tape[ORIG + i] ? "#ffb347" : "#161c2a";
ctx.fillRect(x + 1, ty - cs / 2, cs - 2, cs);
if (tape[ORIG + i]) {
ctx.fillStyle = "#5a3a10"; ctx.font = "bold " + ((cs * 0.5) | 0) + "px monospace";
ctx.textAlign = "center"; ctx.textBaseline = "middle";
ctx.fillText("1", x + cs / 2, ty + 1);
}
}
ctx.strokeStyle = "#2a3144"; ctx.lineWidth = 1;
ctx.strokeRect(0.5, ty - cs / 2 - 0.5, W - 1, cs + 1);
// head cursor
const hx = W / 2 + (headAnim - camX) * cs + cs / 2;
const col = SCOL[state] || "#fff";
ctx.strokeStyle = col; ctx.lineWidth = 2;
ctx.strokeRect(hx - cs / 2 + 1, ty - cs / 2 - 1, cs - 2, cs + 2);
ctx.fillStyle = col;
ctx.beginPath();
ctx.moveTo(hx, ty - cs / 2 - 6);
ctx.lineTo(hx - 11, ty - cs / 2 - 24);
ctx.lineTo(hx + 11, ty - cs / 2 - 24);
ctx.closePath(); ctx.fill();
// big state letter
ctx.fillStyle = col; ctx.font = "bold 46px monospace";
ctx.textAlign = "center"; ctx.textBaseline = "middle";
ctx.fillText(state, W / 2, ty - cs / 2 - 64);
ctx.fillStyle = "#7d8699"; ctx.font = "11px monospace";
ctx.fillText("state", W / 2, ty - cs / 2 - 36);
// HUD
ctx.fillStyle = "rgba(0,0,0,0.55)"; ctx.fillRect(PAD, PAD, 232, 80);
ctx.fillStyle = "#9fb4ff"; ctx.font = "bold 12px monospace";
ctx.textAlign = "left"; ctx.textBaseline = "alphabetic";
ctx.fillText(PROGS[pi].name, PAD + 10, PAD + 18);
ctx.fillStyle = "#fff"; ctx.font = "12px monospace";
ctx.fillText("step " + steps + " ones " + ones, PAD + 10, PAD + 38);
ctx.fillText("rule " + lastRule, PAD + 10, PAD + 56);
ctx.fillText("speed x" + sps, PAD + 10, PAD + 74);
if (halted) {
const a = 0.55 + 0.45 * Math.sin(time * 9);
ctx.fillStyle = "rgba(0,0,0,0.6)";
ctx.fillRect(0, ty + cs / 2 + 14, W, 64);
ctx.textAlign = "center"; ctx.textBaseline = "middle";
ctx.fillStyle = "rgba(255,93,93," + a.toFixed(3) + ")";
ctx.font = "bold 30px monospace";
ctx.fillText("HALT", W / 2, ty + cs / 2 + 36);
ctx.fillStyle = "#ffd34d"; ctx.font = "bold 14px monospace";
ctx.fillText("score: " + ones + " ones in " + steps + " steps", W / 2, ty + cs / 2 + 62);
}
const hotMinus = input.mouseDown && inRect(input.mouseX, input.mouseY, minusX, by, BTN, BTN);
const hotPlus = input.mouseDown && inRect(input.mouseX, input.mouseY, plusX, by, BTN, BTN);
const hotProg = input.mouseDown && inRect(input.mouseX, input.mouseY, progX, by, progW, BTN);
drawBtn(ctx, minusX, by, BTN, "−", hotMinus);
drawBtn(ctx, plusX, by, BTN, "+", hotPlus);
drawBtn(ctx, progX, by, progW, "PROG ▸", hotProg);
}
Comments (0)
Log in to comment.