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,805 +0,0 @@
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" />
6
- <title>Lee (Grid BFS shortest path)</title>
7
- <style>
8
- :root {
9
- --fg: #101014;
10
- --bg: #ffffff;
11
- --muted: #666;
12
- --accent: #2563eb;
13
- --chip: #eef2ff;
14
- --ok: #16a34a;
15
- --bad: #dc2626;
16
- --warn: #ca8a04;
17
- --card: color-mix(in srgb, var(--accent) 4%, transparent);
18
- }
19
- @media (prefers-color-scheme: dark) {
20
- :root {
21
- --fg: #eaeaf0;
22
- --bg: #0b0b10;
23
- --muted: #a0a0b0;
24
- --accent: #60a5fa;
25
- --chip: #0e1a32;
26
- --card: color-mix(in srgb, var(--accent) 6%, transparent);
27
- }
28
- }
29
- html,
30
- body {
31
- margin: 0;
32
- padding: 0;
33
- background: var(--bg);
34
- color: var(--fg);
35
- font:
36
- 15px/1.6 ui-sans-serif,
37
- system-ui,
38
- -apple-system,
39
- Segoe UI,
40
- Roboto,
41
- Helvetica,
42
- Arial;
43
- }
44
- main {
45
- max-width: 1200px;
46
- margin: 0 auto;
47
- padding: 28px 16px 96px;
48
- }
49
- h1 {
50
- font-size: clamp(1.6rem, 2.6vw + 1rem, 2.2rem);
51
- margin: 0 0 6px;
52
- }
53
- header p {
54
- margin: 0;
55
- color: var(--muted);
56
- }
57
- section {
58
- margin: 18px 0 22px;
59
- padding: 14px 14px 16px;
60
- border: 1px solid color-mix(in srgb, var(--accent) 18%, transparent);
61
- border-radius: 14px;
62
- background: var(--card);
63
- }
64
- section h2 {
65
- margin: 0 0 10px;
66
- font-size: 1.15rem;
67
- }
68
- .row {
69
- display: flex;
70
- gap: 12px;
71
- align-items: center;
72
- flex-wrap: wrap;
73
- }
74
- .col {
75
- display: flex;
76
- flex-direction: column;
77
- gap: 10px;
78
- }
79
- .btn {
80
- appearance: none;
81
- border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
82
- background: color-mix(in srgb, var(--accent) 8%, transparent);
83
- color: var(--fg);
84
- border-radius: 10px;
85
- padding: 8px 12px;
86
- font-weight: 700;
87
- cursor: pointer;
88
- }
89
- .mono {
90
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
91
- }
92
- .muted {
93
- color: var(--muted);
94
- }
95
- .small {
96
- font-size: 0.92em;
97
- }
98
- .chip {
99
- display: inline-block;
100
- padding: 2px 8px;
101
- border-radius: 999px;
102
- background: var(--chip);
103
- font-weight: 600;
104
- }
105
- input[type='number'],
106
- input[type='range'] {
107
- border-radius: 10px;
108
- border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
109
- padding: 6px 8px;
110
- }
111
- label {
112
- user-select: none;
113
- }
114
- /* Grid */
115
- #boardWrap {
116
- position: relative;
117
- }
118
- .board {
119
- display: grid;
120
- gap: 2px;
121
- background: color-mix(in srgb, var(--accent) 15%, transparent);
122
- padding: 4px;
123
- border-radius: 10px;
124
- }
125
- .cell {
126
- width: 22px;
127
- height: 22px;
128
- border-radius: 4px;
129
- background: color-mix(in srgb, var(--accent) 8%, transparent);
130
- border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
131
- }
132
- .cell.wall {
133
- background: color-mix(in srgb, #000 50%, transparent);
134
- border-color: color-mix(in srgb, #000 60%, transparent);
135
- }
136
- .cell.start {
137
- background: #16a34a;
138
- border-color: #15803d;
139
- }
140
- .cell.goal {
141
- background: #dc2626;
142
- border-color: #991b1b;
143
- }
144
- .cell.path {
145
- background: #f59e0b;
146
- border-color: #b45309;
147
- }
148
- .toolbar {
149
- display: flex;
150
- gap: 8px;
151
- flex-wrap: wrap;
152
- align-items: center;
153
- }
154
- .tool {
155
- padding: 6px 10px;
156
- border-radius: 10px;
157
- border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
158
- cursor: pointer;
159
- background: color-mix(in srgb, var(--accent) 8%, transparent);
160
- }
161
- .tool.active {
162
- outline: 2px solid color-mix(in srgb, var(--accent) 50%, transparent);
163
- }
164
- #answer {
165
- overflow-x: auto;
166
- }
167
- #answer pre {
168
- white-space: pre !important;
169
- overflow-x: auto;
170
- overflow-y: auto;
171
- max-width: 100%;
172
- }
173
- .ok {
174
- color: var(--ok);
175
- }
176
- .bad {
177
- color: var(--bad);
178
- }
179
- .warn {
180
- color: var(--warn);
181
- }
182
- </style>
183
- </head>
184
- <body>
185
- <main>
186
- <header class="row">
187
- <div>
188
- <h1>Lee (Grid BFS shortest path)</h1>
189
- <p>Self‑contained grid pathfinding with the Lee algorithm (breadth‑first search on a 4‑neighborhood).</p>
190
- </div>
191
- <div class="row" style="margin-left: auto">
192
- <label class="muted small">Rows: <input id="rows" type="number" min="5" max="80" value="25" /></label>
193
- <label class="muted small">Cols: <input id="cols" type="number" min="5" max="80" value="25" /></label>
194
- <button id="resize" class="btn">Resize</button>
195
- <button id="clear" class="btn">Clear</button>
196
- <button id="maze" class="btn">Random maze</button>
197
- <label class="muted small"><input id="ensure" type="checkbox" checked /> ensure solvable</label>
198
- <label class="muted small"
199
- >density <input id="density" type="range" min="0" max="0.6" step="0.02" value="0.28"
200
- /></label>
201
- <button id="solve" class="btn">Solve</button>
202
- <button id="checkBtn" class="btn">Check</button>
203
- </div>
204
- </header>
205
-
206
- <section>
207
- <h2>What this is?</h2>
208
- <p>
209
- The Lee algorithm finds the shortest path in a grid with uniform step cost. It’s a classic breadth‑first
210
- search: we expand from the start, layer by layer, until we reach the goal. Then we backtrack by following
211
- decreasing distance values to reconstruct a shortest path. Works with walls (blocked cells) and 4‑neighborhood
212
- moves.
213
- </p>
214
- </section>
215
-
216
- <section class="row" style="align-items: flex-start">
217
- <div class="col">
218
- <div class="toolbar">
219
- <span class="muted small">Tool:</span>
220
- <button class="tool active" id="tool-wall" data-tool="wall">Draw walls</button>
221
- <button class="tool" id="tool-erase" data-tool="erase">Erase</button>
222
- <button class="tool" id="tool-start" data-tool="start">Set start</button>
223
- <button class="tool" id="tool-goal" data-tool="goal">Set goal</button>
224
- </div>
225
- <div id="boardWrap">
226
- <div id="board" class="board"></div>
227
- </div>
228
- </div>
229
- <div class="col" style="flex: 1">
230
- <h2>Text</h2>
231
- <p class="small muted">
232
- Paste or copy your grid here. Use <code>#</code> for walls, <code>.</code> for empty, <code>S</code> start,
233
- <code>G</code> goal.
234
- </p>
235
- <textarea id="txt" class="mono" spellcheck="false" style="width: 100%; min-height: 180px"></textarea>
236
- <div class="row">
237
- <button id="loadDemo" class="btn">Load demo</button>
238
- <button id="syncFromText" class="btn">Apply from text</button>
239
- <button id="syncToText" class="btn">Copy from grid</button>
240
- </div>
241
- </div>
242
- </section>
243
-
244
- <section id="answer">
245
- <h2>Answer</h2>
246
- <div id="chips" class="row small" style="gap: 8px"></div>
247
- <div id="summary" class="mono small"></div>
248
- </section>
249
-
250
- <section id="reason">
251
- <h2>Reason why</h2>
252
- <div class="small">
253
- <p>
254
- On grids with unit weights, BFS explores in concentric “rings”. The first time it dequeues the goal, the
255
- discovered distance is optimal. The backtracking step simply walks to any neighbor with distance −1 until it
256
- reaches the start. Obstacles are simply never enqueued.
257
- </p>
258
- </div>
259
- </section>
260
-
261
- <section id="check">
262
- <h2>Check (harness)</h2>
263
- <div id="check-body"></div>
264
- </section>
265
- </main>
266
-
267
- <script>
268
- (function () {
269
- 'use strict';
270
- const $ = (id) => document.getElementById(id);
271
- const setHTML = (id, html) => {
272
- const el = $(id);
273
- if (el) el.innerHTML = html;
274
- };
275
- const now = () => performance.now();
276
-
277
- // State
278
- let R = 25,
279
- C = 25;
280
- let grid = []; // 0 empty, 1 wall
281
- let start = null; // [r,c]
282
- let goal = null; // [r,c]
283
- let mouseDown = false,
284
- currentTool = 'wall';
285
-
286
- // Helpers
287
- const inside = (r, c) => r >= 0 && r < R && c >= 0 && c < C;
288
- const idx = (r, c) => r * C + c;
289
- const dirs = [
290
- [1, 0],
291
- [-1, 0],
292
- [0, 1],
293
- [0, -1],
294
- ];
295
-
296
- // Build board UI
297
- function buildBoard() {
298
- const board = $('board');
299
- board.style.gridTemplateColumns = `repeat(${C}, 22px)`;
300
- board.innerHTML = '';
301
- for (let r = 0; r < R; r++) {
302
- for (let c = 0; c < C; c++) {
303
- const div = document.createElement('div');
304
- div.className = 'cell';
305
- div.dataset.r = r;
306
- div.dataset.c = c;
307
- board.appendChild(div);
308
- }
309
- }
310
- paintAll();
311
- }
312
-
313
- function paintAll(pathSet = null) {
314
- const board = $('board');
315
- for (let r = 0; r < R; r++) {
316
- for (let c = 0; c < C; c++) {
317
- const d = board.children[idx(r, c)];
318
- d.className = 'cell';
319
- if (grid[idx(r, c)] === 1) d.classList.add('wall');
320
- if (pathSet && pathSet.has(idx(r, c))) d.classList.add('path');
321
- if (start && start[0] === r && start[1] === c) d.classList.add('start');
322
- if (goal && goal[0] === r && goal[1] === c) d.classList.add('goal');
323
- }
324
- }
325
- }
326
-
327
- function setTool(tool) {
328
- currentTool = tool;
329
- for (const b of document.querySelectorAll('.tool')) b.classList.remove('active');
330
- const el = document.querySelector('.tool[data-tool="' + tool + '"]');
331
- if (el) el.classList.add('active');
332
- }
333
-
334
- function attachBoardEvents() {
335
- const board = $('board');
336
- board.addEventListener('mousedown', (e) => {
337
- mouseDown = true;
338
- onBoardInteract(e);
339
- });
340
- board.addEventListener('mousemove', (e) => {
341
- if (mouseDown) onBoardInteract(e);
342
- });
343
- window.addEventListener('mouseup', () => (mouseDown = false));
344
- board.addEventListener(
345
- 'touchstart',
346
- (e) => {
347
- onBoardInteract(e.touches[0]);
348
- e.preventDefault();
349
- },
350
- { passive: false },
351
- );
352
- board.addEventListener(
353
- 'touchmove',
354
- (e) => {
355
- onBoardInteract(e.touches[0]);
356
- e.preventDefault();
357
- },
358
- { passive: false },
359
- );
360
- }
361
-
362
- function onBoardInteract(e) {
363
- const target = document.elementFromPoint(e.clientX, e.clientY);
364
- if (!target || !target.classList.contains('cell')) return;
365
- const r = Number(target.dataset.r),
366
- c = Number(target.dataset.c);
367
- if (currentTool === 'wall') {
368
- if (start && start[0] === r && start[1] === c) start = null;
369
- if (goal && goal[0] === r && goal[1] === c) goal = null;
370
- grid[idx(r, c)] = 1;
371
- } else if (currentTool === 'erase') {
372
- grid[idx(r, c)] = 0;
373
- } else if (currentTool === 'start') {
374
- if (start) grid[idx(start[0], start[1])] = 0;
375
- start = [r, c];
376
- grid[idx(r, c)] = 0;
377
- } else if (currentTool === 'goal') {
378
- if (goal) grid[idx(goal[0], goal[1])] = 0;
379
- goal = [r, c];
380
- grid[idx(r, c)] = 0;
381
- }
382
- paintAll();
383
- syncTextFromGrid();
384
- }
385
-
386
- // Text <-> grid
387
- function gridToText() {
388
- const lines = [];
389
- for (let r = 0; r < R; r++) {
390
- let s = '';
391
- for (let c = 0; c < C; c++) {
392
- if (start && start[0] === r && start[1] === c) s += 'S';
393
- else if (goal && goal[0] === r && goal[1] === c) s += 'G';
394
- else s += grid[idx(r, c)] === 1 ? '#' : '.';
395
- }
396
- lines.push(s);
397
- }
398
- return lines.join('\n');
399
- }
400
-
401
- function textToGrid(txt) {
402
- const lines = (txt || '')
403
- .trim()
404
- .split(/\\r?\\n/)
405
- .filter(Boolean);
406
- if (lines.length === 0) return false;
407
- const rr = lines.length,
408
- cc = Math.max(...lines.map((s) => s.length));
409
- R = rr;
410
- C = cc;
411
- grid = Array(R * C).fill(0);
412
- start = null;
413
- goal = null;
414
- for (let r = 0; r < R; r++) {
415
- const row = lines[r];
416
- for (let c = 0; c < C; c++) {
417
- const ch = row[c] || '.';
418
- if (ch === '#') grid[idx(r, c)] = 1;
419
- else if (ch === 'S') {
420
- start = [r, c];
421
- grid[idx(r, c)] = 0;
422
- } else if (ch === 'G') {
423
- goal = [r, c];
424
- grid[idx(r, c)] = 0;
425
- } else grid[idx(r, c)] = 0;
426
- }
427
- }
428
- buildBoard();
429
- syncToText();
430
- return true;
431
- }
432
-
433
- function syncFromText() {
434
- textToGrid($('txt').value);
435
- }
436
- function syncToText() {
437
- $('txt').value = gridToText();
438
- }
439
- function syncTextFromGrid() {
440
- $('txt').value = gridToText();
441
- }
442
-
443
- // Resize / clear / maze
444
- function doResize() {
445
- const r = Math.max(5, Math.min(80, Math.floor(Number($('rows').value) || 25)));
446
- const c = Math.max(5, Math.min(80, Math.floor(Number($('cols').value) || 25)));
447
- R = r;
448
- C = c;
449
- grid = Array(R * C).fill(0);
450
- start = null;
451
- goal = null;
452
- buildBoard();
453
- syncToText();
454
- }
455
- function doClear() {
456
- grid.fill(0);
457
- start = null;
458
- goal = null;
459
- paintAll();
460
- syncToText();
461
- }
462
-
463
- function baselineReachable(a, rows, cols, S, G) {
464
- const inside = (r, c) => r >= 0 && r < rows && c >= 0 && c < cols;
465
- const id = (r, c) => r * cols + c;
466
- const dist = new Int32Array(rows * cols).fill(-1);
467
- const s = id(S[0], S[1]),
468
- g = id(G[0], G[1]);
469
- const q = [s];
470
- dist[s] = 0;
471
- let qi = 0;
472
- while (qi < q.length) {
473
- const cur = q[qi++],
474
- r = (cur / cols) | 0,
475
- c = cur % cols;
476
- if (cur === g) return true;
477
- for (const [dr, dc] of [
478
- [1, 0],
479
- [-1, 0],
480
- [0, 1],
481
- [0, -1],
482
- ]) {
483
- const nr = r + dr,
484
- nc = c + dc;
485
- if (!inside(nr, nc)) continue;
486
- const ni = id(nr, nc);
487
- if (a[ni] === 1 || dist[ni] !== -1) continue;
488
- dist[ni] = dist[cur] + 1;
489
- q.push(ni);
490
- }
491
- }
492
- return false;
493
- }
494
-
495
- function doMaze() {
496
- const p = Number($('density').value || 0.28);
497
- const needSolvable = $('ensure') && $('ensure').checked;
498
- const maxTries = 200;
499
- let tries = 0,
500
- ok = false;
501
-
502
- function randomFill() {
503
- for (let i = 0; i < R * C; i++) grid[i] = Math.random() < p ? 1 : 0;
504
- start = [0, 0];
505
- goal = [R - 1, C - 1];
506
- grid[idx(start[0], start[1])] = 0;
507
- grid[idx(goal[0], goal[1])] = 0;
508
- }
509
-
510
- if (!needSolvable) {
511
- randomFill();
512
- } else {
513
- for (tries = 0; tries < maxTries; tries++) {
514
- randomFill();
515
- if (baselineReachable(grid, R, C, start, goal)) {
516
- ok = true;
517
- break;
518
- }
519
- }
520
- if (!ok) {
521
- // As a fallback, thin the maze a bit and try again.
522
- for (tries = 0; tries < maxTries; tries++) {
523
- for (let i = 0; i < R * C; i++) grid[i] = Math.random() < p * 0.6 ? 1 : 0;
524
- start = [0, 0];
525
- goal = [R - 1, C - 1];
526
- grid[idx(start[0], start[1])] = 0;
527
- grid[idx(goal[0], goal[1])] = 0;
528
- if (baselineReachable(grid, R, C, start, goal)) {
529
- ok = true;
530
- break;
531
- }
532
- }
533
- }
534
- }
535
-
536
- paintAll();
537
- syncToText();
538
- if (needSolvable) {
539
- const msg = ok ? '(found solvable maze)' : '(could not ensure solvable after attempts; try lower density)';
540
- setHTML('summary', '<span class="small muted">Random maze ' + msg + '</span>');
541
- }
542
- }
543
- // Lee (BFS) — returns {found, dist, path: [idx...], expansions}
544
- function lee() {
545
- const t0 = now();
546
- if (!start || !goal)
547
- return { found: false, dist: -1, path: [], expansions: 0, ms: 0, reason: 'Start or goal missing.' };
548
- const q = [];
549
- const dist = new Int32Array(R * C).fill(-1);
550
- const sidx = idx(start[0], start[1]);
551
- const gidx = idx(goal[0], goal[1]);
552
- if (grid[sidx] === 1 || grid[gidx] === 1)
553
- return { found: false, dist: -1, path: [], expansions: 0, ms: 0, reason: 'Start or goal on a wall.' };
554
-
555
- q.push(sidx);
556
- dist[sidx] = 0;
557
- let qi = 0,
558
- expansions = 0,
559
- reached = false;
560
- while (qi < q.length) {
561
- const cur = q[qi++];
562
- const r = Math.floor(cur / C),
563
- c = cur % C;
564
- if (cur === gidx) {
565
- reached = true;
566
- break;
567
- }
568
- for (const [dr, dc] of dirs) {
569
- const nr = r + dr,
570
- nc = c + dc;
571
- if (!inside(nr, nc)) continue;
572
- const ni = idx(nr, nc);
573
- if (grid[ni] === 1) continue;
574
- if (dist[ni] !== -1) continue;
575
- dist[ni] = dist[cur] + 1;
576
- q.push(ni);
577
- }
578
- expansions++;
579
- }
580
- const t1 = now();
581
- if (!reached) return { found: false, dist: -1, path: [], expansions, ms: t1 - t0, reason: 'No path.' };
582
-
583
- // Backtrack
584
- const path = [];
585
- let cur = gidx;
586
- path.push(cur);
587
- while (cur !== sidx) {
588
- const r = Math.floor(cur / C),
589
- c = cur % C;
590
- let moved = false;
591
- for (const [dr, dc] of dirs) {
592
- const nr = r + dr,
593
- nc = c + dc;
594
- if (!inside(nr, nc)) continue;
595
- const ni = idx(nr, nc);
596
- if (dist[ni] === dist[cur] - 1) {
597
- path.push(ni);
598
- cur = ni;
599
- moved = true;
600
- break;
601
- }
602
- }
603
- if (!moved) break; // shouldn't happen
604
- }
605
- path.reverse();
606
- return { found: true, dist: dist[gidx], path, expansions, ms: t1 - t0 };
607
- }
608
-
609
- // Solve + render
610
- function doSolve() {
611
- const res = lee();
612
- if (!res.found) {
613
- setHTML('summary', `<div class="bad">No solution: ${res.reason || ''}</div>`);
614
- $('chips').innerHTML = '';
615
- paintAll(null);
616
- return;
617
- }
618
- const pset = new Set(res.path);
619
- paintAll(pset);
620
- const chips = [
621
- ['rows', R],
622
- ['cols', C],
623
- ['walls', grid.reduce((a, b) => a + (b === 1), 0)],
624
- ['expansions', res.expansions],
625
- ['dist', res.dist],
626
- ['time', res.ms.toFixed(2) + ' ms'],
627
- ]
628
- .map(([k, v]) => `<span class="chip">${k}: ${v}</span>`)
629
- .join(' ');
630
- $('chips').innerHTML = chips;
631
- setHTML(
632
- 'summary',
633
- `<pre class="mono">Path length (steps): ${res.dist}
634
- Nodes expanded: ${res.expansions}
635
- </pre>`,
636
- );
637
- }
638
-
639
- // Check (harness)
640
- function runChecks() {
641
- const lines = [];
642
- const ok = (b) => (b ? '✓' : '✗');
643
-
644
- // 1) If path found, verify monotone decrease along path and that it starts at S and ends at G.
645
- const res = lee();
646
- if (res.found) {
647
- const distOk = (function () {
648
- const dist = new Int32Array(R * C).fill(-1);
649
- const q = [idx(start[0], start[1])];
650
- dist[q[0]] = 0;
651
- let qi = 0;
652
- while (qi < q.length) {
653
- const cur = q[qi++];
654
- const r = Math.floor(cur / C),
655
- c = cur % C;
656
- for (const [dr, dc] of dirs) {
657
- const nr = r + dr,
658
- nc = c + dc;
659
- if (!inside(nr, nc)) continue;
660
- const ni = idx(nr, nc);
661
- if (grid[ni] === 1 || dist[ni] !== -1) continue;
662
- dist[ni] = dist[cur] + 1;
663
- q.push(ni);
664
- }
665
- }
666
- // Check endpoints
667
- const sOK = res.path.length > 0 && res.path[0] === idx(start[0], start[1]);
668
- const gOK = res.path.length > 0 && res.path[res.path.length - 1] === idx(goal[0], goal[1]);
669
- let mono = true;
670
- for (let i = 1; i < res.path.length; i++) {
671
- if (dist[res.path[i]] !== dist[res.path[i - 1]] + 1) {
672
- mono = false;
673
- break;
674
- }
675
- }
676
- const lenOK = res.path.length - 1 === dist[idx(goal[0], goal[1])];
677
- return sOK && gOK && mono && lenOK;
678
- })();
679
- lines.push(`Path correctness (start→goal, monotone distances, optimal length): ${ok(distOk)}`);
680
- } else {
681
- lines.push('No path found — skipping path checks.');
682
- }
683
-
684
- // 2) Random mazes quick sanity: generate 5 grids and ensure BFS agrees with itself on distance (trivial) and respects walls
685
- let rndOK = true;
686
- for (let t = 0; t < 5; t++) {
687
- const r = 20,
688
- c = 20,
689
- p = 0.25;
690
- const arr = Array(r * c)
691
- .fill(0)
692
- .map(() => (Math.random() < p ? 1 : 0));
693
- const s = [0, 0],
694
- g = [r - 1, c - 1];
695
- arr[0] = 0;
696
- arr[r * c - 1] = 0;
697
- // single-run BFS
698
- function bfs(arrR, arrC, a, sx, sy, gx, gy) {
699
- const inside = (rr, cc) => rr >= 0 && rr < arrR && cc >= 0 && cc < arrC;
700
- const id = (rr, cc) => rr * arrC + cc;
701
- const dist = new Int32Array(arrR * arrC).fill(-1);
702
- const q = [id(sx, sy)];
703
- dist[q[0]] = 0;
704
- let qi = 0;
705
- let reached = false;
706
- while (qi < q.length) {
707
- const cur = q[qi++];
708
- const rr = Math.floor(cur / arrC),
709
- cc = cur % arrC;
710
- if (rr === gx && cc === gy) {
711
- reached = true;
712
- break;
713
- }
714
- for (const [dr, dc] of [
715
- [1, 0],
716
- [-1, 0],
717
- [0, 1],
718
- [0, -1],
719
- ]) {
720
- const nr = rr + dr,
721
- nc = cc + dc;
722
- if (!inside(nr, nc)) continue;
723
- const ni = id(nr, nc);
724
- if (a[ni] === 1 || dist[ni] !== -1) continue;
725
- dist[ni] = dist[cur] + 1;
726
- q.push(ni);
727
- }
728
- }
729
- return { reached, dist };
730
- }
731
- const r1 = bfs(r, c, arr, s[0], s[1], g[0], g[1]);
732
- if (!r1.reached && r1.dist[r * c - 1] !== -1) {
733
- rndOK = false;
734
- break;
735
- }
736
- }
737
- lines.push(`Randomized quick checks (5 samples): ${ok(rndOK)}`);
738
-
739
- const pre = document.createElement('pre');
740
- pre.className = 'mono';
741
- pre.style.whiteSpace = 'pre-wrap';
742
- pre.textContent = lines.join('\n');
743
- $('check-body').replaceChildren(pre);
744
- }
745
-
746
- // Demo grid
747
- const DEMO = [
748
- 'S....#.................',
749
- '#####.#.#############..',
750
- '.....#.#...........#..G',
751
- '.###.#.#.#######.#.#.##',
752
- '.#...#.#.....#...#.#..#',
753
- '.#.###.#####.#.###.#..#',
754
- '.#...#.....#.#...#.#..#',
755
- '.#.#.#####.#.###.#.####',
756
- '.#.#.....#.#.....#....#',
757
- '.#.#.###.#.#######.##.#',
758
- '.#.#.#...#.........#..#',
759
- '.#.#.#.###########.#..#',
760
- '.#.#.#.............#..#',
761
- '.#.#.###############..#',
762
- '.#.#.................#.',
763
- ].join('\n');
764
-
765
- function loadDemo() {
766
- $('txt').value = DEMO;
767
- syncFromText();
768
- }
769
-
770
- // Wire up
771
- $('resize').addEventListener('click', doResize);
772
- $('clear').addEventListener('click', doClear);
773
- $('maze').addEventListener('click', doMaze);
774
- $('solve').addEventListener('click', () => {
775
- doSolve();
776
- runChecks();
777
- });
778
- $('checkBtn').addEventListener('click', runChecks);
779
- $('loadDemo').addEventListener('click', loadDemo);
780
- $('syncFromText').addEventListener('click', syncFromText);
781
- $('syncToText').addEventListener('click', syncToText);
782
- for (const el of document.querySelectorAll('.tool')) {
783
- el.addEventListener('click', () => setTool(el.dataset.tool));
784
- }
785
-
786
- // Init
787
- (function init() {
788
- R = 25;
789
- C = 25;
790
- grid = Array(R * C).fill(0);
791
- start = null;
792
- goal = null;
793
- buildBoard();
794
- attachBoardEvents();
795
- doMaze(); // start with a random maze
796
- setTool('wall');
797
- setHTML(
798
- 'summary',
799
- '<span class="muted small">Click Solve to find a path, or draw your own maze. Tools: walls, erase, set start/goal.</span>',
800
- );
801
- })();
802
- })();
803
- </script>
804
- </body>
805
- </html>