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.
- package/README.md +0 -1
- package/package.json +2 -3
- package/arctifacts/README.md +0 -59
- package/arctifacts/ackermann.html +0 -678
- package/arctifacts/auroracare.html +0 -1297
- package/arctifacts/bike-trip.html +0 -752
- package/arctifacts/binomial-theorem.html +0 -631
- package/arctifacts/bmi.html +0 -511
- package/arctifacts/building-performance.html +0 -750
- package/arctifacts/clinical-care.html +0 -726
- package/arctifacts/collatz.html +0 -403
- package/arctifacts/complex.html +0 -321
- package/arctifacts/control-system.html +0 -482
- package/arctifacts/delfour.html +0 -849
- package/arctifacts/earthquake-epicenter.html +0 -982
- package/arctifacts/eco-route.html +0 -662
- package/arctifacts/euclid-infinitude.html +0 -564
- package/arctifacts/euler-identity.html +0 -667
- package/arctifacts/exoplanet-transit.html +0 -1000
- package/arctifacts/faltings-theorem.html +0 -1046
- package/arctifacts/fibonacci.html +0 -299
- package/arctifacts/fundamental-theorem-arithmetic.html +0 -398
- package/arctifacts/godel-numbering.html +0 -743
- package/arctifacts/gps-bike.html +0 -759
- package/arctifacts/gps-clinical-bench.html +0 -792
- package/arctifacts/graph-french.html +0 -449
- package/arctifacts/grass-molecular.html +0 -592
- package/arctifacts/group-theory.html +0 -740
- package/arctifacts/health-info.html +0 -833
- package/arctifacts/kaprekar-constant.html +0 -576
- package/arctifacts/lee.html +0 -805
- package/arctifacts/linked-lists.html +0 -502
- package/arctifacts/lldm.html +0 -612
- package/arctifacts/matrix-multiplication.html +0 -502
- package/arctifacts/matrix.html +0 -651
- package/arctifacts/newton-raphson.html +0 -944
- package/arctifacts/peano-factorial.html +0 -456
- package/arctifacts/pi.html +0 -363
- package/arctifacts/polynomial.html +0 -646
- package/arctifacts/prime.html +0 -366
- package/arctifacts/pythagorean-theorem.html +0 -468
- package/arctifacts/rest-path.html +0 -469
- package/arctifacts/roots-of-unity.html +0 -363
- package/arctifacts/turing.html +0 -409
- package/arctifacts/wind-turbines.html +0 -726
package/arctifacts/lee.html
DELETED
|
@@ -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>
|