|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | + <meta charset="UTF-8"> |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | + <title>Hamiltonian Path & Circuit Finder - GraphVisual</title> |
| 7 | + <link rel="stylesheet" href="styles.css"> |
| 8 | + <style> |
| 9 | + .ham-container { display: flex; gap: 20px; flex-wrap: wrap; } |
| 10 | + .ham-canvas-wrap { flex: 1; min-width: 500px; } |
| 11 | + .ham-canvas-wrap canvas { width: 100%; border: 2px solid #334155; border-radius: 8px; background: #0f172a; cursor: crosshair; } |
| 12 | + .ham-controls { width: 300px; } |
| 13 | + .ham-panel { background: #1e293b; border-radius: 8px; padding: 16px; margin-bottom: 16px; } |
| 14 | + .ham-panel h3 { margin: 0 0 12px; color: #94a3b8; font-size: 13px; text-transform: uppercase; letter-spacing: 1px; } |
| 15 | + .ham-btn { display: inline-block; padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600; margin: 3px; transition: all .15s; } |
| 16 | + .ham-btn-primary { background: #3b82f6; color: #fff; } |
| 17 | + .ham-btn-primary:hover { background: #2563eb; } |
| 18 | + .ham-btn-success { background: #10b981; color: #fff; } |
| 19 | + .ham-btn-success:hover { background: #059669; } |
| 20 | + .ham-btn-danger { background: #ef4444; color: #fff; } |
| 21 | + .ham-btn-danger:hover { background: #dc2626; } |
| 22 | + .ham-btn-warn { background: #f59e0b; color: #fff; } |
| 23 | + .ham-btn-warn:hover { background: #d97706; } |
| 24 | + .ham-btn-sm { padding: 5px 10px; font-size: 12px; } |
| 25 | + .ham-btn:disabled { opacity: .4; cursor: not-allowed; } |
| 26 | + .ham-preset { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; } |
| 27 | + .ham-log { background: #0f172a; border-radius: 6px; padding: 10px; font-family: 'Consolas','Courier New',monospace; font-size: 12px; color: #94a3b8; max-height: 200px; overflow-y: auto; line-height: 1.5; } |
| 28 | + .ham-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } |
| 29 | + .ham-stat { background: #0f172a; border-radius: 6px; padding: 10px; text-align: center; } |
| 30 | + .ham-stat .val { font-size: 22px; font-weight: 700; color: #e2e8f0; } |
| 31 | + .ham-stat .lbl { font-size: 11px; color: #64748b; margin-top: 2px; } |
| 32 | + .ham-speed { width: 100%; margin: 8px 0; } |
| 33 | + .ham-result { padding: 10px; border-radius: 6px; margin-top: 10px; font-weight: 600; text-align: center; } |
| 34 | + .ham-result.found { background: #064e3b; color: #34d399; } |
| 35 | + .ham-result.not-found { background: #7f1d1d; color: #fca5a5; } |
| 36 | + .ham-result.running { background: #1e3a5f; color: #93c5fd; } |
| 37 | + .ham-legend { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 8px; } |
| 38 | + .ham-legend-item { display: flex; align-items: center; gap: 5px; font-size: 12px; color: #94a3b8; } |
| 39 | + .ham-legend-dot { width: 12px; height: 12px; border-radius: 50%; } |
| 40 | + select, input[type=number] { background: #0f172a; color: #e2e8f0; border: 1px solid #334155; border-radius: 4px; padding: 6px 8px; font-size: 13px; } |
| 41 | + .ham-mode-toggle { display: flex; border-radius: 6px; overflow: hidden; border: 1px solid #334155; } |
| 42 | + .ham-mode-toggle button { flex: 1; padding: 8px; border: none; background: #0f172a; color: #94a3b8; cursor: pointer; font-size: 13px; font-weight: 600; } |
| 43 | + .ham-mode-toggle button.active { background: #3b82f6; color: #fff; } |
| 44 | + .ham-instructions { font-size: 12px; color: #64748b; margin-top: 8px; line-height: 1.6; } |
| 45 | + </style> |
| 46 | +</head> |
| 47 | +<body> |
| 48 | +<nav class="sidebar"> |
| 49 | + <a href="index.html" class="sidebar-logo"><span>📊</span><div><h2>GraphVisual</h2><small>Documentation</small></div></a> |
| 50 | + <a href="index.html" class="sidebar-link"><span class="icon">🏠</span>Home</a> |
| 51 | + <a href="hamiltonian.html" class="sidebar-link active"><span class="icon">🔁</span>Hamiltonian</a> |
| 52 | + <a href="euler.html" class="sidebar-link"><span class="icon">🛤️</span>Euler Path</a> |
| 53 | + <a href="pathfinder.html" class="sidebar-link"><span class="icon">🧭</span>Path Finder</a> |
| 54 | + <a href="coloring.html" class="sidebar-link"><span class="icon">🎨</span>Coloring</a> |
| 55 | + <a href="planarity.html" class="sidebar-link"><span class="icon">📐</span>Planarity</a> |
| 56 | +</nav> |
| 57 | +<main class="content"> |
| 58 | +<h1>🔁 Hamiltonian Path & Circuit Finder</h1> |
| 59 | +<p>Find Hamiltonian paths and circuits using backtracking with pruning. Watch the algorithm explore and backtrack in real-time.</p> |
| 60 | + |
| 61 | +<div class="ham-container"> |
| 62 | + <div class="ham-canvas-wrap"> |
| 63 | + <canvas id="hamCanvas" width="700" height="500"></canvas> |
| 64 | + <div class="ham-legend"> |
| 65 | + <div class="ham-legend-item"><div class="ham-legend-dot" style="background:#64748b"></div>Unvisited</div> |
| 66 | + <div class="ham-legend-item"><div class="ham-legend-dot" style="background:#3b82f6"></div>Current</div> |
| 67 | + <div class="ham-legend-item"><div class="ham-legend-dot" style="background:#10b981"></div>In Path</div> |
| 68 | + <div class="ham-legend-item"><div class="ham-legend-dot" style="background:#ef4444"></div>Backtracked</div> |
| 69 | + <div class="ham-legend-item"><div class="ham-legend-dot" style="background:#fbbf24"></div>Solution</div> |
| 70 | + </div> |
| 71 | + <div class="ham-instructions"> |
| 72 | + <strong>Click</strong> canvas to add nodes. <strong>Drag</strong> between nodes to add edges. <strong>Right-click</strong> node to delete. <strong>Shift+click</strong> node to set start. |
| 73 | + </div> |
| 74 | + </div> |
| 75 | + <div class="ham-controls"> |
| 76 | + <div class="ham-panel"> |
| 77 | + <h3>Mode</h3> |
| 78 | + <div class="ham-mode-toggle"> |
| 79 | + <button id="modePath" class="active" onclick="setMode('path')">Path</button> |
| 80 | + <button id="modeCircuit" onclick="setMode('circuit')">Circuit</button> |
| 81 | + </div> |
| 82 | + </div> |
| 83 | + <div class="ham-panel"> |
| 84 | + <h3>Presets</h3> |
| 85 | + <div class="ham-preset"> |
| 86 | + <button class="ham-btn ham-btn-primary ham-btn-sm" onclick="loadPreset('petersen')">Petersen</button> |
| 87 | + <button class="ham-btn ham-btn-primary ham-btn-sm" onclick="loadPreset('cube')">Cube</button> |
| 88 | + <button class="ham-btn ham-btn-primary ham-btn-sm" onclick="loadPreset('dodecahedron')">Dodecahedron</button> |
| 89 | + <button class="ham-btn ham-btn-primary ham-btn-sm" onclick="loadPreset('complete5')">K₅</button> |
| 90 | + <button class="ham-btn ham-btn-primary ham-btn-sm" onclick="loadPreset('cycle8')">C₈</button> |
| 91 | + <button class="ham-btn ham-btn-primary ham-btn-sm" onclick="loadPreset('bipartite')">K₃,₃</button> |
| 92 | + <button class="ham-btn ham-btn-primary ham-btn-sm" onclick="loadPreset('herschel')">Herschel</button> |
| 93 | + <button class="ham-btn ham-btn-primary ham-btn-sm" onclick="loadPreset('wheel6')">Wheel₆</button> |
| 94 | + </div> |
| 95 | + </div> |
| 96 | + <div class="ham-panel"> |
| 97 | + <h3>Animation Speed</h3> |
| 98 | + <input type="range" id="speedSlider" class="ham-speed" min="1" max="100" value="50"> |
| 99 | + <div style="display:flex;justify-content:space-between;font-size:11px;color:#64748b"><span>Slow</span><span>Fast</span></div> |
| 100 | + </div> |
| 101 | + <div class="ham-panel"> |
| 102 | + <h3>Actions</h3> |
| 103 | + <button class="ham-btn ham-btn-success" id="btnRun" onclick="runSolver()">▶ Find</button> |
| 104 | + <button class="ham-btn ham-btn-warn" id="btnPause" onclick="togglePause()" disabled>⏸ Pause</button> |
| 105 | + <button class="ham-btn ham-btn-danger" id="btnStop" onclick="stopSolver()" disabled>⏹ Stop</button> |
| 106 | + <button class="ham-btn ham-btn-primary" onclick="clearGraph()">🗑 Clear</button> |
| 107 | + <div id="resultBox"></div> |
| 108 | + </div> |
| 109 | + <div class="ham-panel"> |
| 110 | + <h3>Statistics</h3> |
| 111 | + <div class="ham-stats"> |
| 112 | + <div class="ham-stat"><div class="val" id="statNodes">0</div><div class="lbl">Nodes</div></div> |
| 113 | + <div class="ham-stat"><div class="val" id="statEdges">0</div><div class="lbl">Edges</div></div> |
| 114 | + <div class="ham-stat"><div class="val" id="statSteps">0</div><div class="lbl">Steps</div></div> |
| 115 | + <div class="ham-stat"><div class="val" id="statBacktracks">0</div><div class="lbl">Backtracks</div></div> |
| 116 | + </div> |
| 117 | + </div> |
| 118 | + <div class="ham-panel"> |
| 119 | + <h3>Algorithm Log</h3> |
| 120 | + <div class="ham-log" id="logBox">Ready. Add nodes or load a preset.</div> |
| 121 | + </div> |
| 122 | + </div> |
| 123 | +</div> |
| 124 | + |
| 125 | +<script> |
| 126 | +const canvas = document.getElementById('hamCanvas'); |
| 127 | +const ctx = canvas.getContext('2d'); |
| 128 | +let nodes = [], edges = [], mode = 'path', startNode = 0; |
| 129 | +let solving = false, paused = false, stopRequested = false; |
| 130 | +let steps = 0, backtracks = 0; |
| 131 | +let visitState = [], pathEdges = [], currentPath = []; |
| 132 | +let dragFrom = null, dragPos = null, hoverNode = -1; |
| 133 | + |
| 134 | +const COLORS = { unvisited: '#64748b', current: '#3b82f6', inPath: '#10b981', backtracked: '#ef4444', solution: '#fbbf24' }; |
| 135 | + |
| 136 | +function delay() { const v = document.getElementById('speedSlider').value; return Math.max(5, 500 - v * 4.9); } |
| 137 | + |
| 138 | +function draw() { |
| 139 | + ctx.clearRect(0, 0, canvas.width, canvas.height); |
| 140 | + // edges |
| 141 | + edges.forEach(([a, b], i) => { |
| 142 | + const na = nodes[a], nb = nodes[b]; |
| 143 | + const isPath = pathEdges.includes(i); |
| 144 | + const isSolution = visitState.solution; |
| 145 | + ctx.beginPath(); ctx.moveTo(na.x, na.y); ctx.lineTo(nb.x, nb.y); |
| 146 | + ctx.strokeStyle = isPath ? (isSolution ? COLORS.solution : COLORS.inPath) : '#334155'; |
| 147 | + ctx.lineWidth = isPath ? 3 : 1.5; |
| 148 | + ctx.stroke(); |
| 149 | + }); |
| 150 | + // drag line |
| 151 | + if (dragFrom !== null && dragPos) { |
| 152 | + ctx.beginPath(); ctx.moveTo(nodes[dragFrom].x, nodes[dragFrom].y); |
| 153 | + ctx.lineTo(dragPos.x, dragPos.y); |
| 154 | + ctx.strokeStyle = '#3b82f6'; ctx.lineWidth = 2; ctx.setLineDash([5,5]); ctx.stroke(); ctx.setLineDash([]); |
| 155 | + } |
| 156 | + // nodes |
| 157 | + nodes.forEach((n, i) => { |
| 158 | + let color = COLORS.unvisited; |
| 159 | + if (visitState.solution && currentPath.includes(i)) color = COLORS.solution; |
| 160 | + else if (visitState[i] === 'current') color = COLORS.current; |
| 161 | + else if (visitState[i] === 'inPath') color = COLORS.inPath; |
| 162 | + else if (visitState[i] === 'backtracked') color = COLORS.backtracked; |
| 163 | + ctx.beginPath(); ctx.arc(n.x, n.y, i === startNode ? 18 : 15, 0, Math.PI * 2); |
| 164 | + ctx.fillStyle = color; ctx.fill(); |
| 165 | + ctx.strokeStyle = i === hoverNode ? '#fff' : '#1e293b'; ctx.lineWidth = 2; ctx.stroke(); |
| 166 | + ctx.fillStyle = '#fff'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; |
| 167 | + ctx.fillText(i.toString(), n.x, n.y); |
| 168 | + if (i === startNode) { ctx.fillStyle = '#fbbf24'; ctx.font = '10px sans-serif'; ctx.fillText('★', n.x, n.y - 22); } |
| 169 | + }); |
| 170 | + updateStats(); |
| 171 | +} |
| 172 | + |
| 173 | +function updateStats() { |
| 174 | + document.getElementById('statNodes').textContent = nodes.length; |
| 175 | + document.getElementById('statEdges').textContent = edges.length; |
| 176 | + document.getElementById('statSteps').textContent = steps; |
| 177 | + document.getElementById('statBacktracks').textContent = backtracks; |
| 178 | +} |
| 179 | + |
| 180 | +function log(msg) { |
| 181 | + const box = document.getElementById('logBox'); |
| 182 | + box.innerHTML += '\n' + msg; |
| 183 | + box.scrollTop = box.scrollHeight; |
| 184 | +} |
| 185 | + |
| 186 | +function setMode(m) { |
| 187 | + mode = m; |
| 188 | + document.getElementById('modePath').classList.toggle('active', m === 'path'); |
| 189 | + document.getElementById('modeCircuit').classList.toggle('active', m === 'circuit'); |
| 190 | +} |
| 191 | + |
| 192 | +function nodeAt(x, y) { return nodes.findIndex(n => Math.hypot(n.x - x, n.y - y) < 18); } |
| 193 | +function edgeExists(a, b) { return edges.some(([u, v]) => (u === a && v === b) || (u === b && v === a)); } |
| 194 | +function neighbors(n) { return edges.reduce((acc, [a, b], i) => { if (a === n) acc.push({node: b, edge: i}); if (b === n) acc.push({node: a, edge: i}); return acc; }, []); } |
| 195 | + |
| 196 | +canvas.addEventListener('mousedown', e => { |
| 197 | + const r = canvas.getBoundingClientRect(); |
| 198 | + const x = (e.offsetX / r.width) * canvas.width, y = (e.offsetY / r.height) * canvas.height; |
| 199 | + const idx = nodeAt(x, y); |
| 200 | + if (e.button === 2) { e.preventDefault(); if (idx >= 0) { deleteNode(idx); } return; } |
| 201 | + if (e.shiftKey && idx >= 0) { startNode = idx; log(`Start node → ${idx}`); draw(); return; } |
| 202 | + if (idx >= 0) { dragFrom = idx; } else { nodes.push({x, y}); log(`Added node ${nodes.length-1}`); draw(); } |
| 203 | +}); |
| 204 | +canvas.addEventListener('mousemove', e => { |
| 205 | + const r = canvas.getBoundingClientRect(); |
| 206 | + const x = (e.offsetX / r.width) * canvas.width, y = (e.offsetY / r.height) * canvas.height; |
| 207 | + hoverNode = nodeAt(x, y); |
| 208 | + if (dragFrom !== null) { dragPos = {x, y}; draw(); } |
| 209 | +}); |
| 210 | +canvas.addEventListener('mouseup', e => { |
| 211 | + if (dragFrom !== null) { |
| 212 | + const r = canvas.getBoundingClientRect(); |
| 213 | + const x = (e.offsetX / r.width) * canvas.width, y = (e.offsetY / r.height) * canvas.height; |
| 214 | + const idx = nodeAt(x, y); |
| 215 | + if (idx >= 0 && idx !== dragFrom && !edgeExists(dragFrom, idx)) { |
| 216 | + edges.push([dragFrom, idx]); log(`Edge ${dragFrom}—${idx}`); |
| 217 | + } |
| 218 | + } |
| 219 | + dragFrom = null; dragPos = null; draw(); |
| 220 | +}); |
| 221 | +canvas.addEventListener('contextmenu', e => e.preventDefault()); |
| 222 | + |
| 223 | +function deleteNode(idx) { |
| 224 | + nodes.splice(idx, 1); |
| 225 | + edges = edges.filter(([a, b]) => a !== idx && b !== idx).map(([a, b]) => [a > idx ? a-1 : a, b > idx ? b-1 : b]); |
| 226 | + if (startNode >= nodes.length) startNode = 0; |
| 227 | + if (startNode === idx) startNode = 0; |
| 228 | + else if (startNode > idx) startNode--; |
| 229 | + log(`Deleted node ${idx}`); draw(); |
| 230 | +} |
| 231 | + |
| 232 | +function clearGraph() { nodes = []; edges = []; visitState = []; pathEdges = []; currentPath = []; steps = 0; backtracks = 0; startNode = 0; document.getElementById('logBox').innerHTML = 'Cleared.'; document.getElementById('resultBox').innerHTML = ''; draw(); } |
| 233 | + |
| 234 | +function showResult(found, msg) { |
| 235 | + const box = document.getElementById('resultBox'); |
| 236 | + box.className = 'ham-result ' + (found ? 'found' : 'not-found'); |
| 237 | + box.textContent = msg; |
| 238 | +} |
| 239 | + |
| 240 | +async function runSolver() { |
| 241 | + if (solving) return; |
| 242 | + if (nodes.length < 2) { log('Need at least 2 nodes.'); return; } |
| 243 | + solving = true; paused = false; stopRequested = false; steps = 0; backtracks = 0; |
| 244 | + visitState = new Array(nodes.length).fill('unvisited'); pathEdges = []; currentPath = []; |
| 245 | + document.getElementById('btnRun').disabled = true; |
| 246 | + document.getElementById('btnPause').disabled = false; |
| 247 | + document.getElementById('btnStop').disabled = false; |
| 248 | + document.getElementById('resultBox').innerHTML = '<div class="ham-result running">Searching...</div>'; |
| 249 | + log(`\n--- Finding Hamiltonian ${mode} from node ${startNode} ---`); |
| 250 | + |
| 251 | + const found = await hamiltonian(startNode); |
| 252 | + if (stopRequested) { log('Stopped by user.'); } |
| 253 | + else if (found) { |
| 254 | + visitState.solution = true; |
| 255 | + showResult(true, `✅ Hamiltonian ${mode} found! (${steps} steps, ${backtracks} backtracks)`); |
| 256 | + log(`✅ Found: ${currentPath.join(' → ')}${mode === 'circuit' ? ' → ' + currentPath[0] : ''}`); |
| 257 | + } else { |
| 258 | + showResult(false, `❌ No Hamiltonian ${mode} exists (${steps} steps, ${backtracks} backtracks)`); |
| 259 | + log('❌ No solution exists.'); |
| 260 | + } |
| 261 | + draw(); |
| 262 | + solving = false; |
| 263 | + document.getElementById('btnRun').disabled = false; |
| 264 | + document.getElementById('btnPause').disabled = true; |
| 265 | + document.getElementById('btnStop').disabled = true; |
| 266 | +} |
| 267 | + |
| 268 | +async function hamiltonian(node) { |
| 269 | + if (stopRequested) return false; |
| 270 | + while (paused) await new Promise(r => setTimeout(r, 100)); |
| 271 | + |
| 272 | + currentPath.push(node); |
| 273 | + visitState[node] = 'current'; |
| 274 | + steps++; |
| 275 | + draw(); |
| 276 | + await new Promise(r => setTimeout(r, delay())); |
| 277 | + |
| 278 | + if (currentPath.length === nodes.length) { |
| 279 | + if (mode === 'path') { visitState[node] = 'inPath'; return true; } |
| 280 | + // circuit: check edge back to start |
| 281 | + if (edgeExists(node, startNode)) { |
| 282 | + const ei = edges.findIndex(([a,b]) => (a===node&&b===startNode)||(a===startNode&&b===node)); |
| 283 | + if (ei >= 0) pathEdges.push(ei); |
| 284 | + visitState[node] = 'inPath'; |
| 285 | + return true; |
| 286 | + } |
| 287 | + } |
| 288 | + |
| 289 | + if (currentPath.length < nodes.length) { |
| 290 | + // pruning: check if unvisited nodes are still reachable |
| 291 | + const nbrs = neighbors(node).filter(n => !currentPath.includes(n.node)); |
| 292 | + // sort by degree (Warnsdorff-like heuristic) |
| 293 | + nbrs.sort((a, b) => { |
| 294 | + const da = neighbors(a.node).filter(n => !currentPath.includes(n.node)).length; |
| 295 | + const db = neighbors(b.node).filter(n => !currentPath.includes(n.node)).length; |
| 296 | + return da - db; |
| 297 | + }); |
| 298 | + for (const {node: next, edge: ei} of nbrs) { |
| 299 | + visitState[node] = 'inPath'; |
| 300 | + pathEdges.push(ei); |
| 301 | + draw(); |
| 302 | + if (await hamiltonian(next)) return true; |
| 303 | + pathEdges.pop(); |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + // backtrack |
| 308 | + currentPath.pop(); |
| 309 | + visitState[node] = 'backtracked'; |
| 310 | + backtracks++; |
| 311 | + steps++; |
| 312 | + log(`↩ Backtrack from ${node}`); |
| 313 | + draw(); |
| 314 | + await new Promise(r => setTimeout(r, delay() / 2)); |
| 315 | + return false; |
| 316 | +} |
| 317 | + |
| 318 | +function togglePause() { |
| 319 | + paused = !paused; |
| 320 | + document.getElementById('btnPause').textContent = paused ? '▶ Resume' : '⏸ Pause'; |
| 321 | + if (!paused) log('Resumed.'); |
| 322 | + else log('Paused.'); |
| 323 | +} |
| 324 | +function stopSolver() { stopRequested = true; } |
| 325 | + |
| 326 | +// Presets |
| 327 | +function loadPreset(name) { |
| 328 | + clearGraph(); |
| 329 | + const cx = canvas.width / 2, cy = canvas.height / 2; |
| 330 | + const ring = (n, r, ox=0, oy=0) => Array.from({length: n}, (_, i) => ({x: cx + ox + r * Math.cos(2*Math.PI*i/n - Math.PI/2), y: cy + oy + r * Math.sin(2*Math.PI*i/n - Math.PI/2)})); |
| 331 | + |
| 332 | + if (name === 'petersen') { |
| 333 | + nodes = [...ring(5, 180), ...ring(5, 80)]; |
| 334 | + edges = [[0,1],[1,2],[2,3],[3,4],[4,0],[5,7],[7,9],[9,6],[6,8],[8,5],[0,5],[1,6],[2,7],[3,8],[4,9]]; |
| 335 | + log('Loaded Petersen graph (no Hamiltonian circuit!)'); |
| 336 | + } else if (name === 'cube') { |
| 337 | + nodes = [{x:cx-100,y:cy-100},{x:cx+100,y:cy-100},{x:cx+100,y:cy+100},{x:cx-100,y:cy+100},{x:cx-50,y:cy-50},{x:cx+150,y:cy-50},{x:cx+150,y:cy+150},{x:cx-50,y:cy+150}]; |
| 338 | + edges = [[0,1],[1,2],[2,3],[3,0],[4,5],[5,6],[6,7],[7,4],[0,4],[1,5],[2,6],[3,7]]; |
| 339 | + log('Loaded Cube graph (Q₃)'); |
| 340 | + } else if (name === 'dodecahedron') { |
| 341 | + nodes = [...ring(10, 200), ...ring(10, 110)]; |
| 342 | + edges = [[0,1],[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,8],[8,9],[9,0],[0,10],[1,11],[2,12],[3,13],[4,14],[5,15],[6,16],[7,17],[8,18],[9,19],[10,11],[12,13],[14,15],[16,17],[18,19],[11,12],[13,14],[15,16],[17,18],[19,10]]; |
| 343 | + log('Loaded Dodecahedron'); |
| 344 | + } else if (name === 'complete5') { |
| 345 | + nodes = ring(5, 150); |
| 346 | + edges = [[0,1],[0,2],[0,3],[0,4],[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]; |
| 347 | + log('Loaded K₅ (complete graph)'); |
| 348 | + } else if (name === 'cycle8') { |
| 349 | + nodes = ring(8, 170); |
| 350 | + edges = [[0,1],[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,0]]; |
| 351 | + log('Loaded C₈ (cycle)'); |
| 352 | + } else if (name === 'bipartite') { |
| 353 | + nodes = [{x:cx-120,y:cy-120},{x:cx-120,y:cy},{x:cx-120,y:cy+120},{x:cx+120,y:cy-120},{x:cx+120,y:cy},{x:cx+120,y:cy+120}]; |
| 354 | + edges = [[0,3],[0,4],[0,5],[1,3],[1,4],[1,5],[2,3],[2,4],[2,5]]; |
| 355 | + log('Loaded K₃,₃ (complete bipartite)'); |
| 356 | + } else if (name === 'herschel') { |
| 357 | + nodes = [{x:cx,y:cy-180},{x:cx-150,y:cy-80},{x:cx+150,y:cy-80},{x:cx-180,y:cy+40},{x:cx+180,y:cy+40},{x:cx-100,y:cy+40},{x:cx+100,y:cy+40},{x:cx-60,y:cy+140},{x:cx+60,y:cy+140},{x:cx,y:cy+100},{x:cx,y:cy+200}]; |
| 358 | + edges = [[0,1],[0,2],[1,3],[1,5],[2,4],[2,6],[3,5],[3,7],[4,6],[4,8],[5,9],[6,9],[7,10],[8,10],[9,7],[9,8]]; |
| 359 | + log('Loaded Herschel graph (no Hamiltonian circuit!)'); |
| 360 | + } else if (name === 'wheel6') { |
| 361 | + const outer = ring(6, 170); |
| 362 | + nodes = [{x:cx,y:cy}, ...outer]; |
| 363 | + edges = [[1,2],[2,3],[3,4],[4,5],[5,6],[6,1],[0,1],[0,2],[0,3],[0,4],[0,5],[0,6]]; |
| 364 | + log('Loaded Wheel₆'); |
| 365 | + } |
| 366 | + draw(); |
| 367 | +} |
| 368 | + |
| 369 | +loadPreset('cube'); |
| 370 | +</script> |
| 371 | +</main> |
| 372 | +</body> |
| 373 | +</html> |
0 commit comments