18

Turing Machine Simulator: Busy Beaver

Tap PROG to switch machines, +/− to change speed

A turing machine simulator running the 3-state and 4-state busy beaver champions plus a binary counter on an infinite tape. Each step applies one transition rule like — shown live in the HUD with the step and ones count — while the head glides cell to cell and the tape scrolls to follow it. Watch the busy beavers churn until they HALT with their record scores (6 ones in 14 steps, then 13 ones in 107 steps), and watch carries ripple through the binary counter. Tap PROG to cycle machines and +/− to throttle the 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.