eyeling 1.16.3 → 1.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +0 -1
  2. package/package.json +2 -3
  3. package/arctifacts/README.md +0 -59
  4. package/arctifacts/ackermann.html +0 -678
  5. package/arctifacts/auroracare.html +0 -1297
  6. package/arctifacts/bike-trip.html +0 -752
  7. package/arctifacts/binomial-theorem.html +0 -631
  8. package/arctifacts/bmi.html +0 -511
  9. package/arctifacts/building-performance.html +0 -750
  10. package/arctifacts/clinical-care.html +0 -726
  11. package/arctifacts/collatz.html +0 -403
  12. package/arctifacts/complex.html +0 -321
  13. package/arctifacts/control-system.html +0 -482
  14. package/arctifacts/delfour.html +0 -849
  15. package/arctifacts/earthquake-epicenter.html +0 -982
  16. package/arctifacts/eco-route.html +0 -662
  17. package/arctifacts/euclid-infinitude.html +0 -564
  18. package/arctifacts/euler-identity.html +0 -667
  19. package/arctifacts/exoplanet-transit.html +0 -1000
  20. package/arctifacts/faltings-theorem.html +0 -1046
  21. package/arctifacts/fibonacci.html +0 -299
  22. package/arctifacts/fundamental-theorem-arithmetic.html +0 -398
  23. package/arctifacts/godel-numbering.html +0 -743
  24. package/arctifacts/gps-bike.html +0 -759
  25. package/arctifacts/gps-clinical-bench.html +0 -792
  26. package/arctifacts/graph-french.html +0 -449
  27. package/arctifacts/grass-molecular.html +0 -592
  28. package/arctifacts/group-theory.html +0 -740
  29. package/arctifacts/health-info.html +0 -833
  30. package/arctifacts/kaprekar-constant.html +0 -576
  31. package/arctifacts/lee.html +0 -805
  32. package/arctifacts/linked-lists.html +0 -502
  33. package/arctifacts/lldm.html +0 -612
  34. package/arctifacts/matrix-multiplication.html +0 -502
  35. package/arctifacts/matrix.html +0 -651
  36. package/arctifacts/newton-raphson.html +0 -944
  37. package/arctifacts/peano-factorial.html +0 -456
  38. package/arctifacts/pi.html +0 -363
  39. package/arctifacts/polynomial.html +0 -646
  40. package/arctifacts/prime.html +0 -366
  41. package/arctifacts/pythagorean-theorem.html +0 -468
  42. package/arctifacts/rest-path.html +0 -469
  43. package/arctifacts/roots-of-unity.html +0 -363
  44. package/arctifacts/turing.html +0 -409
  45. package/arctifacts/wind-turbines.html +0 -726
@@ -1,759 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>GPS Bike — Gent → Maasmechelen</title>
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- <style>
8
- :root {
9
- --bg: #f7fafc;
10
- --card: #ffffff;
11
- --ink: #0f172a;
12
- --muted: #475569;
13
- --ok: #16a34a;
14
- --bad: #dc2626;
15
- --accent: #2563eb;
16
- --edge: #93c5fd;
17
- --mway: #cbd5e1;
18
- --path: #1d4ed8;
19
- --node: #020617;
20
- --shadow: 0 8px 20px rgba(2, 6, 23, 0.08);
21
- --radius: 16px;
22
- }
23
- html,
24
- body {
25
- margin: 0;
26
- background: var(--bg);
27
- color: var(--ink);
28
- font-family:
29
- system-ui,
30
- -apple-system,
31
- Segoe UI,
32
- Roboto,
33
- Ubuntu,
34
- Cantarell,
35
- Noto Sans,
36
- sans-serif;
37
- line-height: 1.45;
38
- }
39
- .wrap {
40
- max-width: 1000px;
41
- margin-inline: auto;
42
- padding: 24px;
43
- display: flex;
44
- flex-direction: column;
45
- gap: 20px;
46
- }
47
- header {
48
- display: flex;
49
- flex-direction: column;
50
- gap: 8px;
51
- }
52
- h1 {
53
- font-size: clamp(1.4rem, 2.4vw, 2rem);
54
- margin: 0;
55
- }
56
- .sub {
57
- color: var(--muted);
58
- }
59
- .card {
60
- background: var(--card);
61
- border-radius: var(--radius);
62
- box-shadow: var(--shadow);
63
- padding: 18px;
64
- }
65
- .stack {
66
- display: flex;
67
- flex-direction: column;
68
- gap: 14px;
69
- }
70
- .row {
71
- display: flex;
72
- gap: 10px;
73
- flex-wrap: wrap;
74
- align-items: center;
75
- }
76
- .btn {
77
- border: 0;
78
- border-radius: 12px;
79
- padding: 10px 14px;
80
- font-weight: 600;
81
- cursor: pointer;
82
- background: var(--accent);
83
- color: white;
84
- box-shadow: var(--shadow);
85
- }
86
- .kpi {
87
- display: grid;
88
- grid-template-columns: 1fr 1fr;
89
- gap: 14px;
90
- }
91
- .k {
92
- background: var(--bg);
93
- border-radius: 12px;
94
- padding: 10px 12px;
95
- }
96
- .k .label {
97
- font-size: 0.8rem;
98
- color: var(--muted);
99
- }
100
- .k .val {
101
- font-size: 1.05rem;
102
- font-weight: 700;
103
- }
104
- .checks {
105
- display: grid;
106
- grid-template-columns: 1fr;
107
- gap: 10px;
108
- }
109
- .check {
110
- display: flex;
111
- justify-content: space-between;
112
- align-items: center;
113
- background: var(--bg);
114
- border-radius: 12px;
115
- padding: 10px 12px;
116
- }
117
- .badge {
118
- font-size: 0.8rem;
119
- font-weight: 700;
120
- padding: 4px 8px;
121
- border-radius: 999px;
122
- }
123
- .pass {
124
- background: rgba(22, 163, 74, 0.12);
125
- color: var(--ok);
126
- }
127
- .fail {
128
- background: rgba(220, 38, 38, 0.12);
129
- color: var(--bad);
130
- }
131
- .hint {
132
- color: var(--muted);
133
- font-size: 0.9rem;
134
- }
135
- svg {
136
- width: 100%;
137
- height: 520px;
138
- background: white;
139
- border-radius: var(--radius);
140
- box-shadow: var(--shadow);
141
- }
142
- .legend {
143
- display: flex;
144
- gap: 18px;
145
- align-items: center;
146
- color: var(--muted);
147
- font-size: 0.9rem;
148
- }
149
- .dot {
150
- width: 10px;
151
- height: 10px;
152
- border-radius: 50%;
153
- }
154
- .dot.path {
155
- background: var(--path);
156
- }
157
- .dot.edge {
158
- background: var(--edge);
159
- }
160
- .dot.mway {
161
- background: var(--mway);
162
- }
163
- .dot.node {
164
- background: var(--node);
165
- }
166
- .pill {
167
- display: inline-flex;
168
- align-items: center;
169
- gap: 8px;
170
- background: var(--bg);
171
- border-radius: 999px;
172
- padding: 6px 10px;
173
- }
174
- .pill .seq {
175
- font-weight: 700;
176
- color: var(--accent);
177
- }
178
- code {
179
- background: var(--bg);
180
- padding: 2px 6px;
181
- border-radius: 8px;
182
- }
183
- footer {
184
- color: var(--muted);
185
- font-size: 0.85rem;
186
- }
187
- pre {
188
- white-space: pre-wrap;
189
- background: var(--bg);
190
- padding: 10px;
191
- border-radius: 12px;
192
- margin: 0;
193
- }
194
- </style>
195
- </head>
196
- <body>
197
- <div class="wrap">
198
- <header>
199
- <h1>GPS Bike — Gent → Maasmechelen</h1>
200
- </header>
201
-
202
- <section class="card stack" id="answer">
203
- <div class="row">
204
- <button class="btn" id="run">Compute Optimal Bike Route</button>
205
- <div class="hint">
206
- Shortest path computed on a bike graph (motorways excluded). Weights = great-circle (Haversine) distances.
207
- </div>
208
- </div>
209
- <div class="kpi">
210
- <div class="k">
211
- <div class="label">Start</div>
212
- <div class="val" id="startVal">—</div>
213
- </div>
214
- <div class="k">
215
- <div class="label">Goal</div>
216
- <div class="val" id="goalVal">—</div>
217
- </div>
218
- <div class="k">
219
- <div class="label">Shortest distance (km)</div>
220
- <div class="val" id="distVal">—</div>
221
- </div>
222
- <div class="k">
223
- <div class="label">Stops (nodes)</div>
224
- <div class="val" id="stopsVal">—</div>
225
- </div>
226
- </div>
227
- <div class="row" id="pathList"></div>
228
- </section>
229
-
230
- <section class="card stack" id="reason">
231
- <h3 style="margin: 0">Reason Why</h3>
232
- <div class="hint">
233
- We model cities as nodes and intercity connections as weighted, undirected edges. The
234
- <b>bike graph</b> excludes edges flagged as motorways. We use <b>Dijkstra</b> to find the shortest path, then
235
- verify with <b>A*</b> (admissible straight-line heuristic), <b>Bellman–Ford</b>, and <b>Floyd–Warshall</b>.
236
- </div>
237
- <pre id="reasonText"></pre>
238
- </section>
239
-
240
- <section class="card stack" id="checks">
241
- <h3 style="margin: 0">Independent Checks</h3>
242
- <div class="checks" id="checksList"></div>
243
- </section>
244
-
245
- <section class="card stack">
246
- <h3 style="margin: 0">Route Map (SVG)</h3>
247
- <div class="legend">
248
- <span class="dot edge"></span> bike-allowed edges <span class="dot mway"></span> motorways (not allowed)
249
- <span class="dot path"></span> chosen shortest path <span class="dot node"></span> city
250
- </div>
251
- <svg id="map" viewBox="0 0 1000 520" role="img" aria-label="Belgium bike route map"></svg>
252
- </section>
253
-
254
- <footer>Self-contained; no external libraries. No fixed sequence — the path is discovered automatically.</footer>
255
- </div>
256
-
257
- <script>
258
- // ---------- Cities (approximate centers) ----------
259
- const cities = {
260
- Gent: { lat: 51.0543, lon: 3.7174 },
261
- Lokeren: { lat: 51.1036, lon: 3.9936 },
262
- Dendermonde: { lat: 51.023, lon: 4.101 },
263
- Aalst: { lat: 50.936, lon: 4.0355 },
264
- Mechelen: { lat: 51.0259, lon: 4.4777 },
265
- Vilvoorde: { lat: 50.9281, lon: 4.4294 },
266
- Leuven: { lat: 50.8798, lon: 4.7005 },
267
- Aarschot: { lat: 50.987, lon: 4.8369 },
268
- Tienen: { lat: 50.8074, lon: 4.9367 },
269
- Diest: { lat: 50.989, lon: 5.0508 },
270
- 'Sint-Truiden': { lat: 50.816, lon: 5.1869 },
271
- 'Herk-de-Stad': { lat: 50.9484, lon: 5.1719 },
272
- Hasselt: { lat: 50.9307, lon: 5.3326 },
273
- Genk: { lat: 50.9669, lon: 5.5 },
274
- Zutendaal: { lat: 50.9333, lon: 5.5667 },
275
- Maasmechelen: { lat: 50.9651, lon: 5.6947 },
276
- Antwerpen: { lat: 51.2194, lon: 4.4025 },
277
- Brussel: { lat: 50.8503, lon: 4.3517 },
278
- };
279
-
280
- // ---------- Links: [u, v, {motorway:boolean}] ----------
281
- // Motorways are excluded from the bike graph (still drawn dashed for context).
282
- const links = [
283
- // Bike-allowed backbone & options (canal & regional links)
284
- ['Gent', 'Lokeren', { motorway: false }],
285
- ['Lokeren', 'Dendermonde', { motorway: false }],
286
- ['Gent', 'Aalst', { motorway: false }],
287
- ['Aalst', 'Dendermonde', { motorway: false }],
288
- ['Dendermonde', 'Mechelen', { motorway: false }],
289
- ['Mechelen', 'Leuven', { motorway: false }],
290
- ['Mechelen', 'Aarschot', { motorway: false }],
291
- ['Leuven', 'Aarschot', { motorway: false }],
292
- ['Leuven', 'Tienen', { motorway: false }],
293
- ['Tienen', 'Diest', { motorway: false }],
294
- ['Aarschot', 'Diest', { motorway: false }],
295
- ['Diest', 'Herk-de-Stad', { motorway: false }],
296
- ['Herk-de-Stad', 'Hasselt', { motorway: false }],
297
- ['Diest', 'Sint-Truiden', { motorway: false }],
298
- ['Sint-Truiden', 'Hasselt', { motorway: false }],
299
- ['Hasselt', 'Genk', { motorway: false }],
300
- ['Genk', 'Zutendaal', { motorway: false }],
301
- ['Zutendaal', 'Maasmechelen', { motorway: false }],
302
- ['Genk', 'Maasmechelen', { motorway: false }],
303
- // A few cross options
304
- ['Vilvoorde', 'Mechelen', { motorway: false }],
305
- ['Vilvoorde', 'Leuven', { motorway: false }],
306
- ['Gent', 'Dendermonde', { motorway: false }],
307
- // Motorways (not allowed for bikes)
308
- ['Gent', 'Brussel', { motorway: true }],
309
- ['Brussel', 'Leuven', { motorway: true }],
310
- ['Mechelen', 'Brussel', { motorway: true }],
311
- ['Gent', 'Antwerpen', { motorway: true }],
312
- ['Antwerpen', 'Hasselt', { motorway: true }],
313
- ['Leuven', 'Hasselt', { motorway: true }],
314
- ];
315
-
316
- // ---------- Build graphs ----------
317
- const rad = (d) => (d * Math.PI) / 180;
318
- function haversine(a, b) {
319
- const R = 6371;
320
- const dLat = rad(b.lat - a.lat),
321
- dLon = rad(b.lon - a.lon);
322
- const s = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a.lat)) * Math.cos(rad(b.lat)) * Math.sin(dLon / 2) ** 2;
323
- return 2 * R * Math.asin(Math.sqrt(s));
324
- }
325
-
326
- function buildAdj(includeMotorways) {
327
- const g = {};
328
- for (const [u, v, meta] of links) {
329
- const allow = includeMotorways || !meta.motorway;
330
- if (!allow) continue;
331
- const w = haversine(cities[u], cities[v]);
332
- g[u] = g[u] || {};
333
- g[v] = g[v] || {};
334
- g[u][v] = Math.min(g[u][v] ?? Infinity, w);
335
- g[v][u] = Math.min(g[v][u] ?? Infinity, w);
336
- }
337
- return g;
338
- }
339
-
340
- const bikeGraph = buildAdj(false);
341
- const fullGraph = buildAdj(true);
342
-
343
- // Active graph pointer for algos (set to bike by default)
344
- let graph = bikeGraph;
345
-
346
- // ---------- Shortest path algorithms ----------
347
- function dijkstra(start, goal) {
348
- const dist = {},
349
- prev = {},
350
- Q = new Set(Object.keys(cities));
351
- for (const n of Q) dist[n] = Infinity;
352
- dist[start] = 0;
353
- const steps = [];
354
- while (Q.size) {
355
- let u = null,
356
- best = Infinity;
357
- for (const n of Q) {
358
- if (dist[n] < best) {
359
- best = dist[n];
360
- u = n;
361
- }
362
- }
363
- if (u === null) break;
364
- Q.delete(u);
365
- steps.push({ pick: u, d: dist[u] });
366
- if (u === goal) break;
367
- for (const [v, w] of Object.entries(graph[u] || {})) {
368
- if (!Q.has(v)) continue;
369
- const alt = dist[u] + w;
370
- if (alt < dist[v]) {
371
- dist[v] = alt;
372
- prev[v] = u;
373
- }
374
- }
375
- }
376
- const path = [];
377
- let u = goal;
378
- if (prev[u] || u === start) {
379
- while (u) {
380
- path.unshift(u);
381
- if (u === start) break;
382
- u = prev[u];
383
- }
384
- }
385
- return { distance: dist[goal], path, steps };
386
- }
387
-
388
- function astar(start, goal) {
389
- const h = (n) => haversine(cities[n], cities[goal]);
390
- const open = new Set([start]);
391
- const came = {},
392
- g = {},
393
- f = {};
394
- for (const n in cities) {
395
- g[n] = Infinity;
396
- f[n] = Infinity;
397
- }
398
- g[start] = 0;
399
- f[start] = h(start);
400
- while (open.size) {
401
- let cur = null,
402
- best = Infinity;
403
- open.forEach((n) => {
404
- if (f[n] < best) {
405
- best = f[n];
406
- cur = n;
407
- }
408
- });
409
- if (cur === goal) {
410
- const path = [cur];
411
- let x = cur;
412
- while (came[x]) {
413
- x = came[x];
414
- path.unshift(x);
415
- }
416
- return { distance: g[cur], path };
417
- }
418
- open.delete(cur);
419
- for (const [nbr, w] of Object.entries(graph[cur] || {})) {
420
- const tentative = g[cur] + w;
421
- if (tentative < g[nbr]) {
422
- came[nbr] = cur;
423
- g[nbr] = tentative;
424
- f[nbr] = tentative + h(nbr);
425
- open.add(nbr);
426
- }
427
- }
428
- }
429
- return { distance: Infinity, path: [] };
430
- }
431
-
432
- function bellmanFord(start, goal) {
433
- const nodes = Object.keys(cities);
434
- const dist = {},
435
- prev = {};
436
- nodes.forEach((n) => (dist[n] = Infinity));
437
- dist[start] = 0;
438
- const edges = [];
439
- for (const u in graph) for (const v in graph[u]) edges.push([u, v, graph[u][v]]);
440
- for (let i = 0; i < nodes.length - 1; i++) {
441
- let changed = false;
442
- for (const [u, v, w] of edges) {
443
- if (dist[u] + w < dist[v]) {
444
- dist[v] = dist[u] + w;
445
- prev[v] = u;
446
- changed = true;
447
- }
448
- }
449
- if (!changed) break;
450
- }
451
- const path = [];
452
- let u = goal;
453
- if (prev[u] || u === start) {
454
- while (u) {
455
- path.unshift(u);
456
- if (u === start) break;
457
- u = prev[u];
458
- }
459
- }
460
- return { distance: dist[goal], path };
461
- }
462
-
463
- function floydWarshall() {
464
- const nodes = Object.keys(cities);
465
- const idx = {},
466
- N = nodes.length;
467
- nodes.forEach((n, i) => (idx[n] = i));
468
- const D = Array.from({ length: N }, (_, i) => Array.from({ length: N }, (_, j) => (i === j ? 0 : Infinity)));
469
- for (const u in graph) for (const v in graph[u]) D[idx[u]][idx[v]] = Math.min(D[idx[u]][idx[v]], graph[u][v]);
470
- for (let k = 0; k < N; k++)
471
- for (let i = 0; i < N; i++)
472
- for (let j = 0; j < N; j++) if (D[i][k] + D[k][j] < D[i][j]) D[i][j] = D[i][k] + D[k][j];
473
- return { nodes, idx, D };
474
- }
475
-
476
- // Helper: motorway edge?
477
- function isMotorwayEdge(u, v) {
478
- for (const [a, b, meta] of links) {
479
- if ((a === u && b === v) || (a === v && b === u)) return !!meta.motorway;
480
- }
481
- return false;
482
- }
483
-
484
- // ---------- Checks (independent harness) ----------
485
- function runChecks(result, dA, dB, fw, start, goal, carD) {
486
- const checks = [];
487
- const add = (name, ok, detail) => checks.push({ name, ok, detail: ok ? detail : detail || 'failed' });
488
-
489
- const path = result.path;
490
- const dist = result.distance;
491
-
492
- // 1) Start/End
493
- add(
494
- 'Starts at Gent, ends at Maasmechelen',
495
- path[0] === start && path[path.length - 1] === goal,
496
- `${path[0]} → ${path[path.length - 1]}`,
497
- );
498
-
499
- // 2) Path exists (every hop present)
500
- let edgesOk = true;
501
- for (let i = 0; i < path.length - 1; i++) {
502
- const u = path[i],
503
- v = path[i + 1];
504
- if (!(graph[u] && graph[u][v] > 0)) {
505
- edgesOk = false;
506
- break;
507
- }
508
- }
509
- add('Each hop exists in the bike graph', edgesOk);
510
-
511
- // 3) No motorways used
512
- let noHighways = true,
513
- offender = '';
514
- for (let i = 0; i < path.length - 1; i++) {
515
- const u = path[i],
516
- v = path[i + 1];
517
- if (isMotorwayEdge(u, v)) {
518
- noHighways = false;
519
- offender = `${u}–${v}`;
520
- break;
521
- }
522
- }
523
- add('No motorways (highways) on chosen path', noHighways, offender || 'ok');
524
-
525
- // 4) Distance equals sum of edges
526
- let sum = 0;
527
- for (let i = 0; i < path.length - 1; i++) sum += graph[path[i]][path[i + 1]];
528
- add(
529
- 'Distance equals sum of edge weights',
530
- Math.abs(sum - dist) < 1e-6,
531
- `sum=${sum.toFixed(3)} vs dijkstra=${dist.toFixed(3)}`,
532
- );
533
-
534
- // 5) A* agrees
535
- add('A* distance equals Dijkstra', Math.abs(dA.distance - dist) < 1e-6, `A*=${dA.distance.toFixed(3)} km`);
536
-
537
- // 6) Bellman–Ford agrees
538
- add(
539
- 'Bellman–Ford distance equals Dijkstra',
540
- Math.abs(dB.distance - dist) < 1e-6,
541
- `BF=${dB.distance.toFixed(3)} km`,
542
- );
543
-
544
- // 7) Floyd–Warshall global optimum matches
545
- const i = fw.idx[start],
546
- j = fw.idx[goal];
547
- add('Floyd–Warshall (all-pairs) matches', Math.abs(fw.D[i][j] - dist) < 1e-6, `FW=${fw.D[i][j].toFixed(3)} km`);
548
-
549
- // 8) Path is simple (no repeated nodes)
550
- const uniq = new Set(path);
551
- add('Path is simple (no repeats)', uniq.size === path.length, `uniq=${uniq.size} nodes`);
552
-
553
- // 9) Direct lower bound (sinuosity sanity)
554
- const direct = haversine(cities[start], cities[goal]);
555
- add('Route ≥ direct great-circle distance', dist + 1e-9 >= direct, `direct=${direct.toFixed(2)} km`);
556
-
557
- // 10) Compare to unrestricted (car) graph
558
- add(
559
- 'Bike distance ≥ car (unrestricted) distance',
560
- dist + 1e-9 >= carD,
561
- `Bike=${dist.toFixed(1)} vs Car=${carD.toFixed(1)} km`,
562
- );
563
-
564
- // 11) Edge criticality (no single used edge removable without longer/∞ result)
565
- let criticalOK = true,
566
- note = '';
567
- for (let k = 0; k < path.length - 1; k++) {
568
- const u = path[k],
569
- v = path[k + 1],
570
- w = graph[u][v];
571
- // temporarily remove
572
- delete graph[u][v];
573
- delete graph[v][u];
574
- const alt = dijkstra(start, goal).distance;
575
- // restore
576
- graph[u][v] = w;
577
- graph[v][u] = w;
578
- if (alt + 1e-9 < dist) {
579
- criticalOK = false;
580
- note = `Removing ${u}–${v} gave shorter ${alt.toFixed(3)} km`;
581
- break;
582
- }
583
- }
584
- add('No single edge on the path is replaceable by a shorter alternative', criticalOK, note || 'ok');
585
-
586
- return checks;
587
- }
588
-
589
- // ---------- SVG ----------
590
- function project(lat, lon, box) {
591
- const lats = Object.values(cities).map((c) => c.lat);
592
- const lons = Object.values(cities).map((c) => c.lon);
593
- const minLat = Math.min(...lats),
594
- maxLat = Math.max(...lats);
595
- const minLon = Math.min(...lons),
596
- maxLon = Math.max(...lons);
597
- const x = ((lon - minLon) / (maxLon - minLon)) * box.w + box.x;
598
- const y = box.y + box.h - ((lat - minLat) / (maxLat - minLat)) * box.h;
599
- return [x, y];
600
- }
601
-
602
- function drawMap(path) {
603
- const svg = document.getElementById('map');
604
- svg.innerHTML = '';
605
- const box = { x: 30, y: 20, w: 940, h: 480 };
606
-
607
- // motorways (dashed)
608
- for (const [u, v, meta] of links) {
609
- if (!meta.motorway) continue;
610
- const [x1, y1] = project(cities[u].lat, cities[u].lon, box);
611
- const [x2, y2] = project(cities[v].lat, cities[v].lon, box);
612
- const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
613
- line.setAttribute('x1', x1);
614
- line.setAttribute('y1', y1);
615
- line.setAttribute('x2', x2);
616
- line.setAttribute('y2', y2);
617
- line.setAttribute('stroke', 'var(--mway)');
618
- line.setAttribute('stroke-width', '3');
619
- line.setAttribute('stroke-linecap', 'round');
620
- line.setAttribute('stroke-dasharray', '6 6');
621
- line.setAttribute('opacity', '0.9');
622
- svg.appendChild(line);
623
- }
624
-
625
- // bike edges
626
- for (const [u, v, meta] of links) {
627
- if (meta.motorway) continue;
628
- const [x1, y1] = project(cities[u].lat, cities[u].lon, box);
629
- const [x2, y2] = project(cities[v].lat, cities[v].lon, box);
630
- const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
631
- line.setAttribute('x1', x1);
632
- line.setAttribute('y1', y1);
633
- line.setAttribute('x2', x2);
634
- line.setAttribute('y2', y2);
635
- line.setAttribute('stroke', 'var(--edge)');
636
- line.setAttribute('stroke-width', '3');
637
- line.setAttribute('stroke-linecap', 'round');
638
- line.setAttribute('opacity', '0.8');
639
- svg.appendChild(line);
640
- }
641
-
642
- // chosen path
643
- for (let i = 0; i < path.length - 1; i++) {
644
- const u = path[i],
645
- v = path[i + 1];
646
- const [x1, y1] = project(cities[u].lat, cities[u].lon, box);
647
- const [x2, y2] = project(cities[v].lat, cities[v].lon, box);
648
- const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
649
- line.setAttribute('x1', x1);
650
- line.setAttribute('y1', y1);
651
- line.setAttribute('x2', x2);
652
- line.setAttribute('y2', y2);
653
- line.setAttribute('stroke', 'var(--path)');
654
- line.setAttribute('stroke-width', '6');
655
- line.setAttribute('stroke-linecap', 'round');
656
- svg.appendChild(line);
657
- }
658
-
659
- // nodes + labels
660
- for (const [name, coord] of Object.entries(cities)) {
661
- const [x, y] = project(coord.lat, coord.lon, box);
662
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
663
- const c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
664
- c.setAttribute('cx', x);
665
- c.setAttribute('cy', y);
666
- c.setAttribute('r', 6);
667
- c.setAttribute('fill', 'var(--node)');
668
- c.setAttribute('opacity', path.includes(name) ? '1' : '0.85');
669
- const t = document.createElementNS('http://www.w3.org/2000/svg', 'text');
670
- t.setAttribute('x', x + 10);
671
- t.setAttribute('y', y - 10);
672
- t.setAttribute('font-size', '12');
673
- t.setAttribute('fill', 'var(--ink)');
674
- t.textContent = name;
675
- g.appendChild(c);
676
- g.appendChild(t);
677
- svg.appendChild(g);
678
- }
679
- }
680
-
681
- // ---------- Driver (P3) ----------
682
- function run() {
683
- const start = 'Gent',
684
- goal = 'Maasmechelen';
685
-
686
- // Bike (active)
687
- graph = bikeGraph;
688
- const d = dijkstra(start, goal);
689
- const a = astar(start, goal);
690
- const b = bellmanFord(start, goal);
691
- const fw = floydWarshall();
692
-
693
- // Car (unrestricted) distance for comparison
694
- const saved = graph;
695
- graph = fullGraph;
696
- const car = dijkstra(start, goal);
697
- graph = saved;
698
-
699
- // Answer
700
- document.getElementById('startVal').textContent = start;
701
- document.getElementById('goalVal').textContent = goal;
702
- document.getElementById('distVal').textContent = isFinite(d.distance) ? d.distance.toFixed(2) : '∞';
703
- document.getElementById('stopsVal').textContent = d.path.length;
704
-
705
- // Path chips
706
- const pathList = document.getElementById('pathList');
707
- pathList.innerHTML = '';
708
- d.path.forEach((name, idx) => {
709
- const el = document.createElement('div');
710
- el.className = 'pill';
711
- el.innerHTML = `<span class="seq">${idx + 1}</span> ${name}`;
712
- pathList.appendChild(el);
713
- });
714
-
715
- // Reason
716
- const reason = [
717
- `Graph: weighted, undirected; weight = Haversine distance on bike-allowed links.`,
718
- `Algorithms: Dijkstra (primary), A* (admissible straight-line), Bellman–Ford, Floyd–Warshall.`,
719
- `Chosen shortest path (bike): ${d.path.join(' → ')}`,
720
- `Distance (bike): ${isFinite(d.distance) ? d.distance.toFixed(2) : '∞'} km`,
721
- `Reference car (unrestricted) distance: ${isFinite(car.distance) ? car.distance.toFixed(2) : '∞'} km`,
722
- `Dijkstra selection order: ${d.steps.map((s) => `${s.pick}(${isFinite(s.d) ? s.d.toFixed(1) : '∞'})`).join(' → ')}`,
723
- ].join('\n');
724
- document.getElementById('reasonText').textContent = reason;
725
-
726
- // Checks
727
- const checks = runChecks(d, a, b, fw, start, goal, car.distance);
728
- const list = document.getElementById('checksList');
729
- list.innerHTML = '';
730
- checks.forEach((c) => {
731
- const row = document.createElement('div');
732
- row.className = 'check';
733
- const name = document.createElement('div');
734
- name.textContent = c.name;
735
- const right = document.createElement('div');
736
- right.className = 'row';
737
- const detail = document.createElement('div');
738
- detail.className = 'hint';
739
- detail.textContent = c.detail || '';
740
- const badge = document.createElement('span');
741
- badge.className = 'badge ' + (c.ok ? 'pass' : 'fail');
742
- badge.textContent = c.ok ? 'PASS' : 'FAIL';
743
- right.appendChild(detail);
744
- right.appendChild(badge);
745
- row.appendChild(name);
746
- row.appendChild(right);
747
- list.appendChild(row);
748
- });
749
-
750
- // Map
751
- drawMap(d.path);
752
- }
753
-
754
- document.getElementById('run').addEventListener('click', run);
755
- // Run immediately
756
- run();
757
- </script>
758
- </body>
759
- </html>