목차(클릭하세요)
 우리는 왜 디지털 논리회로 실습을 하였을까?
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}    <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
복사
