">
<head>
<!-- HTML Meta Tags -->
<meta charset="UTF-8" />
<title> 제목 </title>
<meta
name="description" content=" 설명 " />
<meta name="keywords" content="키워드, 양파고, Yang Phago, 노션, 양파고 노션, notion" />
<!-- Open Graph / Facebook -->
<meta property="og:title" content="제목 " />
<meta property="og:description" content=" 설명, 양파고, Yang Phago, 노션, 양파고 노션 " />
<meta property="og:image" content="대표 이미지" />
<meta property="og:url" content="페이지 주소" />
<meta property="og:type" content="website" />
</head>
<aside> 💡
</aside>
컴퓨터는 결국은 0과 1의 조합
컴퓨터의 동작은 대부분 논리 회로로 표현가능

<!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>