Generative circuit-ish boards
Log in to post a comment.
// Create a Manhattan path between two points function createManhattanPath(sourcePin, targetPin) { // Get source and target coordinates const sourceX = sourcePin.x; const sourceY = sourcePin.y; const targetX = targetPin.x; const targetY = targetPin.y; // Convert to canvas coordinates for start and end const { x: sourceCanvasX, y: sourceCanvasY } = gridToCanvas(sourceX, sourceY); const { x: targetCanvasX, y: targetCanvasY } = gridToCanvas(targetX, targetY); // Create path with proper corners (Manhattan style) const path = []; // Add source point path.push({ x: sourceCanvasX, y: sourceCanvasY, canvas: true }); // Determine which direction to go first (horizontal or vertical) // This adds some variety to the paths let horizontalFirst = Math.random() < 0.5; // Add intermediate points if (horizontalFirst) { // First go horizontally const midX = (sourceCanvasX + targetCanvasX) / 2; path.push({ x: midX, y: sourceCanvasY, canvas: true }); // Then go vertically path.push({ x: midX, y: targetCanvasY, canvas: true }); } else { // First go vertically const midY = (sourceCanvasY + targetCanvasY) / 2; path.push({ x: sourceCanvasX, y: midY, canvas: true }); // Then go horizontally path.push({ x: targetCanvasX, y: midY, canvas: true }); } // Add target point path.push({ x: targetCanvasX, y: targetCanvasY, canvas: true }); return path; }// Improved Procedural Circuit Board for TurtleToy // Date: 2025-04-27 // Target: TurtleToy (https://turtletoy.net/) // --- Settings --- const GRID_SIZE = 5; // Spacing between grid points (lower = denser) const TRACE_WIDTH = 0.5; // Simulate thicker traces by drawing parallel lines const NUM_TRACE_LINES = 3; // How many parallel lines for one trace (1 for thin) const PAD_RADIUS = GRID_SIZE * 0.3; const VIA_RADIUS = GRID_SIZE * 0.15; // Component density settings const COMPONENT_DENSITY = 0.7; // Higher = more components const MIN_COMPONENTS = 15; // Minimum number of components to place // IC settings const IC_MIN_WIDTH = 3; // In grid units const IC_MAX_WIDTH = 6; // In grid units const IC_MIN_HEIGHT = 3; // In grid units const IC_MAX_HEIGHT = 6; // In grid units const IC_PIN_SPACING = 1; // Grid units between pins const IC_PIN_PAD_RADIUS = GRID_SIZE * 0.1; const RESISTOR_LENGTH = 2; // Grid units long const RESISTOR_WIDTH = 1; // Grid units wide const RESISTOR_PAD_RADIUS = GRID_SIZE * 0.15; const CAPACITOR_RADIUS = GRID_SIZE * 0.6; const CAPACITOR_PAD_RADIUS = GRID_SIZE * 0.15; const MAX_TRACE_SEGMENTS = 20; // Max length of a trace const STRAIGHT_TRACE_CHANCE = 0.8; // Likelihood trace continues straight // --- Global State --- const turtle = new Turtle(); const occupied = new Set(); // Keep track of occupied grid points "x,y" const pads = []; // Store locations of pads [{x, y, r, componentId, pinType}] const components = []; // Store component info [{id, type, gx, gy, width, height, pins: [{x, y, type}]}] // --- Canvas Setup --- const MIN_X = -90; const MAX_X = 90; const MIN_Y = -90; const MAX_Y = 90; // --- Circuit Types --- const CIRCUIT_TYPES = { POWER_SUPPLY: "power_supply", LOGIC_GATES: "logic_gates", MICROCONTROLLER: "microcontroller", AUDIO_AMPLIFIER: "audio_amplifier", LED_DRIVER: "led_driver" }; // --- Component Types --- const COMPONENT_TYPES = { IC: "ic", RESISTOR: "resistor", CAPACITOR: "capacitor", DIODE: "diode", TRANSISTOR: "transistor", LED: "led", POWER: "power", GROUND: "ground" }; // --- Pin Types --- const PIN_TYPES = { INPUT: "input", OUTPUT: "output", POWER: "power", GROUND: "ground", BIDIRECTIONAL: "bidirectional" }; // --- Main Function --- function walk(i) { if (i === 0) { // Initialize turtle.penup(); turtle.home(); // Go to 0,0 turtle.pendown(); // 1. Create circuit graph (defines the logical structure) const circuitType = chooseRandomCircuitType(); createCircuitGraph(circuitType); // Add additional components for a more complete circuit addSupplementaryComponents(circuitType); // 2. Place components based on the circuit design placeComponents(); // 3. Draw power and ground distribution drawPowerAndGroundRails(); // 4. Route traces logically between connected components routeTraces(); // No outline, just circuit components and traces } // This generator creates a static image in one go return false; // Signal TurtleToy we are done after the first step } // --- Circuit Creation Functions --- // Choose a random circuit type function chooseRandomCircuitType() { const types = Object.values(CIRCUIT_TYPES); return types[Math.floor(Math.random() * types.length)]; } // Create the logical circuit structure based on the chosen type function createCircuitGraph(circuitType) { // Different circuit templates based on type switch(circuitType) { case CIRCUIT_TYPES.POWER_SUPPLY: createPowerSupplyCircuit(); break; case CIRCUIT_TYPES.LOGIC_GATES: createLogicGatesCircuit(); break; case CIRCUIT_TYPES.MICROCONTROLLER: createMicrocontrollerCircuit(); break; case CIRCUIT_TYPES.AUDIO_AMPLIFIER: createAudioAmplifierCircuit(); break; case CIRCUIT_TYPES.LED_DRIVER: createLEDDriverCircuit(); break; default: createPowerSupplyCircuit(); // Default to simple power supply } } // Create a simple power supply circuit function createPowerSupplyCircuit() { // Create power input const powerIn = createComponent(COMPONENT_TYPES.POWER, 1, 1); // Create main IC voltage regulator const regulator = createComponent(COMPONENT_TYPES.IC, 3, 3); regulator.name = "VREG"; // Create filter capacitors const cap1 = createComponent(COMPONENT_TYPES.CAPACITOR, 1, 1); const cap2 = createComponent(COMPONENT_TYPES.CAPACITOR, 1, 1); const cap3 = createComponent(COMPONENT_TYPES.CAPACITOR, 1, 1); // Create output resistors for voltage divider const resistor1 = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); const resistor2 = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); // Create ground connection const ground = createComponent(COMPONENT_TYPES.GROUND, 1, 1); // Create power output const powerOut = createComponent(COMPONENT_TYPES.POWER, 1, 1); powerOut.name = "OUT"; // Create diode for reverse polarity protection const diode = createComponent(COMPONENT_TYPES.DIODE, 2, 1); // Create logical connections between components createConnection(powerIn.id, diode.id); createConnection(diode.id, regulator.id); createConnection(diode.id, cap1.id); createConnection(regulator.id, cap2.id); createConnection(regulator.id, resistor1.id); createConnection(resistor1.id, resistor2.id); createConnection(resistor1.id, powerOut.id); createConnection(resistor2.id, ground.id); createConnection(cap1.id, ground.id); createConnection(cap2.id, ground.id); createConnection(cap3.id, powerOut.id); createConnection(cap3.id, ground.id); createConnection(powerOut.id, ground.id); } // Create a logic gates circuit function createLogicGatesCircuit() { // Create input pins const input1 = createComponent(COMPONENT_TYPES.POWER, 1, 1); input1.name = "IN1"; const input2 = createComponent(COMPONENT_TYPES.POWER, 1, 1); input2.name = "IN2"; // Create logic ICs const gate1 = createComponent(COMPONENT_TYPES.IC, 3, 3); gate1.name = "AND"; const gate2 = createComponent(COMPONENT_TYPES.IC, 3, 3); gate2.name = "OR"; // Create output connections const output = createComponent(COMPONENT_TYPES.POWER, 1, 1); output.name = "OUT"; // Create resistors for pull-up/pull-down const resistor1 = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); const resistor2 = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); // Create ground connection const ground = createComponent(COMPONENT_TYPES.GROUND, 1, 1); // Create logical connections createConnection(input1.id, gate1.id); createConnection(input2.id, gate1.id); createConnection(input1.id, gate2.id); createConnection(input2.id, gate2.id); createConnection(gate1.id, output.id); createConnection(gate2.id, resistor1.id); createConnection(resistor1.id, resistor2.id); createConnection(resistor2.id, ground.id); } // Create a microcontroller circuit function createMicrocontrollerCircuit() { // Create main microcontroller IC const mcu = createComponent(COMPONENT_TYPES.IC, 6, 6); mcu.name = "MCU"; // Create power input const power = createComponent(COMPONENT_TYPES.POWER, 1, 1); // Create crystal oscillator const osc = createComponent(COMPONENT_TYPES.IC, 2, 2); osc.name = "XTAL"; // Create reset circuit components const resetCap = createComponent(COMPONENT_TYPES.CAPACITOR, 1, 1); const resetRes = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); // Create I/O components const led1 = createComponent(COMPONENT_TYPES.LED, 1, 1); const led2 = createComponent(COMPONENT_TYPES.LED, 1, 1); const button = createComponent(COMPONENT_TYPES.POWER, 1, 1); button.name = "BTN"; // Create ground const ground = createComponent(COMPONENT_TYPES.GROUND, 1, 1); // Create logical connections createConnection(power.id, mcu.id); createConnection(power.id, resetRes.id); createConnection(resetRes.id, mcu.id); createConnection(resetRes.id, resetCap.id); createConnection(resetCap.id, ground.id); createConnection(osc.id, mcu.id); createConnection(mcu.id, led1.id); createConnection(mcu.id, led2.id); createConnection(button.id, mcu.id); createConnection(led1.id, ground.id); createConnection(led2.id, ground.id); createConnection(mcu.id, ground.id); createConnection(osc.id, ground.id); } // Create an audio amplifier circuit function createAudioAmplifierCircuit() { // Create input jack const input = createComponent(COMPONENT_TYPES.POWER, 1, 1); input.name = "AUDIO IN"; // Create coupling capacitor const couplingCap = createComponent(COMPONENT_TYPES.CAPACITOR, 1, 1); // Create amplifier IC const ampIC = createComponent(COMPONENT_TYPES.IC, 4, 4); ampIC.name = "AMP"; // Create feedback components const feedbackRes = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); const feedbackCap = createComponent(COMPONENT_TYPES.CAPACITOR, 1, 1); // Create output coupling capacitor const outputCap = createComponent(COMPONENT_TYPES.CAPACITOR, 1, 1); // Create output jack const output = createComponent(COMPONENT_TYPES.POWER, 1, 1); output.name = "AUDIO OUT"; // Create power and ground const power = createComponent(COMPONENT_TYPES.POWER, 1, 1); const ground = createComponent(COMPONENT_TYPES.GROUND, 1, 1); // Create logical connections createConnection(input.id, couplingCap.id); createConnection(couplingCap.id, ampIC.id); createConnection(ampIC.id, feedbackRes.id); createConnection(feedbackRes.id, feedbackCap.id); createConnection(feedbackCap.id, ampIC.id); createConnection(ampIC.id, outputCap.id); createConnection(outputCap.id, output.id); createConnection(power.id, ampIC.id); createConnection(ampIC.id, ground.id); createConnection(input.id, ground.id); createConnection(output.id, ground.id); } // Create an LED driver circuit function createLEDDriverCircuit() { // Create power input const power = createComponent(COMPONENT_TYPES.POWER, 1, 1); // Create driver IC const driver = createComponent(COMPONENT_TYPES.IC, 4, 4); driver.name = "LED DRIVER"; // Create LEDs const leds = []; for (let i = 0; i < 4; i++) { leds.push(createComponent(COMPONENT_TYPES.LED, 1, 1)); } // Create current limiting resistors const resistors = []; for (let i = 0; i < 4; i++) { resistors.push(createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH)); } // Create ground const ground = createComponent(COMPONENT_TYPES.GROUND, 1, 1); // Create logical connections createConnection(power.id, driver.id); for (let i = 0; i < 4; i++) { createConnection(driver.id, resistors[i].id); createConnection(resistors[i].id, leds[i].id); createConnection(leds[i].id, ground.id); } createConnection(driver.id, ground.id); } // --- Component Management Functions --- // Create a new component and add it to the components array function createComponent(type, width, height) { const id = components.length; const component = { id, type, width, height, pins: [], connections: [] }; components.push(component); return component; } // Create a logical connection between components function createConnection(fromId, toId) { // Add to both components' connection lists components[fromId].connections.push(toId); components[toId].connections.push(fromId); } // Add supplementary components for more detailed circuits function addSupplementaryComponents(circuitType) { // Add more components based on circuit type switch(circuitType) { case CIRCUIT_TYPES.POWER_SUPPLY: // Add a fuse const fuse = createComponent(COMPONENT_TYPES.RESISTOR, 2, 1); fuse.name = "FUSE"; createConnection(0, fuse.id); // Connect to power input // Add an LED indicator const led = createComponent(COMPONENT_TYPES.LED, 1, 1); const ledResistor = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); createConnection(components.find(c => c.name === "OUT").id, ledResistor.id); createConnection(ledResistor.id, led.id); createConnection(led.id, components.find(c => c.type === COMPONENT_TYPES.GROUND).id); break; case CIRCUIT_TYPES.LOGIC_GATES: // Add a pull-up resistor const pullUp = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); createConnection(components.find(c => c.name === "IN1").id, pullUp.id); createConnection(pullUp.id, components.find(c => c.type === COMPONENT_TYPES.POWER).id); // Add a capacitor for noise filtering const noiseCap = createComponent(COMPONENT_TYPES.CAPACITOR, 1, 1); createConnection(components.find(c => c.name === "IN2").id, noiseCap.id); createConnection(noiseCap.id, components.find(c => c.type === COMPONENT_TYPES.GROUND).id); break; case CIRCUIT_TYPES.MICROCONTROLLER: // Add a reset button const resetBtn = createComponent(COMPONENT_TYPES.POWER, 1, 1); resetBtn.name = "RST"; const mcu = components.find(c => c.name === "MCU"); createConnection(resetBtn.id, mcu.id); // Add more LEDs const led3 = createComponent(COMPONENT_TYPES.LED, 1, 1); const ledRes3 = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); createConnection(mcu.id, ledRes3.id); createConnection(ledRes3.id, led3.id); createConnection(led3.id, components.find(c => c.type === COMPONENT_TYPES.GROUND).id); break; case CIRCUIT_TYPES.AUDIO_AMPLIFIER: // Add tone control const toneRes = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); const toneCap = createComponent(COMPONENT_TYPES.CAPACITOR, 1, 1); const amp = components.find(c => c.name === "AMP"); createConnection(amp.id, toneRes.id); createConnection(toneRes.id, toneCap.id); createConnection(toneCap.id, components.find(c => c.type === COMPONENT_TYPES.GROUND).id); // Add volume control resistors const volRes1 = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); const volRes2 = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); createConnection(components.find(c => c.name === "AUDIO IN").id, volRes1.id); createConnection(volRes1.id, volRes2.id); createConnection(volRes2.id, components.find(c => c.type === COMPONENT_TYPES.GROUND).id); createConnection(volRes1.id, amp.id); break; case CIRCUIT_TYPES.LED_DRIVER: // Add more LEDs const ledDriver = components.find(c => c.name === "LED DRIVER"); for (let i = 0; i < 2; i++) { const led = createComponent(COMPONENT_TYPES.LED, 1, 1); const ledRes = createComponent(COMPONENT_TYPES.RESISTOR, RESISTOR_LENGTH, RESISTOR_WIDTH); createConnection(ledDriver.id, ledRes.id); createConnection(ledRes.id, led.id); createConnection(led.id, components.find(c => c.type === COMPONENT_TYPES.GROUND).id); } // Add timing capacitor const timingCap = createComponent(COMPONENT_TYPES.CAPACITOR, 1, 1); createConnection(ledDriver.id, timingCap.id); createConnection(timingCap.id, components.find(c => c.type === COMPONENT_TYPES.GROUND).id); break; } // Add decoupling capacitors to all ICs const ics = components.filter(c => c.type === COMPONENT_TYPES.IC); for (const ic of ics) { const decouplingCap = createComponent(COMPONENT_TYPES.CAPACITOR, 1, 1); createConnection(ic.id, decouplingCap.id); createConnection(decouplingCap.id, components.find(c => c.type === COMPONENT_TYPES.GROUND).id); } // Add test points const testPoint1 = createComponent(COMPONENT_TYPES.POWER, 1, 1); testPoint1.name = "TP1"; const testPoint2 = createComponent(COMPONENT_TYPES.POWER, 1, 1); testPoint2.name = "TP2"; // Connect test points to interesting nodes if (components.length > 5) { createConnection(testPoint1.id, components[Math.floor(Math.random() * components.length)].id); createConnection(testPoint2.id, components[Math.floor(Math.random() * components.length)].id); } } // Place components based on their connections function placeComponents() { const nx = Math.floor((MAX_X - MIN_X) / GRID_SIZE); const ny = Math.floor((MAX_Y - MIN_Y) / GRID_SIZE); // Ensure we have enough components if (components.length < MIN_COMPONENTS) { // Add some generic components const numToAdd = MIN_COMPONENTS - components.length; for (let i = 0; i < numToAdd; i++) { const type = Object.values(COMPONENT_TYPES)[Math.floor(Math.random() * 5)]; // Choose one of first 5 types const newComp = createComponent(type, type === COMPONENT_TYPES.IC ? 3 : 1, type === COMPONENT_TYPES.IC ? 3 : 1); // Connect to existing components const connectTo = components[Math.floor(Math.random() * components.length)]; createConnection(newComp.id, connectTo.id); if (type === COMPONENT_TYPES.RESISTOR || type === COMPONENT_TYPES.CAPACITOR) { // Connect other end to ground or power const groundOrPower = components.find(c => c.type === (Math.random() < 0.5 ? COMPONENT_TYPES.GROUND : COMPONENT_TYPES.POWER) ); if (groundOrPower) { createConnection(newComp.id, groundOrPower.id); } } } } // Group related components (to place them closer together) const componentGroups = []; const processedComponents = new Set(); // Create groups based on connections for (const component of components) { if (processedComponents.has(component.id)) continue; const group = [component]; processedComponents.add(component.id); // Add directly connected components to group for (const connectedId of component.connections) { if (!processedComponents.has(connectedId)) { group.push(components[connectedId]); processedComponents.add(connectedId); } } componentGroups.push(group); } // Assign regions to each group const groupRegions = []; const numGroups = componentGroups.length; if (numGroups > 1) { // Divide board into regions const rowGroups = Math.ceil(Math.sqrt(numGroups)); const colGroups = Math.ceil(numGroups / rowGroups); const regionWidth = Math.floor(nx / colGroups); const regionHeight = Math.floor(ny / rowGroups); for (let i = 0; i < numGroups; i++) { const row = Math.floor(i / colGroups); const col = i % colGroups; groupRegions.push({ startX: col * regionWidth, startY: row * regionHeight, endX: (col + 1) * regionWidth - 1, endY: (row + 1) * regionHeight - 1 }); } } else { // Just one group, use the whole board groupRegions.push({ startX: 0, startY: 0, endX: nx - 1, endY: ny - 1 }); } // Place components by group for (let g = 0; g < componentGroups.length; g++) { const group = componentGroups[g]; const region = groupRegions[g]; for (const component of group) { let placed = false; let attempts = 0; while (!placed && attempts < 100) { attempts++; // Generate position within region const gx = region.startX + Math.floor(Math.random() * (region.endX - region.startX - component.width + 1)); const gy = region.startY + Math.floor(Math.random() * (region.endY - region.startY - component.height + 1)); // Check if space is available let canPlace = true; for (let ox = 0; ox < component.width; ox++) { for (let oy = 0; oy < component.height; oy++) { if (!isInBounds(gx + ox, gy + oy) || occupied.has(`${gx+ox},${gy+oy}`)) { canPlace = false; break; } } if (!canPlace) break; } if (canPlace) { component.gx = gx; component.gy = gy; // Mark as occupied markOccupiedRect(gx, gy, component.width, component.height); // Draw the component drawComponent(component); placed = true; } } // If we couldn't place in the region, try anywhere if (!placed) { attempts = 0; while (!placed && attempts < 100) { attempts++; // Generate random position anywhere const gx = Math.floor(Math.random() * (nx - component.width)); const gy = Math.floor(Math.random() * (ny - component.height)); // Check if space is available let canPlace = true; for (let ox = 0; ox < component.width; ox++) { for (let oy = 0; oy < component.height; oy++) { if (!isInBounds(gx + ox, gy + oy) || occupied.has(`${gx+ox},${gy+oy}`)) { canPlace = false; break; } } if (!canPlace) break; } if (canPlace) { component.gx = gx; component.gy = gy; // Mark as occupied markOccupiedRect(gx, gy, component.width, component.height); // Draw the component drawComponent(component); placed = true; } } } } } } // Draw a component on the board function drawComponent(component) { const { x: cx, y: cy } = gridToCanvas(component.gx, component.gy); switch (component.type) { case COMPONENT_TYPES.IC: drawIC(component, cx, cy); break; case COMPONENT_TYPES.RESISTOR: drawResistor(component, cx, cy); break; case COMPONENT_TYPES.CAPACITOR: drawCapacitor(component, cx, cy); break; case COMPONENT_TYPES.LED: drawLED(component, cx, cy); break; case COMPONENT_TYPES.POWER: drawPowerNode(component, cx, cy); break; case COMPONENT_TYPES.GROUND: drawGroundNode(component, cx, cy); break; case COMPONENT_TYPES.DIODE: drawDiode(component, cx, cy); break; case COMPONENT_TYPES.TRANSISTOR: drawTransistor(component, cx, cy); break; } } // --- Component Drawing Functions --- // Draw an IC component function drawIC(component, cx, cy) { const width = component.width * GRID_SIZE; const height = component.height * GRID_SIZE; // Draw IC body drawFilledRect(cx + width/2, cy + height/2, width, height); // Add text/name if available if (component.name) { // Simple text representation (no actual text in TurtleToy) const textX = cx + width/2; const textY = cy + height/2; drawSimpleText(component.name, textX, textY, width * 0.6); } // Add pins along edges component.pins = []; // Top edge pins for (let px = 0; px < component.width; px += IC_PIN_SPACING) { if (px === 0 || px === component.width - 1 || Math.random() < 0.7) { const { x: pinX, y: pinY } = gridToCanvas(component.gx + px, component.gy - 1); const pinType = (px === 0) ? PIN_TYPES.POWER : (px === component.width - 1) ? PIN_TYPES.GROUND : (Math.random() < 0.5) ? PIN_TYPES.INPUT : PIN_TYPES.OUTPUT; if (isInBounds(component.gx + px, component.gy - 1)) { drawPad(pinX, pinY, IC_PIN_PAD_RADIUS, component.id, pinType); component.pins.push({ x: component.gx + px, y: component.gy - 1, type: pinType }); } } } // Bottom edge pins for (let px = 0; px < component.width; px += IC_PIN_SPACING) { if (px === 0 || px === component.width - 1 || Math.random() < 0.7) { const { x: pinX, y: pinY } = gridToCanvas(component.gx + px, component.gy + component.height); const pinType = (px === 0) ? PIN_TYPES.GROUND : (px === component.width - 1) ? PIN_TYPES.POWER : (Math.random() < 0.5) ? PIN_TYPES.OUTPUT : PIN_TYPES.INPUT; if (isInBounds(component.gx + px, component.gy + component.height)) { drawPad(pinX, pinY, IC_PIN_PAD_RADIUS, component.id, pinType); component.pins.push({ x: component.gx + px, y: component.gy + component.height, type: pinType }); } } } // Left edge pins for (let py = 0; py < component.height; py += IC_PIN_SPACING) { if (py === 0 || py === component.height - 1 || Math.random() < 0.7) { const { x: pinX, y: pinY } = gridToCanvas(component.gx - 1, component.gy + py); const pinType = (py === 0) ? PIN_TYPES.POWER : (py === component.height - 1) ? PIN_TYPES.GROUND : (Math.random() < 0.5) ? PIN_TYPES.INPUT : PIN_TYPES.BIDIRECTIONAL; if (isInBounds(component.gx - 1, component.gy + py)) { drawPad(pinX, pinY, IC_PIN_PAD_RADIUS, component.id, pinType); component.pins.push({ x: component.gx - 1, y: component.gy + py, type: pinType }); } } } // Right edge pins for (let py = 0; py < component.height; py += IC_PIN_SPACING) { if (py === 0 || py === component.height - 1 || Math.random() < 0.7) { const { x: pinX, y: pinY } = gridToCanvas(component.gx + component.width, component.gy + py); const pinType = (py === 0) ? PIN_TYPES.GROUND : (py === component.height - 1) ? PIN_TYPES.POWER : (Math.random() < 0.5) ? PIN_TYPES.OUTPUT : PIN_TYPES.BIDIRECTIONAL; if (isInBounds(component.gx + component.width, component.gy + py)) { drawPad(pinX, pinY, IC_PIN_PAD_RADIUS, component.id, pinType); component.pins.push({ x: component.gx + component.width, y: component.gy + py, type: pinType }); } } } } // Draw a resistor component function drawResistor(component, cx, cy) { const width = component.width * GRID_SIZE; const height = component.height * GRID_SIZE; // Draw resistor body drawFilledRect(cx + width/2, cy + height/2, width, height); // Add zigzag pattern inside turtle.jump(cx, cy + height/2); turtle.goto(cx + width * 0.2, cy + height/2); // Zigzag pattern const zigZagWidth = width * 0.6; const segmentWidth = zigZagWidth / 6; const zigHeight = height * 0.6; turtle.goto(cx + width * 0.2 + segmentWidth, cy + height/2 - zigHeight/2); turtle.goto(cx + width * 0.2 + segmentWidth * 2, cy + height/2 + zigHeight/2); turtle.goto(cx + width * 0.2 + segmentWidth * 3, cy + height/2 - zigHeight/2); turtle.goto(cx + width * 0.2 + segmentWidth * 4, cy + height/2 + zigHeight/2); turtle.goto(cx + width * 0.2 + segmentWidth * 5, cy + height/2 - zigHeight/2); turtle.goto(cx + width * 0.2 + segmentWidth * 6, cy + height/2); turtle.goto(cx + width, cy + height/2); // Add pads at ends component.pins = []; // Left pad const { x: pinX1, y: pinY1 } = gridToCanvas(component.gx - 1, component.gy); if (isInBounds(component.gx - 1, component.gy)) { drawPad(pinX1, pinY1, RESISTOR_PAD_RADIUS, component.id, PIN_TYPES.BIDIRECTIONAL); component.pins.push({ x: component.gx - 1, y: component.gy, type: PIN_TYPES.BIDIRECTIONAL }); } // Right pad const { x: pinX2, y: pinY2 } = gridToCanvas(component.gx + component.width, component.gy); if (isInBounds(component.gx + component.width, component.gy)) { drawPad(pinX2, pinY2, RESISTOR_PAD_RADIUS, component.id, PIN_TYPES.BIDIRECTIONAL); component.pins.push({ x: component.gx + component.width, y: component.gy, type: PIN_TYPES.BIDIRECTIONAL }); } } // Draw a capacitor component function drawCapacitor(component, cx, cy) { // Draw capacitor symbol const radius = CAPACITOR_RADIUS; const centerX = cx + radius; const centerY = cy + radius; // Draw plates // Vertical line left plate turtle.jump(centerX - radius * 0.5, centerY - radius * 0.7); turtle.goto(centerX - radius * 0.5, centerY + radius * 0.7); // Vertical line right plate turtle.jump(centerX + radius * 0.5, centerY - radius * 0.7); turtle.goto(centerX + radius * 0.5, centerY + radius * 0.7); // Add pads component.pins = []; // Left pad const { x: pinX1, y: pinY1 } = gridToCanvas(component.gx - 1, component.gy); if (isInBounds(component.gx - 1, component.gy)) { drawPad(pinX1, pinY1, CAPACITOR_PAD_RADIUS, component.id, PIN_TYPES.BIDIRECTIONAL); component.pins.push({ x: component.gx - 1, y: component.gy, type: PIN_TYPES.BIDIRECTIONAL }); } // Right pad const { x: pinX2, y: pinY2 } = gridToCanvas(component.gx + 2, component.gy); if (isInBounds(component.gx + 2, component.gy)) { drawPad(pinX2, pinY2, CAPACITOR_PAD_RADIUS, component.id, PIN_TYPES.BIDIRECTIONAL); component.pins.push({ x: component.gx + 2, y: component.gy, type: PIN_TYPES.BIDIRECTIONAL }); } // Connect pads to plates turtle.jump(pinX1, pinY1); turtle.goto(centerX - radius * 0.5, centerY); turtle.jump(pinX2, pinY2); turtle.goto(centerX + radius * 0.5, centerY); } // Draw an LED component function drawLED(component, cx, cy) { const centerX = cx + GRID_SIZE/2; const centerY = cy + GRID_SIZE/2; const radius = GRID_SIZE * 0.4; // Draw circle for LED turtle.jump(centerX, centerY - radius); turtle.circle(radius); // Draw arrow inside (simplified LED symbol) const arrowSize = radius * 0.8; turtle.jump(centerX - arrowSize/2, centerY - arrowSize/2); turtle.goto(centerX + arrowSize/2, centerY); turtle.goto(centerX - arrowSize/2, centerY + arrowSize/2); // Add pads component.pins = []; // Anode pad const { x: anodeX, y: anodeY } = gridToCanvas(component.gx - 1, component.gy); if (isInBounds(component.gx - 1, component.gy)) { drawPad(anodeX, anodeY, RESISTOR_PAD_RADIUS, component.id, PIN_TYPES.POWER); component.pins.push({ x: component.gx - 1, y: component.gy, type: PIN_TYPES.POWER }); } // Cathode pad const { x: cathodeX, y: cathodeY } = gridToCanvas(component.gx + 1, component.gy); if (isInBounds(component.gx + 1, component.gy)) { drawPad(cathodeX, cathodeY, RESISTOR_PAD_RADIUS, component.id, PIN_TYPES.GROUND); component.pins.push({ x: component.gx + 1, y: component.gy, type: PIN_TYPES.GROUND }); } } // Draw a power node function drawPowerNode(component, cx, cy) { const centerX = cx + GRID_SIZE/2; const centerY = cy + GRID_SIZE/2; const radius = GRID_SIZE * 0.4; // Draw power symbol drawPad(centerX, centerY, radius, component.id, PIN_TYPES.POWER); // Draw + symbol inside const symbolSize = radius * 0.6; // Horizontal line turtle.jump(centerX - symbolSize, centerY); turtle.goto(centerX + symbolSize, centerY); // Vertical line turtle.jump(centerX, centerY - symbolSize); turtle.goto(centerX, centerY + symbolSize); component.pins = [{ x: component.gx, y: component.gy, type: PIN_TYPES.POWER }]; } // Draw a ground node function drawGroundNode(component, cx, cy) { const centerX = cx + GRID_SIZE/2; const centerY = cy + GRID_SIZE/2; const radius = GRID_SIZE * 0.4; // Draw ground symbol drawPad(centerX, centerY, radius, component.id, PIN_TYPES.GROUND); // Draw ground symbol inside const symbolSize = radius * 0.8; // Horizontal lines turtle.jump(centerX - symbolSize, centerY); turtle.goto(centerX + symbolSize, centerY); turtle.jump(centerX - symbolSize * 0.7, centerY + symbolSize * 0.3); turtle.goto(centerX + symbolSize * 0.7, centerY + symbolSize * 0.3); turtle.jump(centerX - symbolSize * 0.4, centerY + symbolSize * 0.6); turtle.goto(centerX + symbolSize * 0.4, centerY + symbolSize * 0.6); component.pins = [{ x: component.gx, y: component.gy, type: PIN_TYPES.GROUND }]; } // Draw a diode function drawDiode(component, cx, cy) { const centerX = cx + GRID_SIZE; const centerY = cy + GRID_SIZE/2; // Draw diode symbol // Triangle const triangleSize = GRID_SIZE * 0.8; turtle.jump(centerX - triangleSize, centerY - triangleSize/2); turtle.goto(centerX, centerY); turtle.goto(centerX - triangleSize, centerY + triangleSize/2); turtle.goto(centerX - triangleSize, centerY - triangleSize/2); // Line turtle.jump(centerX, centerY - triangleSize/2); turtle.goto(centerX, centerY + triangleSize/2); // Add pads component.pins = []; // Anode pad const { x: anodeX, y: anodeY } = gridToCanvas(component.gx - 1, component.gy); if (isInBounds(component.gx - 1, component.gy)) { drawPad(anodeX, anodeY, RESISTOR_PAD_RADIUS, component.id, PIN_TYPES.POWER); component.pins.push({ x: component.gx - 1, y: component.gy, type: PIN_TYPES.POWER }); } // Cathode pad const { x: cathodeX, y: cathodeY } = gridToCanvas(component.gx + 2, component.gy); if (isInBounds(component.gx + 2, component.gy)) { drawPad(cathodeX, cathodeY, RESISTOR_PAD_RADIUS, component.id, PIN_TYPES.GROUND); component.pins.push({ x: component.gx + 2, y: component.gy, type: PIN_TYPES.GROUND }); } } // Draw a transistor function drawTransistor(component, cx, cy) { const centerX = cx + GRID_SIZE; const centerY = cy + GRID_SIZE; const size = GRID_SIZE * 0.8; // Draw transistor symbol (simplified) // Circle turtle.jump(centerX, centerY - size); turtle.circle(size); // Base line turtle.jump(centerX - size, centerY); turtle.goto(centerX - size/4, centerY); // Collector line turtle.jump(centerX - size/4, centerY + size/2); turtle.goto(centerX - size/4, centerY - size/2); turtle.goto(centerX - size/4, centerY + size/3); // Emitter line turtle.jump(centerX - size/4, centerY); turtle.goto(centerX + size/2, centerY + size/2); // Add pads component.pins = []; // Base pad const { x: baseX, y: baseY } = gridToCanvas(component.gx - 1, component.gy + 1); if (isInBounds(component.gx - 1, component.gy + 1)) { drawPad(baseX, baseY, RESISTOR_PAD_RADIUS, component.id, PIN_TYPES.INPUT); component.pins.push({ x: component.gx - 1, y: component.gy + 1, type: PIN_TYPES.INPUT }); } // Collector pad const { x: collectorX, y: collectorY } = gridToCanvas(component.gx + 1, component.gy); if (isInBounds(component.gx + 1, component.gy)) { drawPad(collectorX, collectorY, RESISTOR_PAD_RADIUS, component.id, PIN_TYPES.OUTPUT); component.pins.push({ x: component.gx + 1, y: component.gy, type: PIN_TYPES.OUTPUT }); } // Emitter pad const { x: emitterX, y: emitterY } = gridToCanvas(component.gx + 1, component.gy + 2); if (isInBounds(component.gx + 1, component.gy + 2)) { drawPad(emitterX, emitterY, RESISTOR_PAD_RADIUS, component.id, PIN_TYPES.GROUND); component.pins.push({ x: component.gx + 1, y: component.gy + 2, type: PIN_TYPES.GROUND }); } } // --- Trace Routing Functions --- // Route traces between connected components function routeTraces() { const processed = new Set(); // For each component for (const component of components) { // For each connection for (const connectedId of component.connections) { const connectionKey = [component.id, connectedId].sort().join('-'); // Skip if already processed if (processed.has(connectionKey)) continue; processed.add(connectionKey); const connectedComponent = components[connectedId]; // Find suitable pins to connect const sourcePin = findSuitablePin(component, connectedComponent); const targetPin = findSuitablePin(connectedComponent, component); if (sourcePin && targetPin) { routeTraceBetweenPins(sourcePin, targetPin, component.id, connectedId); } } } } // Find a suitable pin on a component for connection function findSuitablePin(fromComponent, toComponent) { if (!fromComponent.pins || fromComponent.pins.length === 0) return null; // Find compatible pins based on types let compatiblePins = []; for (const pin of fromComponent.pins) { // Check pin type compatibility if ( (pin.type === PIN_TYPES.OUTPUT && toComponent.type !== COMPONENT_TYPES.POWER) || (pin.type === PIN_TYPES.INPUT && toComponent.type !== COMPONENT_TYPES.GROUND) || (pin.type === PIN_TYPES.POWER && (toComponent.type === COMPONENT_TYPES.POWER || toComponent.type === COMPONENT_TYPES.IC)) || (pin.type === PIN_TYPES.GROUND && (toComponent.type === COMPONENT_TYPES.GROUND || toComponent.type === COMPONENT_TYPES.IC)) || pin.type === PIN_TYPES.BIDIRECTIONAL ) { compatiblePins.push(pin); } } // If no compatible pins, use any available pin if (compatiblePins.length === 0) { compatiblePins = fromComponent.pins; } // Pick a pin return compatiblePins[Math.floor(Math.random() * compatiblePins.length)]; } // Route a trace between two pins using A* pathfinding function routeTraceBetweenPins(sourcePin, targetPin, sourceComponentId, targetComponentId) { // Convert to canvas coordinates const { x: sourceX, y: sourceY } = gridToCanvas(sourcePin.x, sourcePin.y); const { x: targetX, y: targetY } = gridToCanvas(targetPin.x, targetPin.y); // Use A* to find a path const path = findPath(sourcePin.x, sourcePin.y, targetPin.x, targetPin.y); // Only draw if we have a valid path with multiple segments if (path.length > 0) { // Convert path to canvas coordinates const canvasPath = path.map(point => gridToCanvas(point.x, point.y)); // Add start and end points canvasPath.unshift({ x: sourceX, y: sourceY }); canvasPath.push({ x: targetX, y: targetY }); // Only draw if we have at least 3 points (minimum 2 segments) if (canvasPath.length >= 3) { // Draw the trace drawTracePath(canvasPath); } else { // For short paths, add intermediate points to create a proper routed path const midX = (sourceX + targetX) / 2; const midY = (sourceY + targetY) / 2; // Create a 3-segment path with jogs const enhancedPath = [ { x: sourceX, y: sourceY }, { x: sourceX, y: midY }, { x: targetX, y: midY }, { x: targetX, y: targetY } ]; // Draw the enhanced path drawTracePath(enhancedPath); } } else { // Create a multi-segment manhattan path instead of direct line const manhattanPath = createManhattanPath(sourcePin, targetPin); const canvasPath = manhattanPath.map(point => point.canvas ? point : { x: gridToCanvas(point.x, point.y).x, y: gridToCanvas(point.x, point.y).y } ); // Draw the manhattan path drawTracePath(canvasPath); } } // A* pathfinding algorithm function findPath(startX, startY, endX, endY) { const openSet = [{ x: startX, y: startY, g: 0, h: heuristic(startX, startY, endX, endY), f: 0 }]; const closedSet = new Set(); const cameFrom = {}; while (openSet.length > 0) { // Find node with lowest f score let currentIndex = 0; for (let i = 1; i < openSet.length; i++) { if (openSet[i].f < openSet[currentIndex].f) { currentIndex = i; } } const current = openSet[currentIndex]; // Check if we reached the goal if (current.x === endX && current.y === endY) { // Reconstruct path const path = []; let currentNode = `${current.x},${current.y}`; while (cameFrom[currentNode]) { const [x, y] = currentNode.split(',').map(Number); path.unshift({ x, y }); currentNode = cameFrom[currentNode]; } return path; } // Remove current from openSet and add to closedSet openSet.splice(currentIndex, 1); closedSet.add(`${current.x},${current.y}`); // Check neighbors const directions = [ { dx: 1, dy: 0 }, // Right { dx: 0, dy: 1 }, // Up { dx: -1, dy: 0 }, // Left { dx: 0, dy: -1 } // Down ]; for (const dir of directions) { const neighborX = current.x + dir.dx; const neighborY = current.y + dir.dy; const neighborKey = `${neighborX},${neighborY}`; // Skip if neighbor is in closedSet or not in bounds if (closedSet.has(neighborKey) || !isInBounds(neighborX, neighborY)) { continue; } // Skip if occupied (unless it's the target) if (occupied.has(neighborKey) && !(neighborX === endX && neighborY === endY)) { continue; } const g = current.g + 1; const h = heuristic(neighborX, neighborY, endX, endY); const f = g + h; // Check if neighbor is in openSet with a better path const existingNeighbor = openSet.find(node => node.x === neighborX && node.y === neighborY); if (existingNeighbor && existingNeighbor.g <= g) { continue; } // Add neighbor to openSet if (!existingNeighbor) { openSet.push({ x: neighborX, y: neighborY, g, h, f }); } else { existingNeighbor.g = g; existingNeighbor.f = f; } // Record path cameFrom[neighborKey] = `${current.x},${current.y}`; } } // No path found return []; } // Manhattan distance heuristic function heuristic(x1, y1, x2, y2) { return Math.abs(x1 - x2) + Math.abs(y1 - y2); } // --- Power and Ground Routing --- // Draw integrated power and ground distribution function drawPowerAndGroundRails() { const nx = Math.floor((MAX_X - MIN_X) / GRID_SIZE); const ny = Math.floor((MAX_Y - MIN_Y) / GRID_SIZE); // Find power and ground components const powerComponents = components.filter(c => c.type === COMPONENT_TYPES.POWER); const groundComponents = components.filter(c => c.type === COMPONENT_TYPES.GROUND); // Only draw rails if we have enough components to connect to if (powerComponents.length > 0 && groundComponents.length > 0 && components.length >= 5) { // Instead of straight horizontal rails, create a more integrated power distribution // Find component clusters to route power through // Create terminal blocks at edges for power and ground const powerTerminalX = 5; const powerTerminalY = Math.floor(ny / 4); const { x: powerTermX, y: powerTermY } = gridToCanvas(powerTerminalX, powerTerminalY); const groundTerminalX = nx - 6; const groundTerminalY = Math.floor(3 * ny / 4); const { x: groundTermX, y: groundTermY } = gridToCanvas(groundTerminalX, groundTerminalY); // Draw terminal blocks drawFilledRect(powerTermX + GRID_SIZE, powerTermY + GRID_SIZE, GRID_SIZE * 2, GRID_SIZE * 2); drawSimpleText("VCC", powerTermX + GRID_SIZE, powerTermY + GRID_SIZE, GRID_SIZE * 1.5); drawPad(powerTermX, powerTermY, PAD_RADIUS * 1.2, -1, PIN_TYPES.POWER); drawFilledRect(groundTermX + GRID_SIZE, groundTermY + GRID_SIZE, GRID_SIZE * 2, GRID_SIZE * 2); drawSimpleText("GND", groundTermX + GRID_SIZE, groundTermY + GRID_SIZE, GRID_SIZE * 1.5); drawPad(groundTermX, groundTermY, PAD_RADIUS * 1.2, -1, PIN_TYPES.GROUND); // Create power distribution tree instead of a single rail const powerNodes = []; const groundNodes = []; // Add 3-4 power distribution nodes const numPowerNodes = 3 + Math.floor(Math.random() * 2); const numGroundNodes = 3 + Math.floor(Math.random() * 2); // Create power distribution nodes for (let i = 0; i < numPowerNodes; i++) { const nodeX = Math.floor(nx * (i + 1) / (numPowerNodes + 1)); const nodeY = Math.floor(ny / 3 + (Math.random() * ny / 3)); if (!isOccupied(nodeX, nodeY)) { const { x: canvasX, y: canvasY } = gridToCanvas(nodeX, nodeY); drawPad(canvasX, canvasY, VIA_RADIUS * 1.5, -1, PIN_TYPES.POWER); powerNodes.push({ x: nodeX, y: nodeY, canvasX, canvasY }); markOccupiedPoint(nodeX, nodeY); } } // Create ground distribution nodes for (let i = 0; i < numGroundNodes; i++) { const nodeX = Math.floor(nx * (i + 1) / (numGroundNodes + 1)); const nodeY = Math.floor(ny * 2/3 + (Math.random() * ny / 4)); if (!isOccupied(nodeX, nodeY)) { const { x: canvasX, y: canvasY } = gridToCanvas(nodeX, nodeY); drawPad(canvasX, canvasY, VIA_RADIUS * 1.5, -1, PIN_TYPES.GROUND); groundNodes.push({ x: nodeX, y: nodeY, canvasX, canvasY }); markOccupiedPoint(nodeX, nodeY); } } // Connect power terminal to first power node if (powerNodes.length > 0) { turtle.jump(powerTermX, powerTermY); turtle.goto(powerNodes[0].canvasX, powerNodes[0].canvasY); // Connect power nodes in a tree for (let i = 1; i < powerNodes.length; i++) { const prevNode = powerNodes[i-1]; const currNode = powerNodes[i]; turtle.jump(prevNode.canvasX, prevNode.canvasY); turtle.goto(currNode.canvasX, currNode.canvasY); } } // Connect ground terminal to first ground node if (groundNodes.length > 0) { turtle.jump(groundTermX, groundTermY); turtle.goto(groundNodes[0].canvasX, groundNodes[0].canvasY); // Connect ground nodes in a tree for (let i = 1; i < groundNodes.length; i++) { const prevNode = groundNodes[i-1]; const currNode = groundNodes[i]; turtle.jump(prevNode.canvasX, prevNode.canvasY); turtle.goto(currNode.canvasX, currNode.canvasY); } } // Connect power components to nearest power node for (const component of powerComponents) { if (component.pins && component.pins.length > 0) { const pin = component.pins[0]; const { x: pinX, y: pinY } = gridToCanvas(pin.x, pin.y); // Find closest power node let closestNode = null; let minDist = Infinity; for (const node of powerNodes) { const dist = Math.sqrt( Math.pow(pin.x - node.x, 2) + Math.pow(pin.y - node.y, 2) ); if (dist < minDist) { minDist = dist; closestNode = node; } } if (closestNode) { // Route to closest power node const path = findPath(pin.x, pin.y, closestNode.x, closestNode.y); if (path.length > 0) { // Convert path to canvas coordinates const canvasPath = path.map(point => gridToCanvas(point.x, point.y)); // Add start and end points canvasPath.unshift({ x: pinX, y: pinY }); canvasPath.push({ x: closestNode.canvasX, y: closestNode.canvasY }); // Draw the trace drawTracePath(canvasPath); } else { // Direct line if no path found turtle.jump(pinX, pinY); turtle.goto(closestNode.canvasX, closestNode.canvasY); } } } } // Connect ground components to nearest ground node for (const component of groundComponents) { if (component.pins && component.pins.length > 0) { const pin = component.pins[0]; const { x: pinX, y: pinY } = gridToCanvas(pin.x, pin.y); // Find closest ground node let closestNode = null; let minDist = Infinity; for (const node of groundNodes) { const dist = Math.sqrt( Math.pow(pin.x - node.x, 2) + Math.pow(pin.y - node.y, 2) ); if (dist < minDist) { minDist = dist; closestNode = node; } } if (closestNode) { // Route to closest ground node const path = findPath(pin.x, pin.y, closestNode.x, closestNode.y); if (path.length > 0) { // Convert path to canvas coordinates const canvasPath = path.map(point => gridToCanvas(point.x, point.y)); // Add start and end points canvasPath.unshift({ x: pinX, y: pinY }); canvasPath.push({ x: closestNode.canvasX, y: closestNode.canvasY }); // Draw the trace drawTracePath(canvasPath); } else { // Direct line if no path found turtle.jump(pinX, pinY); turtle.goto(closestNode.canvasX, closestNode.canvasY); } } } } // Connect power/ground pins from ICs to nearest distribution nodes for (const component of components) { if (component.type === COMPONENT_TYPES.IC && component.pins) { // Find power pins const powerPins = component.pins.filter(pin => pin.type === PIN_TYPES.POWER); for (const pin of powerPins) { const { x: pinX, y: pinY } = gridToCanvas(pin.x, pin.y); // Find closest power node let closestNode = null; let minDist = Infinity; for (const node of powerNodes) { const dist = Math.sqrt( Math.pow(pin.x - node.x, 2) + Math.pow(pin.y - node.y, 2) ); if (dist < minDist) { minDist = dist; closestNode = node; } } if (closestNode) { // Route to power node const path = findPath(pin.x, pin.y, closestNode.x, closestNode.y); if (path.length > 0) { // Convert path to canvas coordinates const canvasPath = path.map(point => gridToCanvas(point.x, point.y)); // Add start and end points canvasPath.unshift({ x: pinX, y: pinY }); canvasPath.push({ x: closestNode.canvasX, y: closestNode.canvasY }); // Draw the trace drawTracePath(canvasPath); } else { // Direct line turtle.jump(pinX, pinY); turtle.goto(closestNode.canvasX, closestNode.canvasY); } } } // Find ground pins const groundPins = component.pins.filter(pin => pin.type === PIN_TYPES.GROUND); for (const pin of groundPins) { const { x: pinX, y: pinY } = gridToCanvas(pin.x, pin.y); // Find closest ground node let closestNode = null; let minDist = Infinity; for (const node of groundNodes) { const dist = Math.sqrt( Math.pow(pin.x - node.x, 2) + Math.pow(pin.y - node.y, 2) ); if (dist < minDist) { minDist = dist; closestNode = node; } } if (closestNode) { // Route to ground node const path = findPath(pin.x, pin.y, closestNode.x, closestNode.y); if (path.length > 0) { // Convert path to canvas coordinates const canvasPath = path.map(point => gridToCanvas(point.x, point.y)); // Add start and end points canvasPath.unshift({ x: pinX, y: pinY }); canvasPath.push({ x: closestNode.canvasX, y: closestNode.canvasY }); // Draw the trace drawTracePath(canvasPath); } else { // Direct line turtle.jump(pinX, pinY); turtle.goto(closestNode.canvasX, closestNode.canvasY); } } } } } } } // --- Helper Functions --- // Add board outline and decorative elements function addBoardOutline() { // Draw PCB outline turtle.penup(); turtle.goto(MIN_X + 5, MIN_Y + 5); turtle.pendown(); // Rounded corners const cornerRadius = 10; // Bottom left corner turtle.goto(MIN_X + 5 + cornerRadius, MIN_Y + 5); turtle.arc(90, cornerRadius); // Bottom edge turtle.goto(MAX_X - 5 - cornerRadius, MIN_Y + 5 + cornerRadius); // Bottom right corner turtle.arc(90, cornerRadius); // Right edge turtle.goto(MAX_X - 5, MAX_Y - 5 - cornerRadius); // Top right corner turtle.arc(90, cornerRadius); // Top edge turtle.goto(MIN_X + 5 + cornerRadius, MAX_Y - 5); // Top left corner turtle.arc(90, cornerRadius); // Left edge turtle.goto(MIN_X + 5, MIN_Y + 5 + cornerRadius); } // Add mounting holes to the board function addMountingHoles() { const holeRadius = 3; const margin = 15; // Bottom left mounting hole turtle.jump(MIN_X + margin, MIN_Y + margin); turtle.circle(holeRadius); // Bottom right mounting hole turtle.jump(MAX_X - margin, MIN_Y + margin); turtle.circle(holeRadius); // Top right mounting hole turtle.jump(MAX_X - margin, MAX_Y - margin); turtle.circle(holeRadius); // Top left mounting hole turtle.jump(MIN_X + margin, MAX_Y - margin); turtle.circle(holeRadius); } // Arc drawing helper (adds to Turtle prototype) Turtle.prototype.arc = function(angle, radius) { const startX = this.x; const startY = this.y; const steps = Math.max(10, Math.floor(radius * 0.5)); const angleStep = angle / steps; // Get starting angle based on current direction let currentAngle = this.angle - 90; for (let i = 0; i <= steps; i++) { const rads = (currentAngle + angleStep * i) * Math.PI / 180; const x = startX + radius * Math.cos(rads); const y = startY + radius * Math.sin(rads); this.goto(x, y); } // Update turtle's angle this.angle = (this.angle + angle) % 360; }; // Grid coordinate conversion function gridToCanvas(gx, gy) { const canvasX = MIN_X + (MAX_X - MIN_X) * (gx / (Math.floor((MAX_X - MIN_X) / GRID_SIZE))); const canvasY = MIN_Y + (MAX_Y - MIN_Y) * (gy / (Math.floor((MAX_Y - MIN_Y) / GRID_SIZE))); // Clamp to bounds just in case return { x: Math.max(MIN_X, Math.min(MAX_X, canvasX)), y: Math.max(MIN_Y, Math.min(MAX_Y, canvasY)) }; } // Check if grid coordinate is within bounds function isInBounds(gx, gy) { const nx = Math.floor((MAX_X - MIN_X) / GRID_SIZE); const ny = Math.floor((MAX_Y - MIN_Y) / GRID_SIZE); return gx >= 0 && gx < nx && gy >= 0 && gy < ny; } // Mark grid points as occupied function markOccupiedRect(gx, gy, gw, gh) { for (let x = gx; x < gx + gw; x++) { for (let y = gy; y < gy + gh; y++) { if (isInBounds(x, y)) { occupied.add(`${x},${y}`); } } } } function markOccupiedPoint(gx, gy) { if (isInBounds(gx, gy)) { occupied.add(`${gx},${gy}`); } } function isOccupied(gx, gy) { return occupied.has(`${gx},${gy}`); } // Draw a simple circle pad function drawPad(cx, cy, radius, componentId, pinType) { turtle.jump(cx, cy - radius); // Jump to starting point for circle turtle.circle(radius); // Store pad center for trace routing pads.push({ x: cx, y: cy, r: radius, componentId, pinType }); } // Draw a filled rectangle (for components) function drawFilledRect(cx, cy, width, height) { const x1 = cx - width / 2; const y1 = cy - height / 2; const x2 = cx + width / 2; const y2 = cy + height / 2; turtle.jump(x1, y1); turtle.goto(x2, y1); turtle.goto(x2, y2); turtle.goto(x1, y2); turtle.goto(x1, y1); // Add simple hatching fill for (let y = y1; y <= y2; y += GRID_SIZE / 2) { turtle.jump(x1, y); turtle.goto(x2, y); } } // Draws the actual lines for a trace path, optionally thicker function drawTracePath(path) { if (NUM_TRACE_LINES <= 1) { // Simple thin trace turtle.jump(path[0].x, path[0].y); for (let k = 1; k < path.length; k++) { turtle.goto(path[k].x, path[k].y); } } else { // Simulate thicker trace with parallel lines for (let lineIdx = 0; lineIdx < NUM_TRACE_LINES; lineIdx++) { const offset = (lineIdx - Math.floor(NUM_TRACE_LINES / 2)) * TRACE_WIDTH; let lastOffsetP = null; turtle.penup(); for (let k = 0; k < path.length; k++) { let p1 = path[k]; let p2 = (k < path.length - 1) ? path[k+1] : p1; // Next point or current if last let p0 = (k > 0) ? path[k-1] : p1; // Previous point or current if first let dx = p2.x - p0.x; let dy = p2.y - p0.y; let len = Math.sqrt(dx*dx + dy*dy); let nx = 0, ny = 0; if (len > 0.01) { // Normal vector (perpendicular) nx = -dy / len; ny = dx / len; } else if (k > 0) { // Handle endpoint case - use previous segment's normal dx = p1.x - p0.x; dy = p1.y - p0.y; len = Math.sqrt(dx*dx + dy*dy); if (len > 0.01) { nx = -dy / len; ny = dx / len; } } const offsetX = p1.x + nx * offset; const offsetY = p1.y + ny * offset; const offsetP = {x: offsetX, y: offsetY}; if (k === 0) { turtle.goto(offsetP.x, offsetP.y); turtle.pendown(); } else { turtle.goto(offsetP.x, offsetP.y); } lastOffsetP = offsetP; } turtle.penup(); // End of one parallel line } } } // Draw simplified text representation function drawSimpleText(text, x, y, maxWidth) { const scale = Math.min(1, maxWidth / (text.length * 5)); const charWidth = 5 * scale; const charHeight = 7 * scale; const startX = x - (text.length * charWidth) / 2; for (let i = 0; i < text.length; i++) { const charX = startX + i * charWidth; // Extremely simplified text (just a box for each character) turtle.jump(charX, y - charHeight/2); turtle.goto(charX + charWidth, y - charHeight/2); turtle.goto(charX + charWidth, y + charHeight/2); turtle.goto(charX, y + charHeight/2); turtle.goto(charX, y - charHeight/2); } } // Simple jump function Turtle.prototype.jump = function(x,y) { this.penup(); this.goto(x,y); this.pendown(); };