// =================================================================== // Build a two-level SOP circuit with correct positions and connections // - Inputs a,b,c,d // - NOTs only if needed by any term (literal '0') // - For each term: chain of 2-input ANDs (or direct if 1 literal) // - OR chain to combine all products to OUTPUT // - Handles edge cases: f=0 (no terms) and f=1 (terms includes '----') // =================================================================== function buildCircuitFromTerms(terms) { // Reset canvas/state svg.innerHTML = ''; components = []; connections = []; nextId = 0; // Small helpers function connect(fromId, fromPort, toId, toPort) { const start = getPointPos(fromId, fromPort); const end = getPointPos(toId, toPort); const line = document.createElementNS(svgNS, 'line'); line.setAttribute('x1', start.x); line.setAttribute('y1', start.y); line.setAttribute('x2', end.x); line.setAttribute('y2', end.y); line.setAttribute('stroke', '#2c3e50'); line.setAttribute('stroke-width', '2.8'); svg.insertBefore(line, svg.firstChild); connections.push({ from: { id: fromId, port: fromPort }, to: { id: toId, port: toPort }, line }); const target = components.find(c => c.id === toId); if (target) { if (!target.connectionsIn) target.connectionsIn = {}; target.connectionsIn[toPort] = { fromId, fromPort }; } } function addGateAt(type, x, y, label = '') { const id = nextId++; const g = createGateElement(type, id, x, y, label); svg.appendChild(g); components.push({ id, type, connectionsIn: {}, value: undefined, label }); makeDraggable(g); g.addEventListener('dblclick', editLabel); addDeleteListener(g); return id; } function addInputAt(x, y, value, label) { const id = nextId++; const g = createInputElement(id, x, y, value, label); svg.appendChild(g); components.push({ id, type: 'INPUT', value, connectionsIn: {}, label }); makeDraggable(g); g.addEventListener('click', toggleInput); g.addEventListener('dblclick', editLabel); addDeleteListener(g); return id; } function addOutputAt(x, y, label) { const id = nextId++; const g = createOutputElement(id, x, y, label); svg.appendChild(g); components.push({ id, type: 'OUTPUT', connectionsIn: {}, value: undefined, label }); makeDraggable(g); g.addEventListener('dblclick', editLabel); addDeleteListener(g); return id; } // Normalize and trivial cases if (!Array.isArray(terms)) terms = []; // Remove duplicates / ensure valid 4-chars of 0/1/- terms = Array.from(new Set(terms.filter(t => typeof t === 'string' && t.length === 4 && /^[01-]{4}$/.test(t)))); if (terms.length === 0) { // f = 0 → draw constant 0 → Output const const0 = addInputAt(100, 220, 0, '0'); const outId = addOutputAt(650, 220, 'F'); connect(const0, 'out', outId, 'in'); pushState(); if (simulating) propagate(); return; } if (terms.includes('----')) { // f = 1 → draw constant 1 → Output const const1 = addInputAt(100, 220, 1, '1'); const outId = addOutputAt(650, 220, 'F'); connect(const1, 'out', outId, 'in'); pushState(); if (simulating) propagate(); return; } // 1) Layout columns (UPDATED: widened gaps for OR and OUT to prevent collisions) const COL = { IN: 70, NOT: 180, AND_START: 300, AND_STEP: 90, OR: 600, OUT: 760 }; const ROW = { TOP: 90, STEP: 90 }; // Inputs a,b,c,d const inputY = [80, 165, 250, 335]; const inputIds = []; for (let i = 0; i < 4; i++) { inputIds.push(addInputAt(COL.IN, inputY[i], 0, specVarNames[i])); } // 2) NOT gates only if needed by any term having '0' on that variable // (UPDATED: Aligned perfectly horizontally with their respective inputs) const needNot = [0, 1, 2, 3].map(i => terms.some(t => t[i] === '0')); const notIds = [null, null, null, null]; for (let i = 0; i < 4; i++) { if (needNot[i]) { notIds[i] = addGateAt('NOT', COL.NOT, inputY[i], specVarNames[i] + "'"); // Wire input → NOT connect(inputIds[i], 'out', notIds[i], 'in'); } } // 3) Build each product term with a 2-input AND chain (if needed) function termLabelOf(t) { let s = ''; for (let i = 0; i < 4; i++) { if (t[i] === '1') s += specVarNames[i]; else if (t[i] === '0') s += specVarNames[i] + "'"; } return s || '1'; } const products = []; // [{id, port:'out', y}] terms.forEach((t, k) => { const y = ROW.TOP + k * ROW.STEP; // Gather literal sources (id of INPUT or NOT) const literals = []; for (let i = 0; i < 4; i++) { if (t[i] === '1') literals.push({ id: inputIds[i], port: 'out' }); else if (t[i] === '0') literals.push({ id: notIds[i], port: 'out' }); } if (literals.length === 0) return; // Handled by '----' earlier if (literals.length === 1) { // Single literal: direct feed products.push({ id: literals[0].id, port: literals[0].port, y }); } else { // Build a chain of ANDs at AND_START, AND_START+STEP, ... let current = literals[0]; for (let j = 1; j < literals.length; j++) { const isLast = (j === literals.length - 1); const label = isLast ? termLabelOf(t) : ''; const andId = addGateAt('AND', COL.AND_START + (j - 1) * COL.AND_STEP, y, label); connect(current.id, current.port, andId, 'in1'); connect(literals[j].id, literals[j].port, andId, 'in2'); current = { id: andId, port: 'out' }; } products.push({ id: current.id, port: current.port, y }); } }); // 4) Combine products with an OR chain let finalSource = null; if (products.length === 1) { finalSource = { id: products[0].id, port: products[0].port }; } else { let current = { id: products[0].id, port: products[0].port }; for (let i = 1; i < products.length; i++) { // Stack OR gates vertically const orY = ROW.TOP + (i - 1) * ROW.STEP; const orId = addGateAt('OR', COL.OR, orY, ''); connect(current.id, current.port, orId, 'in1'); connect(products[i].id, products[i].port, orId, 'in2'); current = { id: orId, port: 'out' }; } finalSource = current; } // 5) Output (UPDATED: Removed the Math.min boundary to allow output to drop lower) const avgY = (() => { if (products.length === 1) return products[0].y; const ys = products.map(p => p.y); return Math.round((Math.min(...ys) + Math.max(...ys)) / 2); })(); const outId = addOutputAt(COL.OUT, Math.max(100, avgY), 'F'); connect(finalSource.id, finalSource.port, outId, 'in'); // 6) Dynamic SVG resize: Ensure the drawing bounds stretch down if there are many products const requiredHeight = Math.max(600, (terms.length * ROW.STEP) + 200); svg.setAttribute('viewBox', `0 0 850 ${requiredHeight}`); // Finish pushState(); if (simulating) propagate(); }