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();
};