9
Logic Gate Simulator: Build Circuits Live
Tap the input switches; press NEXT to change circuits
idle
152 lines · vanilla
view source
const DELAY = 0.32;
let CIRCUITS, ci, sw, shown, targ, waveT, lastUser, lastAuto;
let nodes, outs, depths, wires, gdefs, nIn;
let W = 0, H = 0;
function makeCircuits() {
return [
{ name: "HALF ADDER", nIn: 2, inL: ["A", "B"],
inPos: [[0.10, 0.28], [0.10, 0.72]],
gates: [["XOR", 0.50, 0.28, 0, 1], ["AND", 0.50, 0.72, 0, 1]],
outs: [{ x: 0.88, y: 0.28, src: 2, label: "SUM" }, { x: 0.88, y: 0.72, src: 3, label: "CARRY" }] },
{ name: "(A AND B) OR C", nIn: 3, inL: ["A", "B", "C"],
inPos: [[0.09, 0.18], [0.09, 0.48], [0.09, 0.80]],
gates: [["AND", 0.44, 0.30, 0, 1], ["OR", 0.70, 0.52, 3, 2]],
outs: [{ x: 0.91, y: 0.52, src: 4, label: "Q" }] },
{ name: "XOR FROM 4 NANDS", nIn: 2, inL: ["A", "B"],
inPos: [[0.08, 0.24], [0.08, 0.76]],
gates: [["NAND", 0.34, 0.50, 0, 1], ["NAND", 0.56, 0.26, 0, 2], ["NAND", 0.56, 0.74, 1, 2], ["NAND", 0.78, 0.50, 3, 4]],
outs: [{ x: 0.93, y: 0.50, src: 5, label: "Q" }] },
{ name: "(A OR B) AND NOT C", nIn: 3, inL: ["A", "B", "C"],
inPos: [[0.09, 0.16], [0.09, 0.42], [0.09, 0.80]],
gates: [["OR", 0.42, 0.28, 0, 1], ["NOT", 0.42, 0.80, 2, -1], ["AND", 0.71, 0.52, 3, 4]],
outs: [{ x: 0.93, y: 0.52, src: 5, label: "Q" }] },
];
}
function evalGate(t, a, b) {
if (t === "AND") return a & b;
if (t === "OR") return a | b;
if (t === "NOT") return 1 - a;
if (t === "XOR") return a ^ b;
return 1 - (a & b); // NAND
}
function loadCircuit(k, time) {
ci = k;
const c = CIRCUITS[k];
nIn = c.nIn; gdefs = c.gates; outs = c.outs;
nodes = []; depths = []; wires = [];
for (let i = 0; i < nIn; i++) { nodes.push({ x: c.inPos[i][0], y: c.inPos[i][1] }); depths.push(0); }
for (let g = 0; g < gdefs.length; g++) {
const gt = gdefs[g];
nodes.push({ x: gt[1], y: gt[2] });
depths.push(1 + Math.max(depths[gt[3]], gt[4] >= 0 ? depths[gt[4]] : 0));
wires.push({ a: gt[3], bx: gt[1], by: gt[2], out: false, port: gt[4] >= 0 ? -1 : 0 });
if (gt[4] >= 0) wires.push({ a: gt[4], bx: gt[1], by: gt[2], out: false, port: 1 });
}
for (const o of outs) wires.push({ a: o.src, bx: o.x, by: o.y, out: true, port: 0 });
sw = new Array(nIn).fill(0); sw[0] = 1;
targ = new Array(nodes.length).fill(0);
shown = new Array(nodes.length).fill(0);
waveT = time;
}
function wirePath(ctx, x1, y1, x2, y2, p) {
const mx = (x1 + x2) / 2;
ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(mx, y1); ctx.lineTo(mx, y2); ctx.lineTo(x2, y2); ctx.stroke();
const l1 = Math.abs(mx - x1), l2 = Math.abs(y2 - y1), l3 = Math.abs(x2 - mx);
const L = l1 + l2 + l3 || 1; const d = p * L; let qx, qy;
if (d <= l1) { qx = x1 + Math.sign(mx - x1) * d; qy = y1; }
else if (d <= l1 + l2) { qx = mx; qy = y1 + Math.sign(y2 - y1) * (d - l1); }
else { qx = mx + Math.sign(x2 - mx) * (d - l1 - l2); qy = y2; }
ctx.beginPath(); ctx.arc(qx, qy, 3.5, 0, Math.PI * 2); ctx.fill();
}
function init({ width, height }) {
W = width; H = height;
CIRCUITS = makeCircuits();
lastUser = 0; lastAuto = 0;
loadCircuit(0, 0);
}
function tick({ ctx, time, width, height, input }) {
if (width !== W || height !== H) { W = width; H = height; }
const pax = 14, pay = 60, paw = W - 28, pah = H - 60 - 74;
const PX = (i) => pax + nodes[i].x * paw, PY = (i) => pay + nodes[i].y * pah;
const gw = Math.min(60, paw * 0.18), gh = 34, SW = 48;
const nbw = 104, nbx = W - nbw - 12, nby = H - 44 - 12;
for (const c of input.consumeClicks()) {
let hit = false;
for (let i = 0; i < nIn; i++) {
if (Math.abs(c.x - PX(i)) <= SW / 2 + 8 && Math.abs(c.y - PY(i)) <= SW / 2 + 8) {
sw[i] ^= 1; waveT = time; lastUser = time; hit = true;
}
}
if (!hit && c.x >= nbx && c.x <= nbx + nbw && c.y >= nby && c.y <= nby + 44) {
loadCircuit((ci + 1) % CIRCUITS.length, time); lastUser = time;
}
}
if (time - lastUser > 5 && time - lastAuto > 2.8) {
sw[(Math.random() * nIn) | 0] ^= 1; waveT = time; lastAuto = time;
}
for (let i = 0; i < nIn; i++) targ[i] = sw[i];
for (let g = 0; g < gdefs.length; g++) {
const gt = gdefs[g];
targ[nIn + g] = evalGate(gt[0], targ[gt[3]], gt[4] >= 0 ? targ[gt[4]] : 0);
}
for (let i = 0; i < targ.length; i++) if (time - waveT >= depths[i] * DELAY) shown[i] = targ[i];
ctx.fillStyle = "#0c0f16"; ctx.fillRect(0, 0, W, H);
ctx.lineWidth = 2;
for (let j = 0; j < wires.length; j++) {
const w = wires[j], on = shown[w.a];
const x1 = PX(w.a) + (w.a < nIn ? SW / 2 : gw / 2), y1 = PY(w.a);
const x2 = pax + w.bx * paw - (w.out ? 20 : gw / 2);
const y2 = pay + w.by * pah + (w.port < 0 ? -8 : w.port > 0 ? 8 : 0);
ctx.strokeStyle = on ? "#3dff7a" : "#3a4150";
ctx.fillStyle = on ? "#c8ffdd" : "#596275";
wirePath(ctx, x1, y1, x2, y2, (time * 0.8 + j * 0.19) % 1);
}
ctx.textAlign = "center"; ctx.textBaseline = "middle";
for (let g = 0; g < gdefs.length; g++) {
const i = nIn + g, x = PX(i), y = PY(i), on = shown[i];
ctx.fillStyle = "#151b28"; ctx.fillRect(x - gw / 2, y - gh / 2, gw, gh);
ctx.lineWidth = 2; ctx.strokeStyle = on ? "#3dff7a" : "#5a6377";
ctx.strokeRect(x - gw / 2, y - gh / 2, gw, gh);
ctx.fillStyle = on ? "#aaffc8" : "#9aa3b5"; ctx.font = "bold 13px monospace";
ctx.fillText(gdefs[g][0], x, y);
}
for (let i = 0; i < nIn; i++) {
const x = PX(i), y = PY(i), on = sw[i];
ctx.fillStyle = on ? "#1f8f4a" : "#222835";
ctx.fillRect(x - SW / 2, y - SW / 2, SW, SW);
ctx.lineWidth = 2; ctx.strokeStyle = on ? "#5dffa0" : "#4a5366";
ctx.strokeRect(x - SW / 2, y - SW / 2, SW, SW);
ctx.fillStyle = "#fff"; ctx.font = "bold 20px monospace";
ctx.fillText(String(on), x, y + 6);
ctx.font = "bold 11px monospace"; ctx.fillStyle = on ? "#d9ffe9" : "#8b93a6";
ctx.fillText(CIRCUITS[ci].inL[i], x, y - SW / 2 + 10);
}
for (const o of outs) {
const x = pax + o.x * paw, y = pay + o.y * pah, on = shown[o.src];
if (on) { ctx.fillStyle = "rgba(255,211,77,0.25)"; ctx.beginPath(); ctx.arc(x, y, 26, 0, Math.PI * 2); ctx.fill(); }
ctx.fillStyle = on ? "#ffd34d" : "#2a3144";
ctx.beginPath(); ctx.arc(x, y, 15, 0, Math.PI * 2); ctx.fill();
ctx.lineWidth = 2; ctx.strokeStyle = on ? "#ffe9a8" : "#4a5366"; ctx.stroke();
ctx.fillStyle = "#cfd6e4"; ctx.font = "bold 11px monospace";
ctx.fillText(o.label, x, y + 28);
}
ctx.fillStyle = "rgba(0,0,0,0.55)"; ctx.fillRect(0, 0, W, 52);
ctx.textAlign = "left"; ctx.textBaseline = "alphabetic";
ctx.fillStyle = "#9fb4ff"; ctx.font = "bold 13px monospace";
ctx.fillText(CIRCUITS[ci].name + " (" + (ci + 1) + "/" + CIRCUITS.length + ")", 12, 20);
let row = "";
for (let i = 0; i < nIn; i++) row += CIRCUITS[ci].inL[i] + "=" + sw[i] + " ";
row += "→ ";
for (const o of outs) row += o.label + "=" + shown[o.src] + " ";
ctx.fillStyle = "#fff"; ctx.font = "12px monospace";
ctx.fillText(row, 12, 40);
ctx.fillStyle = "rgba(0,0,0,0.65)"; ctx.fillRect(nbx, nby, nbw, 44);
ctx.strokeStyle = "rgba(255,255,255,0.45)"; ctx.lineWidth = 1;
ctx.strokeRect(nbx + 0.5, nby + 0.5, nbw - 1, 43);
ctx.fillStyle = "#fff"; ctx.font = "bold 14px monospace";
ctx.textAlign = "center"; ctx.textBaseline = "middle";
ctx.fillText("NEXT ▸", nbx + nbw / 2, nby + 22);
ctx.textAlign = "left"; ctx.textBaseline = "alphabetic";
ctx.fillStyle = "#7d8699"; ctx.font = "11px monospace";
ctx.fillText("tap the switches to toggle inputs", 12, H - 16);
}
Comments (0)
Log in to comment.