Seven Bridges of Königsberg

SeniorMathematics
Graph Theory · Seven Bridges of Königsberg

.kb-wrap{--bg:#0b1020;--panel:#121a33;--muted:#8fa0c9;--accent:#7aa2ff;--accent2:#00d6c9;--bridge:#e7c46a;--bridgeEdge:#a6852f;--used:#ff6f61;--node:#ffe08a;--nodeStroke:#2f3651;--good:#1cc69c;--bad:#ff5c7a; font-family:…

Learn the methodRead guide

.kb-wrap{--bg:#0b1020;--panel:#121a33;--muted:#8fa0c9;--accent:#7aa2ff;--accent2:#00d6c9;--bridge:#e7c46a;--bridgeEdge:#a6852f;--used:#ff6f61;--node:#ffe08a;--nodeStroke:#2f3651;--good:#1cc69c;--bad:#ff5c7a; font-family

,-apple-system,Segoe UI,Roboto,Inter,Arial,sans-serif; color:#e9eefb; background
(180deg,#0b1020,#0a0f1f); border-radius
; padding
; box-shadow
10px 30px rgba(0,0,0,.35)} .kb-header{display
;gap
;align-items
;justify-content
;margin-bottom
;flex-wrap
} .kb-title{font-weight
;font-size
;letter-spacing:.2px} .kb-controls{display
;gap
;align-items
;flex-wrap
} .kb-btn{appearance
;border
;border-radius
;padding
12px;background:#1a2344;color:#e9eefb;cursor
;font-weight
} .kb-btn
{transform
(1px)} .kb-btn--accent{background
(135deg,var(--accent),var(--accent2));color:#04122b} .kb-counter{font-weight
;opacity:.9} .kb-note{font-size
;color
(--muted);margin
0 10px} .kb-board{background
(1200px 600px at 60% 50%,#0f1733 0%,#0b1020 70%); border
solid #1a2650;border-radius
;padding
} .kb-svg{width
%;height
;display
;max-width
;margin
} /* rivers (just decorative) / .water{fill:#0a6ebd22;stroke:#0a6ebd44;stroke-width
} /
bridges / .bridge{stroke
(--bridge);stroke-width
;fill
;stroke-linecap
;filter
(0 0 2px #000)} .bridge-edge{stroke
(--bridgeEdge);stroke-width
;fill
;stroke-linecap
;opacity:.65} .bridge.used{stroke
(--used)} .bridge
{filter
(0 0 4px rgba(255,255,255,.35))} /
nodes / .node circle{fill
(--node);stroke
(--nodeStroke);stroke-width
} .node text{font-weight
;fill:#0b1020} .node.active circle{stroke
(--accent);stroke-width
} .hint-edge{stroke-dasharray
10; animation
1.2s linear infinite} @keyframes dash{to{stroke-dashoffset:-16}} /
toast */ .kb-toast{margin-top
;font-size
;padding
12px;border-radius
;background:#0f1631;border
solid #1d2a58;display
} .kb-toast.show{display
} .kb-toast.good{border-color:#175e4f;background:#0c1f23;color:#b7ffec} .kb-toast.bad{border-color:#5a2231;background:#25101a;color:#ffc7d2} details.kb-proof{margin-top
;background:#0f1631;border
solid #1d2a58;border-radius
;padding
12px} details.kb-proof summary{cursor
;font-weight
}

The Seven Bridges of Königsberg – Try to cross each bridge once

0 / 7 bridges Undo Reset Hint

Drag from one landmass to another (or tap a node, then tap the next). A bridge turns red once you’ve crossed it. You can’t reuse bridges.

Why it’s impossible (Euler’s 1736 result) In graph terms, each landmass is a vertex and each bridge is an edge. A route that uses every edge exactly once is an Eulerian trail. Such a trail exists only if the graph has either 0 or 2 vertices of odd degree (odd number of incident edges). In Königsberg there are four landmasses and all four have an odd degree, so no Eulerian trail exists. You’ll always get stuck with bridges remaining!

(function(){ const svg = document.querySelector('.kb-svg'); const nodeLayer = document.getElementById('nodes'); const edgeShadowLayer = document.getElementById('bridge-edges'); const edgeLayer = document.getElementById('bridges'); const counterEl = document.getElementById('kb-count'); const toastEl = document.getElementById('kb-toast'); const btnReset = document.getElementById('kb-reset'); const btnUndo = document.getElementById('kb-undo'); const btnHint = document.getElementById('kb-hint');

// --- Layout (you can tweak these) --- const nodeDefs = { A:{x

, y
, label:'North Bank'}, B:{x
, y
, label:'South Bank'}, C:{x
, y
, label:'Kneiphof Island'}, D:{x
, y
, label:'Lomse Island'} }; // Seven bridges as edges between nodes; bend controls curve amount (+/-) const edgeDefs = [ {id:'e1', u:'A', v:'C', bend:-60, label:'1'}, {id:'e2', u:'A', v:'C', bend: 60, label:'2'}, {id:'e3', u:'A', v:'D', bend:-20, label:'3'}, {id:'e4', u:'A', v:'B', bend: 0, label:'4'}, {id:'e5', u:'B', v:'C', bend:-60, label:'5'}, {id:'e6', u:'B', v:'C', bend: 60, label:'6'}, {id:'e7', u:'B', v:'D', bend: 30, label:'7'} ]; const TOTAL = edgeDefs.length;

// --- Helpers to make curved paths between nodes --- function qCurve(u,v,bend){ const p1 = nodeDefs[u], p2 = nodeDefs[v]; const mx=(p1.x+p2.x)/2, my=(p1.y+p2.y)/2; const dx=p2.x-p1.x, dy=p2.y-p1.y; const len=Math.hypot(dx,dy)||1; const nx=-dy/len, ny=dx/len; // unit normal const cx=mx+nxbend, cy=my+nybend; return {d:M ${p1.x} ${p1.y} Q ${cx} ${cy} ${p2.x} ${p2.y}, cx, cy}; }

// --- Build nodes --- const nodes = {}; Object.entries(nodeDefs).forEach(([id,p])=>{ const g = document.createElementNS('http://www.w3.org/2000/svg','g'); g.classList.add('node'); g.setAttribute('data-id',id); g.setAttribute('tabindex','0'); g.setAttribute('role','button'); g.setAttribute('aria-label', p.label); const circle = document.createElementNS(svg.namespaceURI,'circle'); circle.setAttribute('cx',p.x); circle.setAttribute('cy',p.y); circle.setAttribute('r',16); const text = document.createElementNS(svg.namespaceURI,'text'); text.setAttribute('x',p.x); text.setAttribute('y',p.y+5); text.setAttribute('text-anchor','middle'); text.setAttribute('font-size','12'); text.textContent = id; g.appendChild(circle); g.appendChild(text); nodeLayer.appendChild(g); nodes[id]=g; });

// --- Build bridges --- const edges = edgeDefs.map(def=>{ const curve = qCurve(def.u, def.v, def.bend); const shadow = document.createElementNS(svg.namespaceURI,'path'); shadow.setAttribute('d',curve.d); shadow.classList.add('bridge-edge');

const path = document.createElementNS(svg.namespaceURI,'path'); path.setAttribute('d',curve.d); path.setAttribute('id',def.id); path.classList.add('bridge'); path.dataset.u=def.u; path.dataset.v=def.v; path.dataset.used="0";

// (Optional) small label for the bridge number const label = document.createElementNS(svg.namespaceURI,'text'); label.setAttribute('font-size','11'); label.setAttribute('text-anchor','middle'); label.setAttribute('x',curve.cx || 0); label.setAttribute('y',(nodeDefs[def.u].y + nodeDefs[def.v].y)/2 + (def.bend>=0?14:-8)); label.setAttribute('fill','#b8c4f8'); label.setAttribute('opacity','.75'); label.textContent = def.label || '';

edgeShadowLayer.appendChild(shadow); edgeLayer.appendChild(path); edgeLayer.appendChild(label); return path; });

// --- Game state --- let currentNode = null; // where the “pen” is let history = []; // stack of {edgeId, from, to} let showHints = false;

function setToast(msg,type=''){ toastEl.textContent = msg||''; toastEl.className = 'kb-toast ' + (type||''); if(msg) toastEl.classList.add('show'); else toastEl.classList.remove('show'); }

function updateCounter(){ const used = edges.filter(e=>e.dataset.used==="1").length; counterEl.textContent = ${used} / ${TOTAL} bridges; }

function clearHighlights(){ Object.values(nodes).forEach(n=>n.classList.remove('active')); // remove hint styling edges.forEach(e=>e.classList.remove('hint-edge')); }

function availableEdges(from){ return edges.filter(e=>{ const u=e.dataset.u, v=e.dataset.v; return (u===from || v===from) && e.dataset.used==="0"; }); }

function traverseEdgeBetween(a,b){ // pick any unused edge between a and b const candidate = edges.find(e=>{ const u=e.dataset.u, v=e.dataset.v; return e.dataset.used==="0" && ((u===a && v===b) || (u===b && v===a)); }); if(!candidate) return false; candidate.dataset.used="1"; candidate.classList.add('used'); history.push({edgeId

.id, from
, to
}); currentNode = b; // continue from destination updateCounter(); const stillAvail = availableEdges(currentNode).length; if(stillAvail===0 && edges.some(e=>e.dataset.used==="0")){ setToast("You're stuck and bridges remain… that’s the Königsberg trap!", 'bad'); }else{ setToast(''); } return true; }

function resetBoard(){ history = []; currentNode = null; edges.forEach(e=>{e.dataset.used="0"; e.classList.remove('used','hint-edge');}); clearHighlights(); updateCounter(); setToast(''); }

function undo(){ const last = history.pop(); if(!last){ setToast('Nothing to undo.'); return; } const e = document.getElementById(last.edgeId); if(e){ e.dataset.used="0"; e.classList.remove('used'); } currentNode = history.length ? history[history.length-1].to : last.from; // move pen back updateCounter(); setToast('Undid last move.',''); if(currentNode) nodes[currentNode].classList.add('active'); }

// --- Node interactions (tap or drag from one node to another) --- Object.entries(nodes).forEach(([id,el])=>{ const start = ()=>{ clearHighlights(); currentNode=id; nodes[id].classList.add('active'); if(showHints){ availableEdges(id).forEach(e=>e.classList.add('hint-edge')); } }; el.addEventListener('pointerdown', start); el.addEventListener('click', function(){ if(currentNode===null){ start(); return; } // If clicked a different node, attempt to traverse if(currentNode && currentNode!==id){ const ok = traverseEdgeBetween(currentNode, id); clearHighlights(); nodes[id].classList.add('active'); if(showHints){ availableEdges(id).forEach(e=>e.classList.add('hint-edge')); } if(!ok) setToast('No unused bridge connects those two landmasses.', 'bad'); } }); });

// global drag end support: if user drags and releases over another node svg.addEventListener('pointerup', (ev)=>{ const target = ev.target.closest('.node'); if(!target) return; const id = target.getAttribute('data-id'); if(currentNode && currentNode!==id){ const ok = traverseEdgeBetween(currentNode, id); clearHighlights(); nodes[id].classList.add('active'); if(showHints){ availableEdges(id).forEach(e=>e.classList.add('hint-edge')); } if(!ok) setToast('No unused bridge connects those two landmasses.', 'bad'); } });

// Controls btnReset.addEventListener('click', resetBoard); btnUndo.addEventListener('click', undo); btnHint.addEventListener('click', ()=>{ showHints = !showHints; btnHint.textContent = showHints ? 'Hide Hint' : 'Hint'; clearHighlights(); if(currentNode && showHints){ nodes[currentNode].classList.add('active'); availableEdges(currentNode).forEach(e=>e.classList.add('hint-edge')); } });

// init updateCounter(); })();