Search
Duplicate

퍼셉트론으로 논리게이트 시뮬레이션

목차(클릭하세요)

우리는 왜 디지털 논리회로 실습을 하였을까?

1. 컴퓨터는 결국은 0과 1의 조합 - 양자컴퓨터 제외 - 모든 컴퓨터, 스마트폰, 심지어 인공지능 칩도 결국 ‘0’과 ‘1’로 작동함. - 디지털 논리회로는 이 0과 1을 계산하는 컴퓨터의 두뇌 회로 설계도임. 2. 컴퓨터의 동작은 대부분 논리 회로로 표현가능 - 예를 들어, "체력이 0이고 적이 가까이 있으면 도망가라!"는 것도 논리 회로로 표현 가능함
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>인터랙티브 NAND 게이트 시뮬레이터</title> <script src="https://cdn.tailwindcss.com"></script> <style> body { font-family: 'Inter', sans-serif; } .truth-table { border-collapse: collapse; margin-top: 0.5rem; width: 100%; box-shadow: 0 2px 4px rgba(0,0,0,0.1); font-size: 0.75rem; } .truth-table th, .truth-table td { border: 1px solid #CBD5E0; padding: 0.25rem 0.5rem; text-align: center; } .truth-table th { background-color: #E2E8F0; font-weight: bold; } .truth-table tr:nth-child(even) { background-color: #F7FAFC; } .highlight { background-color: #FEFCBF; padding: 1px 3px; border-radius: 3px; font-weight: bold; } .circuit-svg { width: 100%; height: 100%; min-height: 200px; border: none; border-radius: 0.375rem; background-color: #FAFAFA; } .gate-body { fill: #EBF8FF; stroke: #2B6CB0; stroke-width: 1.5; } .output-bubble { fill: #EBF8FF; stroke: #2B6CB0; stroke-width: 1.5; } .wire { stroke: #4A5568; stroke-width: 1.5; fill: none; } .wire-active { stroke: #DC2626; stroke-width: 2.5; fill: none; } .wire-inactive { stroke: #9CA3AF; stroke-width: 1.5; fill: none; } .label-text { font-size: 12px; fill: #2D3748; dominant-baseline: middle; } .signal-value { font-size: 10px; font-weight: bold; fill: #DC2626; dominant-baseline: middle; } .input-switch { cursor: pointer; transition: all 0.2s ease; } .input-switch:hover { transform: scale(1.1); } .switch-on { fill: #10B981; stroke: #047857; } .switch-off { fill: #EF4444; stroke: #B91C1C; } .gate-highlight { filter: drop-shadow(0 0 8px #FBBF24); } @keyframes pulse-signal { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } } .signal-pulse { animation: pulse-signal 1s ease-in-out infinite; } </style> </head> <body class="bg-gray-100 text-gray-800 min-h-screen flex flex-col items-center p-4 sm:p-6"> <div class="w-full max-w-6xl bg-white p-6 sm:p-8 rounded-xl shadow-2xl"> <header class="mb-6 text-center"> <h1 class="text-2xl sm:text-3xl font-bold text-blue-700">🔧 인터랙티브 NAND 게이트 시뮬레이터</h1> <p class="text-gray-600 mt-1 text-sm sm:text-base">입력값을 변경하고 신호 흐름을 실시간으로 관찰하세요!</p> </header> <main> <!-- 상단 3: 게이트 선택 / 입력값 제어 / 현재 신호값 + 완전 진리표 --> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6"> <!-- 1. 게이트 선택 --> <div> <label for="gateSelector" class="block text-base font-medium text-gray-700 mb-1">논리 게이트 선택:</label> <select id="gateSelector" class="w-full p-2 border border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition duration-150 text-sm sm:text-base"> <option value="NAND" selected>NAND 게이트 (기본)</option> <option value="NOT">NOT 게이트</option> <option value="AND">AND 게이트</option> <option value="OR">OR 게이트</option> <option value="XOR">XOR 게이트</option> <option value="NOR">NOR 게이트</option> <option value="XNOR">XNOR 게이트</option> </select> </div> <!-- 2. 입력값 제어 --> <div id="inputControls" class="space-y-2"> <label class="block text-base font-medium text-gray-700">입력값 제어:</label> <!-- 동적으로 생성됨 --> </div> <!-- 3. 현재 신호값 + 완전 진리표(같은 카드 안에 배치) --> <div class="bg-blue-50 p-3 rounded-lg"> <h3 class="text-sm font-semibold text-blue-700 mb-2">📊 현재 신호값</h3> <div id="signalStatus" class="text-xs text-blue-600 mb-3"> <!-- 동적으로 생성됨 --> </div> <!-- 완전 진리표 영역을 '현재 신호값' (같은 열) 카드 안으로 이동 --> <div id="truthTableArea" class="p-3 sm:p-4 bg-gray-50 rounded-lg border border-gray-200"> <!-- 동적으로 생성됨 --> </div> </div> </div> <!-- 회로 시각화 --> <div id="visualizationArea" class="mb-6"> <svg id="circuitDiagram" class="circuit-svg"></svg> </div> <p id="circuitDescription" class="text-xs text-gray-600 mb-4 text-center px-2"></p> </main> <footer class="mt-4 text-center text-xs text-gray-500"> <p>Made By Yangphago (feat. Claude) 🤖</p> </footer> </div> <script> const gateSelector = document.getElementById('gateSelector'); const circuitDiagramSvg = document.getElementById('circuitDiagram'); const circuitDescriptionP = document.getElementById('circuitDescription'); const truthTableArea = document.getElementById('truthTableArea'); const inputControls = document.getElementById('inputControls'); const signalStatus = document.getElementById('signalStatus'); const quickTruthTable = document.getElementById('quickTruthTable'); // (옵션) 없음이면 무시됨 const svgNS = "http://www.w3.org/2000/svg"; // 현재 입력값 상태 let currentInputs = {}; let currentSignals = {}; let currentConfig = null; // 게이트 심볼 크기 및 간격 정의 const GATE_WIDTH = 60; const GATE_HEIGHT = 40; const H_SPACING = 80; const V_SPACING = 30; const INPUT_MARGIN = 30; const gateImplementations = { NOT: (inputA = 'A') => ({ description: `NOT ${inputA} = ${inputA} <span class="highlight">NAND</span> ${inputA}`, gates: [ { id: 'g1', type: 'NAND', inputs: [inputA, inputA], output: 'Q_NOT' } ], circuitInputs: [inputA], circuitOutput: 'Q_NOT', finalOutputName: `Q (NOT ${inputA})` }), AND: (inputA = 'A', inputB = 'B') => ({ description: `AND(${inputA}, ${inputB}) = (${inputA} <span class="highlight">NAND</span> ${inputB}) <span class="highlight">NAND</span> (${inputA} <span class="highlight">NAND</span> ${inputB})`, gates: [ { id: 'g1', type: 'NAND', inputs: [inputA, inputB], output: 'N1' }, { id: 'g2', type: 'NAND', inputs: ['N1', 'N1'], output: 'Q_AND' } ], circuitInputs: [inputA, inputB], circuitOutput: 'Q_AND', finalOutputName: `Q (AND ${inputA},${inputB})` }), OR: (inputA = 'A', inputB = 'B') => ({ description: `OR(${inputA}, ${inputB}) = (${inputA} <span class="highlight">NAND</span> ${inputA}) <span class="highlight">NAND</span> (${inputB} <span class="highlight">NAND</span> ${inputB})`, gates: [ { id: 'g1', type: 'NAND', inputs: [inputA, inputA], output: 'N1' }, { id: 'g2', type: 'NAND', inputs: [inputB, inputB], output: 'N2' }, { id: 'g3', type: 'NAND', inputs: ['N1', 'N2'], output: 'Q_OR' } ], circuitInputs: [inputA, inputB], circuitOutput: 'Q_OR', finalOutputName: `Q (OR ${inputA},${inputB})` }), NAND: (inputA = 'A', inputB = 'B') => ({ description: `NAND(${inputA}, ${inputB}) 는 기본 게이트입니다.`, gates: [ { id: 'g1', type: 'NAND', inputs: [inputA, inputB], output: 'Q_NAND' } ], circuitInputs: [inputA, inputB], circuitOutput: 'Q_NAND', finalOutputName: `Q (NAND ${inputA},${inputB})` }), XOR: (inputA = 'A', inputB = 'B') => ({ description: `XOR(${inputA}, ${inputB}) = (A <span class="highlight">NAND</span> (A <span class="highlight">NAND</span> B)) <span class="highlight">NAND</span> (B <span class="highlight">NAND</span> (A <span class="highlight">NAND</span> B))`, gates: [ { id: 'g1', type: 'NAND', inputs: [inputA, inputB], output: 'N1' }, { id: 'g2', type: 'NAND', inputs: [inputA, 'N1'], output: 'N2' }, { id: 'g3', type: 'NAND', inputs: [inputB, 'N1'], output: 'N3' }, { id: 'g4', type: 'NAND', inputs: ['N2', 'N3'], output: 'Q_XOR' } ], circuitInputs: [inputA, inputB], circuitOutput: 'Q_XOR', finalOutputName: `Q (XOR ${inputA},${inputB})` }), NOR: (inputA = 'A', inputB = 'B') => ({ description: `NOR(${inputA}, ${inputB}) = ((A <span class="highlight">NAND</span> A) <span class="highlight">NAND</span> (B <span class="highlight">NAND</span> B)) <span class="highlight">NAND</span> ((A <span class="highlight">NAND</span> A) <span class="highlight">NAND</span> (B <span class="highlight">NAND</span> B))`, gates: [ { id: 'g1', type: 'NAND', inputs: [inputA, inputA], output: 'N1' }, { id: 'g2', type: 'NAND', inputs: [inputB, inputB], output: 'N2' }, { id: 'g3', type: 'NAND', inputs: ['N1', 'N2'], output: 'N3' }, { id: 'g4', type: 'NAND', inputs: ['N3', 'N3'], output: 'Q_NOR' } ], circuitInputs: [inputA, inputB], circuitOutput: 'Q_NOR', finalOutputName: `Q (NOR ${inputA},${inputB})` }), XNOR: (inputA = 'A', inputB = 'B') => ({ description: `XNOR(${inputA}, ${inputB}) = 최종 출력은 XOR의 반대입니다.`, gates: [ { id: 'g1', type: 'NAND', inputs: [inputA, inputB], output: 'N1' }, { id: 'g2', type: 'NAND', inputs: [inputA, 'N1'], output: 'N2' }, { id: 'g3', type: 'NAND', inputs: [inputB, 'N1'], output: 'N3' }, { id: 'g4', type: 'NAND', inputs: ['N2', 'N3'], output: 'N4_XOR' }, { id: 'g5', type: 'NAND', inputs: ['N4_XOR', 'N4_XOR'], output: 'Q_XNOR' } ], circuitInputs: [inputA, inputB], circuitOutput: 'Q_XNOR', finalOutputName: `Q (XNOR ${inputA},${inputB})` }) }; function simulateCircuit(config, inputs) { let signalValues = { ...inputs }; let maxIterations = config.gates.length * 2; let iterations = 0; let allGatesComputed = false; while(!allGatesComputed && iterations < maxIterations) { allGatesComputed = true; config.gates.forEach(gate => { if (signalValues[gate.output] === undefined) { let inputsReady = true; const gateInputVals = gate.inputs.map(inputSignalName => { if (signalValues[inputSignalName] === undefined) inputsReady = false; return signalValues[inputSignalName]; }); if (inputsReady) { const a = gateInputVals[0]; const b = gateInputVals.length > 1 ? gateInputVals[1] : gateInputVals[0]; // NAND 연산 signalValues[gate.output] = (a === 1 && b === 1) ? 0 : 1; // ★ 핵심 NAND } else { allGatesComputed = false; } } }); iterations++; } return signalValues; } function createInputControls(config) { const controlsContainer = inputControls.querySelector('div') || document.createElement('div'); controlsContainer.className = 'flex gap-3'; controlsContainer.innerHTML = ''; config.circuitInputs.forEach(inputName => { const controlDiv = document.createElement('div'); controlDiv.className = 'flex items-center gap-2'; const label = document.createElement('span'); label.textContent = `${inputName}:`; label.className = 'text-sm font-medium'; const toggleButton = document.createElement('button'); toggleButton.className = 'px-4 py-2 rounded-md text-sm font-bold transition-all duration-200'; toggleButton.onclick = () => toggleInput(inputName); controlDiv.appendChild(label); controlDiv.appendChild(toggleButton); controlsContainer.appendChild(controlDiv); if (currentInputs[inputName] === undefined) currentInputs[inputName] = 0; }); if (!inputControls.querySelector('div')) inputControls.appendChild(controlsContainer); updateInputButtons(); } function updateInputButtons() { const controlsContainer = inputControls.querySelector('div'); if (!controlsContainer) return; const buttons = controlsContainer.querySelectorAll('button'); currentConfig.circuitInputs.forEach((inputName, index) => { const button = buttons[index]; if (button) { const value = currentInputs[inputName]; button.textContent = value === 1 ? '1' : '0'; button.className = `px-4 py-2 rounded-md text-sm font-bold transition-all duration-200 ${value === 1 ? 'bg-green-500 text-white' : 'bg-red-500 text-white'}`; } }); } function toggleInput(inputName) { currentInputs[inputName] = currentInputs[inputName] === 1 ? 0 : 1; updateInputButtons(); updateRealTimeSimulation(); } function updateRealTimeSimulation() { if (!currentConfig) return; currentSignals = simulateCircuit(currentConfig, currentInputs); updateSignalStatus(); updateCircuitVisualization(); updateTruthTable(gateSelector.value); } function updateSignalStatus() { if (!signalStatus || !currentConfig) return; signalStatus.innerHTML = ''; const statusLine = document.createElement('div'); const inputString = currentConfig.circuitInputs.map(name => `${name}=${currentSignals[name] || 0}`).join(', '); const outputValue = currentSignals[currentConfig.circuitOutput] || 0; statusLine.innerHTML = `<strong>입력:</strong> ${inputString} &nbsp;&nbsp; <strong>출력:</strong> Q=${outputValue}`; signalStatus.appendChild(statusLine); } function updateQuickTruthTable() { if (!currentConfig || !quickTruthTable) return; quickTruthTable.innerHTML = ''; const currentRow = document.createElement('div'); currentRow.className = 'font-bold text-green-700'; const inputString = currentConfig.circuitInputs.map(name => `${name}=${currentSignals[name] || 0}`).join(', '); const outputValue = currentSignals[currentConfig.circuitOutput] || 0; currentRow.innerHTML = `${inputString} → Q=${outputValue}`; quickTruthTable.appendChild(currentRow); let exampleCount = 0; const numInputs = currentConfig.circuitInputs.length; const numRows = Math.pow(2, numInputs); for (let i = 0; i < numRows && exampleCount < 2; i++) { const testInputs = {}; let isDifferent = false; for (let j = 0; j < numInputs; j++) { const inputName = currentConfig.circuitInputs[j]; const val = (i >> (numInputs - 1 - j)) & 1; testInputs[inputName] = val; if (val !== currentInputs[inputName]) isDifferent = true; } if (isDifferent) { const testSignals = simulateCircuit(currentConfig, testInputs); const testRow = document.createElement('div'); testRow.className = 'text-gray-500 text-xs'; const testInputString = currentConfig.circuitInputs.map(name => `${name}=${testInputs[name]}`).join(', '); const testOutputValue = testSignals[currentConfig.circuitOutput] || 0; testRow.innerHTML = `${testInputString} → Q=${testOutputValue}`; quickTruthTable.appendChild(testRow); exampleCount++; } } } function createNandSymbolElement(gateX, gateY, gateId) { const group = document.createElementNS(svgNS, "g"); group.setAttribute("id", `gate-${gateId}`); const path = document.createElementNS(svgNS, "path"); path.setAttribute("d", `M0 0 H${GATE_WIDTH*0.6} C${GATE_WIDTH*0.9} 0, ${GATE_WIDTH*0.9} ${GATE_HEIGHT}, ${GATE_WIDTH*0.6} ${GATE_HEIGHT} H0 Z`); path.setAttribute("class", "gate-body"); group.appendChild(path); const circle = document.createElementNS(svgNS, "circle"); circle.setAttribute("cx", GATE_WIDTH*0.9 + GATE_HEIGHT*0.1); circle.setAttribute("cy", GATE_HEIGHT / 2); circle.setAttribute("r", GATE_HEIGHT * 0.1); circle.setAttribute("class", "output-bubble"); group.appendChild(circle); group.setAttribute("transform", `translate(${gateX}, ${gateY})`); return group; } function drawLine(x1, y1, x2, y2, className = "wire", lineId = null) { const line = document.createElementNS(svgNS, "line"); line.setAttribute("x1", x1); line.setAttribute("y1", y1); line.setAttribute("x2", x2); line.setAttribute("y2", y2); line.setAttribute("class", className); if (lineId) line.setAttribute("id", lineId); return line; } function drawText(x, y, textContent, anchor = "middle", className = "label-text") { const text = document.createElementNS(svgNS, "text"); text.setAttribute("x", x); text.setAttribute("y", y); text.setAttribute("class", className); text.setAttribute("text-anchor", anchor); text.textContent = textContent; return text; } function drawInputSwitch(x, y, inputName, value) { const group = document.createElementNS(svgNS, "g"); group.setAttribute("class", "input-switch"); group.setAttribute("id", `switch-${inputName}`); group.onclick = () => toggleInput(inputName); const rect = document.createElementNS(svgNS, "rect"); rect.setAttribute("x", x - 8); rect.setAttribute("y", y - 6); rect.setAttribute("width", 16); rect.setAttribute("height", 12); rect.setAttribute("rx", 2); rect.setAttribute("class", value === 1 ? "switch-on" : "switch-off"); const text = document.createElementNS(svgNS, "text"); text.setAttribute("x", x); text.setAttribute("y", y); text.setAttribute("class", "label-text"); text.setAttribute("text-anchor", "middle"); text.setAttribute("font-size", "8"); text.setAttribute("fill", "white"); text.textContent = value; group.appendChild(rect); group.appendChild(text); return group; } function updateVisualization(gateType) { circuitDiagramSvg.innerHTML = ''; currentConfig = gateImplementations[gateType](); circuitDescriptionP.innerHTML = `<strong>수식:</strong> ${currentConfig.description}`; const nodePositions = {}; let maxGatesInLevel = 0; const levels = {}; const gateObjects = {}; currentConfig.gates.forEach(g => gateObjects[g.id] = {...g, level: -1, connections: 0}); // 게이트 레벨링 let currentLevel = 0; let gatesProcessed = 0; while(gatesProcessed < currentConfig.gates.length) { levels[currentLevel] = []; currentConfig.gates.forEach(gate => { if (gateObjects[gate.id].level !== -1) return; let allInputsReady = true; gate.inputs.forEach(inputSignal => { if (!currentConfig.circuitInputs.includes(inputSignal)) { const sourceGate = currentConfig.gates.find(g => g.output === inputSignal); if (!sourceGate || gateObjects[sourceGate.id].level === -1 || gateObjects[sourceGate.id].level >= currentLevel) { allInputsReady = false; } } }); if (allInputsReady) { gateObjects[gate.id].level = currentLevel; levels[currentLevel].push(gateObjects[gate.id]); gatesProcessed++; } }); if (levels[currentLevel].length > maxGatesInLevel) maxGatesInLevel = levels[currentLevel].length; if(levels[currentLevel].length === 0 && gatesProcessed < currentConfig.gates.length) { currentConfig.gates.filter(g => gateObjects[g.id].level === -1).forEach(g => { levels[currentLevel].push(g); gateObjects[g.id].level = currentLevel; gatesProcessed++; }); } currentLevel++; } const numLevels = Object.keys(levels).length; const totalHeight = Math.max(maxGatesInLevel * (GATE_HEIGHT + V_SPACING) + V_SPACING, 120); const totalWidth = INPUT_MARGIN + numLevels * (GATE_WIDTH + H_SPACING) + H_SPACING; circuitDiagramSvg.setAttribute("viewBox", `0 0 ${totalWidth} ${totalHeight}`); // 회로 입력 위치 및 스위치 const inputYStep = totalHeight / (currentConfig.circuitInputs.length + 1); currentConfig.circuitInputs.forEach((inputName, index) => { const x = INPUT_MARGIN / 2; const y = inputYStep * (index + 1); nodePositions[inputName] = { x: x + 15, y: y }; const switchElement = drawInputSwitch(x - 5, y, inputName, currentInputs[inputName] || 0); circuitDiagramSvg.appendChild(switchElement); circuitDiagramSvg.appendChild(drawText(x - 20, y, inputName, "end")); circuitDiagramSvg.appendChild(drawLine(x + 3, y, x + 15, y, "wire", `input-wire-${inputName}`)); }); // 게이트 그리기 for (let l = 0; l < numLevels; l++) { const gatesInLevel = levels[l]; const levelYStep = totalHeight / (gatesInLevel.length + 1); gatesInLevel.forEach((gate, index) => { const gateX = INPUT_MARGIN + l * (GATE_WIDTH + H_SPACING); const gateY = levelYStep * (index + 1) - GATE_HEIGHT / 2; const gateSymbol = createNandSymbolElement(gateX, gateY, gate.id); circuitDiagramSvg.appendChild(gateSymbol); gateObjects[gate.id].x = gateX; gateObjects[gate.id].y = gateY; nodePositions[gate.output] = { x: gateX + GATE_WIDTH*0.9 + GATE_HEIGHT*0.1 + 2, y: gateY + GATE_HEIGHT / 2 }; }); } // 선 연결 for (let l = 0; l < numLevels; l++) { const gatesInLevel = levels[l]; gatesInLevel.forEach((gate) => { const targetGateX = gateObjects[gate.id].x; const targetGateY = gateObjects[gate.id].y; gate.inputs.forEach((inputSignal, inputIndex) => { const sourcePos = nodePositions[inputSignal]; if (!sourcePos) return; const targetInputY = targetGateY + (gate.inputs.length === 1 ? GATE_HEIGHT / 2 : (inputIndex === 0 ? GATE_HEIGHT * 0.25 : GATE_HEIGHT * 0.75)); const targetInputX = targetGateX; const wireId = `wire-${inputSignal}-to-${gate.id}-${inputIndex}`; const midX = sourcePos.x + (targetInputX - sourcePos.x) / 2; if (Math.abs(sourcePos.y - targetInputY) < 5 && sourcePos.x < targetInputX) { circuitDiagramSvg.appendChild(drawLine(sourcePos.x, sourcePos.y, targetInputX, targetInputY, "wire", wireId)); } else { const group = document.createElementNS(svgNS, "g"); group.setAttribute("id", wireId); group.appendChild(drawLine(sourcePos.x, sourcePos.y, midX, sourcePos.y, "wire")); group.appendChild(drawLine(midX, sourcePos.y, midX, targetInputY, "wire")); group.appendChild(drawLine(midX, targetInputY, targetInputX, targetInputY, "wire")); circuitDiagramSvg.appendChild(group); } }); }); } // 최종 출력 const finalOutputSignal = currentConfig.circuitOutput; const finalOutputSourcePos = nodePositions[finalOutputSignal]; if (finalOutputSourcePos) { const outputX = finalOutputSourcePos.x + H_SPACING / 2; const outputY = finalOutputSourcePos.y; const outputWire = drawLine(finalOutputSourcePos.x, finalOutputSourcePos.y, outputX, outputY, "wire", "output-wire"); outputWire.style.strokeWidth = "2.5"; circuitDiagramSvg.appendChild(outputWire); circuitDiagramSvg.appendChild(drawText(outputX + 5, outputY, currentConfig.finalOutputName, "start")); } // 입력 컨트롤 생성 및 초기 시뮬레이션 createInputControls(currentConfig); updateRealTimeSimulation(); } function updateCircuitVisualization() { if (!currentConfig || !currentSignals) return; // 입력 스위치/와이어 currentConfig.circuitInputs.forEach(inputName => { const switchElement = document.getElementById(`switch-${inputName}`); if (switchElement) { const rect = switchElement.querySelector('rect'); const text = switchElement.querySelector('text'); const value = currentSignals[inputName]; rect.setAttribute("class", value === 1 ? "switch-on" : "switch-off"); text.textContent = value; const inputWire = document.getElementById(`input-wire-${inputName}`); if (inputWire) inputWire.setAttribute("class", value === 1 ? "wire-active" : "wire-inactive"); } }); // 게이트/와이어 상태 currentConfig.gates.forEach(gate => { const gateElement = document.getElementById(`gate-${gate.id}`); const gateOutputValue = currentSignals[gate.output]; const allInputsActive = gate.inputs.every(input => currentSignals[input] === 1); if (gateElement) { if (allInputsActive) gateElement.setAttribute("class", "gate-highlight"); else gateElement.removeAttribute("class"); } gate.inputs.forEach((inputSignal, inputIndex) => { const wireId = `wire-${inputSignal}-to-${gate.id}-${inputIndex}`; const wireElement = document.getElementById(wireId); if (wireElement) { const inputValue = currentSignals[inputSignal]; const wireClass = inputValue === 1 ? "wire-active" : "wire-inactive"; if (wireElement.tagName === 'g') { wireElement.querySelectorAll('line').forEach(line => line.setAttribute("class", wireClass)); } else { wireElement.setAttribute("class", wireClass); } } }); const outputSignal = gate.output; currentConfig.gates.forEach(targetGate => { targetGate.inputs.forEach((input, inputIndex) => { if (input === outputSignal) { const wireId = `wire-${outputSignal}-to-${targetGate.id}-${inputIndex}`; const wireElement = document.getElementById(wireId); if (wireElement) { const wireClass = gateOutputValue === 1 ? "wire-active" : "wire-inactive"; if (wireElement.tagName === 'g') { wireElement.querySelectorAll('line').forEach(line => line.setAttribute("class", wireClass)); } else { wireElement.setAttribute("class", wireClass); } } } }); }); }); // 최종 출력선 const outputWire = document.getElementById("output-wire"); if (outputWire) { const finalOutputValue = currentSignals[currentConfig.circuitOutput]; outputWire.setAttribute("class", finalOutputValue === 1 ? "wire-active" : "wire-inactive"); } // 신호 라벨 addSignalValueLabels(); } function addSignalValueLabels() { circuitDiagramSvg.querySelectorAll('.signal-value').forEach(label => label.remove()); currentConfig.gates.forEach(gate => { const gateElement = document.getElementById(`gate-${gate.id}`); if (gateElement) { const transform = gateElement.getAttribute('transform'); const match = transform.match(/translate\(([^,]+),([^)]+)\)/); if (match) { const gateX = parseFloat(match[1]); const gateY = parseFloat(match[2]); const labelOutputValue = currentSignals[gate.output]; const labelX = gateX + GATE_WIDTH + 5; const labelY = gateY + GATE_HEIGHT / 2 - 8; const label = drawText(labelX, labelY, `${gate.output}=${labelOutputValue}`, "start", "signal-value"); if (labelOutputValue === 1) label.setAttribute("class", "signal-value signal-pulse"); circuitDiagramSvg.appendChild(label); } } }); } function updateTruthTable(gateType) { truthTableArea.innerHTML = ''; const config = gateImplementations[gateType](); const headerEl = document.createElement('h2'); headerEl.className = 'text-sm font-semibold text-gray-700 mb-2 text-center'; headerEl.textContent = `📋 완전 진리표`; truthTableArea.appendChild(headerEl); const table = document.createElement('table'); table.className = 'truth-table mx-auto'; table.setAttribute('id', 'truth-table'); const thead = table.createTHead(); const tbody = table.createTBody(); const headerRow = thead.insertRow(); config.circuitInputs.forEach(inputName => { const th = document.createElement('th'); th.textContent = inputName; headerRow.appendChild(th); }); const thOutput = document.createElement('th'); thOutput.textContent = 'Q'; headerRow.appendChild(thOutput); const numInputs = config.circuitInputs.length; const numRows = Math.pow(2, numInputs); for (let i = 0; i < numRows; i++) { const bodyRow = tbody.insertRow(); const currentInputValues = {}; for (let j = 0; j < numInputs; j++) { const inputName = config.circuitInputs[j]; const val = (i >> (numInputs - 1 - j)) & 1; currentInputValues[inputName] = val; const cell = bodyRow.insertCell(); cell.textContent = val; } const signalValues = simulateCircuit(config, currentInputValues); const finalOutputValue = signalValues[config.circuitOutput]; const cellOutput = bodyRow.insertCell(); cellOutput.textContent = (finalOutputValue !== undefined) ? finalOutputValue : '?'; let isCurrentRow = true; config.circuitInputs.forEach(inputName => { if (currentInputValues[inputName] !== currentInputs[inputName]) isCurrentRow = false; }); if (isCurrentRow) { bodyRow.style.backgroundColor = '#FEF3C7'; bodyRow.style.fontWeight = 'bold'; } } truthTableArea.appendChild(table); } // 이벤트 리스너 gateSelector.addEventListener('change', (event) => { const selectedGate = event.target.value; currentInputs = {}; // 입력 초기화 updateVisualization(selectedGate); updateTruthTable(selectedGate); }); // 초기 로드 updateVisualization('NAND'); updateTruthTable('NAND'); // 키보드 단축키 document.addEventListener('keydown', (event) => { if (!currentConfig) return; if (event.key === '1' && currentConfig.circuitInputs.includes('A')) toggleInput('A'); else if (event.key === '2' && currentConfig.circuitInputs.includes('B')) toggleInput('B'); else if (event.key === ' ') { event.preventDefault(); if (currentConfig.circuitInputs.length > 0) toggleInput(currentConfig.circuitInputs[0]); } }); </script> </body> </html>
JavaScript
복사