// Custom components system — save and reuse circuits as components import { state } from './state.js'; import { GATE_W, GATE_H } from './constants.js'; /** * Save current circuit as a reusable component * Returns the component ID if successful */ export function saveComponentFromCircuit(name) { // Validate inputs exist const inputGates = state.gates.filter(g => g.type === 'INPUT'); const outputGates = state.gates.filter(g => g.type === 'OUTPUT'); if (inputGates.length === 0 || outputGates.length === 0) { return { success: false, error: 'Component must have at least one INPUT and one OUTPUT' }; } // Create component definition const component = { id: sanitizeComponentName(name), name, inputCount: inputGates.length, outputCount: outputGates.length, gates: JSON.parse(JSON.stringify(state.gates)), connections: JSON.parse(JSON.stringify(state.connections)) }; // Store in state if (!state.customComponents) { state.customComponents = {}; } state.customComponents[component.id] = component; return { success: true, component }; } /** * Instantiate a component on the canvas */ export function instantiateComponent(componentId, x, y) { if (!state.customComponents || !state.customComponents[componentId]) { return { success: false, error: 'Component not found' }; } const component = state.customComponents[componentId]; const instanceId = state.nextId++; // Create a component instance gate const gate = { id: instanceId, type: `COMPONENT:${componentId}`, x, y, value: 0, component }; state.gates.push(gate); return { success: true, gate }; } /** * Evaluate a component instance * Simulates the internal circuit and returns output */ export function evaluateComponent(gate, inputs) { if (!gate.component) return 0; const comp = gate.component; const internalState = { gates: JSON.parse(JSON.stringify(comp.gates)), connections: JSON.parse(JSON.stringify(comp.connections)), nextId: Math.max(...comp.gates.map(g => g.id), 0) + 1 }; // Set inputs const inputGates = internalState.gates.filter(g => g.type === 'INPUT'); inputs.forEach((val, i) => { if (inputGates[i]) inputGates[i].value = val; }); // Evaluate internal circuit evaluateInternalCircuit(internalState); // Get outputs const outputGates = internalState.gates.filter(g => g.type === 'OUTPUT'); const outputs = outputGates.map(g => g.value || 0); return outputs; } /** * Helper to evaluate internal circuit */ function evaluateInternalCircuit(internalState) { const { gates, connections } = internalState; // Simple evaluation - may need optimization for complex circuits for (let i = 0; i < 10; i++) { for (const gate of gates) { if (gate.type === 'INPUT' || gate.type === 'CLOCK') continue; const inputCount = getGateInputCount(gate.type); const inputs = []; for (let j = 0; j < inputCount; j++) { const conn = connections.find(c => c.to === gate.id && c.toPort === j); if (conn) { const srcGate = gates.find(g => g.id === conn.from); inputs.push(srcGate ? srcGate.value || 0 : 0); } else { inputs.push(0); } } // Evaluate based on gate type let result = 0; if (gate.type === 'AND') result = (inputs[0] && inputs[1]) ? 1 : 0; else if (gate.type === 'OR') result = (inputs[0] || inputs[1]) ? 1 : 0; else if (gate.type === 'NOT') result = inputs[0] ? 0 : 1; else if (gate.type === 'NAND') result = (inputs[0] && inputs[1]) ? 0 : 1; else if (gate.type === 'NOR') result = (inputs[0] || inputs[1]) ? 0 : 1; else if (gate.type === 'XOR') result = (inputs[0] !== inputs[1]) ? 1 : 0; else if (gate.type === 'OUTPUT') result = inputs[0] || 0; gate.value = result; } } } /** * Get input count for a gate type (includes component types) */ function getGateInputCount(type) { if (type === 'CLOCK' || type === 'INPUT') return 0; if (type === 'NOT' || type === 'OUTPUT') return 1; if (type.startsWith('COMPONENT:')) { // Return the component's input count return 2; // Default for now, should lookup } return 2; } /** * Get output count for a gate type */ function getGateOutputCount(type) { if (type === 'OUTPUT') return 0; return 1; } /** * Sanitize component name for use as ID */ function sanitizeComponentName(name) { return name .toLowerCase() .replace(/[^a-z0-9_]/g, '_') .replace(/_+/g, '_') .replace(/^_|_$/g, ''); } /** * Get all custom components */ export function getAllComponents() { return state.customComponents || {}; } /** * Delete a component */ export function deleteComponent(componentId) { if (state.customComponents) { delete state.customComponents[componentId]; return { success: true }; } return { success: false, error: 'Component not found' }; } /** * Export component data as JSON */ export function exportComponent(componentId) { if (!state.customComponents || !state.customComponents[componentId]) { return { success: false, error: 'Component not found' }; } return { success: true, data: state.customComponents[componentId] }; } /** * Import component from JSON */ export function importComponent(data) { if (!data.id || !data.gates || !data.connections) { return { success: false, error: 'Invalid component data' }; } if (!state.customComponents) { state.customComponents = {}; } state.customComponents[data.id] = data; return { success: true, component: data }; }