EnovinxSchool commited on
Commit
f11c2b5
·
verified ·
1 Parent(s): a6a3528

undefined - Initial Deployment

Browse files
Files changed (1) hide show
  1. index.html +90 -148
index.html CHANGED
@@ -24,7 +24,7 @@
24
  gap: 2rem;
25
  justify-content: center;
26
  width: 100%;
27
- max-width: 1200px;
28
  }
29
  .canvas-container {
30
  display: flex;
@@ -55,7 +55,7 @@
55
  display: flex;
56
  flex-direction: column;
57
  gap: 1rem;
58
- min-width: 250px;
59
  }
60
  .control-group {
61
  display: flex;
@@ -69,6 +69,8 @@
69
  padding: 0.5rem;
70
  border: 1px solid #ccc;
71
  border-radius: 4px;
 
 
72
  }
73
  .control-group input[type="checkbox"] {
74
  width: 20px;
@@ -148,6 +150,10 @@
148
  <div class="control-group">
149
  <label for="n-size">Pattern Size (N)</label>
150
  <input type="number" id="n-size" value="3" min="2" max="5">
 
 
 
 
151
  </div>
152
  <div class="control-group">
153
  <label for="output-width">Output Width</label>
@@ -183,7 +189,7 @@
183
 
184
  <div class="canvas-container">
185
  <h2>Input Canvas</h2>
186
- <canvas id="input-canvas" width="200" height="200"></canvas>
187
  </div>
188
 
189
  <div class="canvas-container">
@@ -200,6 +206,7 @@ document.addEventListener('DOMContentLoaded', () => {
200
  const outputCanvas = document.getElementById('output-canvas');
201
  const outputCtx = outputCanvas.getContext('2d');
202
  const nSizeInput = document.getElementById('n-size');
 
203
  const outputWidthInput = document.getElementById('output-width');
204
  const outputHeightInput = document.getElementById('output-height');
205
  const augmentCheckbox = document.getElementById('augment');
@@ -211,12 +218,13 @@ document.addEventListener('DOMContentLoaded', () => {
211
  const colorPicker = document.getElementById('color-picker');
212
 
213
  // --- DRAWING STATE ---
214
- const INPUT_DIM = 20; // 20x20 grid for input
215
- const PIXEL_SIZE = inputCanvas.width / INPUT_DIM;
216
  let isDrawing = false;
217
- let colors = ['#ffffff', '#000000', '#007bff', '#28a745'];
 
218
  let currentColorIndex = 1;
219
- let inputGrid = Array.from({ length: INPUT_DIM }, () => Array(INPUT_DIM).fill(0));
220
 
221
  // --- HELPER FUNCTIONS ---
222
  const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
@@ -250,33 +258,41 @@ document.addEventListener('DOMContentLoaded', () => {
250
 
251
  function drawInputGrid() {
252
  inputCtx.clearRect(0, 0, inputCanvas.width, inputCanvas.height);
253
- for (let y = 0; y < INPUT_DIM; y++) {
254
- for (let x = 0; x < INPUT_DIM; x++) {
255
  inputCtx.fillStyle = colors[inputGrid[y][x]];
256
- inputCtx.fillRect(x * PIXEL_SIZE, y * PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE);
257
  }
258
  }
259
  // Draw grid lines for clarity
260
  inputCtx.strokeStyle = '#ddd';
261
  inputCtx.lineWidth = 0.5;
262
- for (let i = 0; i <= INPUT_DIM; i++) {
263
  inputCtx.beginPath();
264
- inputCtx.moveTo(i * PIXEL_SIZE, 0);
265
- inputCtx.lineTo(i * PIXEL_SIZE, inputCanvas.height);
266
  inputCtx.stroke();
267
  inputCtx.beginPath();
268
- inputCtx.moveTo(0, i * PIXEL_SIZE);
269
- inputCtx.lineTo(inputCanvas.width, i * PIXEL_SIZE);
270
  inputCtx.stroke();
271
  }
272
  }
 
 
 
 
 
 
 
 
273
 
274
  function handleDraw(event) {
275
  if (!isDrawing) return;
276
  const rect = inputCanvas.getBoundingClientRect();
277
- const x = Math.floor((event.clientX - rect.left) / PIXEL_SIZE);
278
- const y = Math.floor((event.clientY - rect.top) / PIXEL_SIZE);
279
- if (x >= 0 && x < INPUT_DIM && y >= 0 && y < INPUT_DIM) {
280
  if (inputGrid[y][x] !== currentColorIndex) {
281
  inputGrid[y][x] = currentColorIndex;
282
  drawInputGrid();
@@ -288,26 +304,26 @@ document.addEventListener('DOMContentLoaded', () => {
288
  inputCanvas.addEventListener('mouseup', () => { isDrawing = false; });
289
  inputCanvas.addEventListener('mouseleave', () => { isDrawing = false; });
290
  inputCanvas.addEventListener('mousemove', handleDraw);
291
-
292
- clearBtn.addEventListener('click', () => {
293
- inputGrid = Array.from({ length: INPUT_DIM }, () => Array(INPUT_DIM).fill(0));
294
- drawInputGrid();
295
- });
296
 
297
- // Default pattern
 
 
 
 
298
  function createDefaultPattern() {
299
- inputGrid = Array.from({ length: INPUT_DIM }, () => Array(INPUT_DIM).fill(0));
300
- // A simple cross pattern
301
- for(let i = 5; i < 15; i++) {
302
- inputGrid[10][i] = 1; // black line
303
- inputGrid[i][10] = 2; // blue line
304
- }
305
- inputGrid[10][10] = 3; // green center
 
 
306
  }
307
 
308
 
309
- // --- WFC ALGORITHM ---
310
-
311
  class WFC {
312
  constructor(inputGrid, N, outputWidth, outputHeight, useAugmentations, visualizeCallback) {
313
  this.N = N;
@@ -322,9 +338,7 @@ document.addEventListener('DOMContentLoaded', () => {
322
  this.parseInput(inputGrid);
323
  this.buildAdjacency();
324
 
325
- // The "wave" is the main grid for the output
326
  this.wave = [];
327
- // Track entropy for quick lookup
328
  this.entropy = [];
329
  this.allPatternIndices = Array.from({ length: this.patterns.length }, (_, i) => i);
330
 
@@ -332,19 +346,18 @@ document.addEventListener('DOMContentLoaded', () => {
332
  this.wave[y] = [];
333
  this.entropy[y] = [];
334
  for (let x = 0; x < outputWidth; x++) {
335
- // Each cell in the wave can initially be any pattern
336
  this.wave[y][x] = [...this.allPatternIndices];
337
  this.entropy[y][x] = this.patterns.length;
338
  }
339
  }
340
  }
341
-
342
- // 1. Read the input and count NxN patterns.
343
  parseInput(grid) {
344
  const patternMap = new Map();
 
 
345
 
346
- for (let y = 0; y < grid.length - this.N + 1; y++) {
347
- for (let x = 0; x < grid[0].length - this.N + 1; x++) {
348
  const pattern = [];
349
  for (let dy = 0; dy < this.N; dy++) {
350
  pattern.push(...grid[y + dy].slice(x, x + this.N));
@@ -373,8 +386,6 @@ document.addEventListener('DOMContentLoaded', () => {
373
  throw new Error("No patterns found. Try a larger or more varied input, or a smaller N.");
374
  }
375
  }
376
-
377
- // (optional) Augment pattern data with rotations and reflections.
378
  getAugmentations(pattern) {
379
  const augmentations = new Set();
380
  let current = pattern;
@@ -385,7 +396,6 @@ document.addEventListener('DOMContentLoaded', () => {
385
  }
386
  return Array.from(augmentations).map(hash => hash.split(',').map(Number));
387
  }
388
-
389
  rotate(p) {
390
  const newPattern = Array(this.N * this.N);
391
  for (let y = 0; y < this.N; y++) {
@@ -395,7 +405,6 @@ document.addEventListener('DOMContentLoaded', () => {
395
  }
396
  return newPattern;
397
  }
398
-
399
  reflect(p) {
400
  const newPattern = Array(this.N * this.N);
401
  for (let y = 0; y < this.N; y++) {
@@ -405,109 +414,67 @@ document.addEventListener('DOMContentLoaded', () => {
405
  }
406
  return newPattern;
407
  }
408
-
409
- // Pre-calculate which patterns can be placed next to each other.
410
  buildAdjacency() {
411
- this.adjacency = {}; // { patternIndex: { dx, dy: [valid neighbor indices] } }
412
  for (let i = 0; i < this.patterns.length; i++) {
413
- this.adjacency[i] = {
414
- '1,0': [], // Right
415
- '-1,0': [], // Left
416
- '0,1': [], // Down
417
- '0,-1': [] // Up
418
- };
419
  }
420
-
421
  for (let i = 0; i < this.patterns.length; i++) {
422
  for (let j = 0; j < this.patterns.length; j++) {
423
- const p1 = this.patterns[i];
424
- const p2 = this.patterns[j];
425
-
426
- // Check Right: p2 is to the right of p1
427
- if (this.checkOverlap(p1, p2, 1, 0)) this.adjacency[i]['1,0'].push(j);
428
- // Check Left: p2 is to the left of p1
429
- if (this.checkOverlap(p1, p2, -1, 0)) this.adjacency[i]['-1,0'].push(j);
430
- // Check Down: p2 is below p1
431
- if (this.checkOverlap(p1, p2, 0, 1)) this.adjacency[i]['0,1'].push(j);
432
- // Check Up: p2 is above p1
433
- if (this.checkOverlap(p1, p2, 0, -1)) this.adjacency[i]['0,-1'].push(j);
434
  }
435
  }
436
  }
437
-
438
  checkOverlap(p1, p2, dx, dy) {
439
- // Checks if p2 can be placed at offset (dx, dy) from p1
440
  for (let y = 0; y < this.N; y++) {
441
  for (let x = 0; x < this.N; x++) {
442
  const nx = x - dx;
443
  const ny = y - dy;
444
  if (nx >= 0 && nx < this.N && ny >= 0 && ny < this.N) {
445
- if (p1[y * this.N + x] !== p2[ny * this.N + nx]) {
446
- return false;
447
- }
448
  }
449
  }
450
  }
451
  return true;
452
  }
453
-
454
-
455
- // 4. Main Loop
456
  async run() {
457
  let iterations = this.outputWidth * this.outputHeight;
458
  while(iterations > 0) {
459
  const collapsedCoords = this.observe();
460
- if (collapsedCoords === null) {
461
- // Finished successfully
462
- return this.generateOutput();
463
- }
464
-
465
  const contradiction = this.propagate(collapsedCoords);
466
- if (contradiction) {
467
- throw new Error("Contradiction reached. Cannot continue.");
468
- }
469
-
470
  iterations--;
471
  if (this.visualizeCallback) {
472
- await this.visualizeCallback(this.entropy, this.patterns.length);
473
- await sleep(0); // Yield to the browser to render changes
474
  }
475
  }
476
- // If loop finishes, it's also a success
477
  return this.generateOutput();
478
  }
479
-
480
- // 4a. Observation: Find the cell with the lowest entropy and collapse it.
481
  observe() {
482
  let minEntropy = Infinity;
483
  let minCoords = null;
484
-
485
- // Find cell with minimum non-zero entropy
486
  for (let y = 0; y < this.outputHeight; y++) {
487
  for (let x = 0; x < this.outputWidth; x++) {
488
- const ent = this.entropy[y][x];
489
- if (ent > 1 && ent < minEntropy) {
490
- minEntropy = ent;
491
  minCoords = [{x, y}];
492
- } else if (ent > 1 && ent === minEntropy) {
493
  minCoords.push({x, y});
494
  }
495
  }
496
  }
497
-
498
- if (minCoords === null) {
499
- return null; // All cells collapsed
500
- }
501
-
502
- // Break ties randomly
503
  const choice = minCoords[Math.floor(Math.random() * minCoords.length)];
504
  const {x, y} = choice;
505
-
506
- // Collapse the chosen cell
507
  const possiblePatterns = this.wave[y][x];
508
  const totalWeight = possiblePatterns.reduce((sum, pIndex) => sum + this.patternWeights[pIndex], 0);
509
  let rnd = Math.random() * totalWeight;
510
-
511
  let chosenPattern;
512
  for(const pIndex of possiblePatterns) {
513
  rnd -= this.patternWeights[pIndex];
@@ -516,70 +483,45 @@ document.addEventListener('DOMContentLoaded', () => {
516
  break;
517
  }
518
  }
519
-
520
  this.wave[y][x] = [chosenPattern];
521
- this.entropy[y][x] = 1;
522
-
523
  return {x, y};
524
  }
525
-
526
- // 4b. Propagation: Update neighbors based on the recent collapse.
527
  propagate(startCoords) {
528
  const stack = [startCoords];
529
-
530
  while(stack.length > 0) {
531
  const {x, y} = stack.pop();
532
  const currentPatterns = this.wave[y][x];
533
-
534
- // For each neighbor
535
  for (const [dx, dy] of [[0,1], [0,-1], [1,0], [-1,0]]) {
536
  const nx = x + dx;
537
  const ny = y + dy;
538
-
539
  if (nx < 0 || nx >= this.outputWidth || ny < 0 || ny >= this.outputHeight) continue;
540
-
541
  let neighborPatterns = this.wave[ny][nx];
542
  if(neighborPatterns.length <= 1) continue;
543
-
544
- // Get all patterns that are valid neighbors in this direction
545
  const validNeighboringPatterns = new Set();
546
- const directionKey = `${-dx},${-dy}`; // from neighbor's perspective
547
  for (const pIndex of currentPatterns) {
548
- this.adjacency[pIndex][directionKey].forEach(validIndex => {
549
- validNeighboringPatterns.add(validIndex);
550
- });
551
  }
552
-
553
- // Filter neighbor's possibilities
554
  const originalLength = neighborPatterns.length;
555
  const newNeighborPatterns = neighborPatterns.filter(pIndex => validNeighboringPatterns.has(pIndex));
556
-
557
- if (newNeighborPatterns.length === 0) {
558
- return true; // Contradiction!
559
- }
560
-
561
  if (newNeighborPatterns.length < originalLength) {
562
  this.wave[ny][nx] = newNeighborPatterns;
563
- this.entropy[ny][nx] = newNeighborPatterns.length;
564
  stack.push({x: nx, y: ny});
565
  }
566
  }
567
  }
568
- return false; // No contradiction
569
  }
570
-
571
- // 5. Create the final image from the collapsed wave.
572
  generateOutput() {
573
  const outputGrid = Array.from({ length: this.outputHeight }, () => Array(this.outputWidth));
574
  for (let y = 0; y < this.outputHeight; y++) {
575
  for (let x = 0; x < this.outputWidth; x++) {
576
  if (this.wave[y][x].length > 0) {
577
- const patternIndex = this.wave[y][x][0];
578
- const pattern = this.patterns[patternIndex];
579
- // Render the top-left pixel of the pattern at this position
580
  outputGrid[y][x] = pattern[0];
581
  } else {
582
- outputGrid[y][x] = 0; // Should not happen in success case
583
  }
584
  }
585
  }
@@ -589,21 +531,18 @@ document.addEventListener('DOMContentLoaded', () => {
589
 
590
 
591
  // --- MAIN EXECUTION ---
592
-
593
  async function handleGenerate() {
594
  generateBtn.disabled = true;
595
  statusDiv.textContent = 'Starting...';
596
  statusDiv.style.color = '#333';
597
 
598
- // Resize output canvas
599
  const outputWidth = parseInt(outputWidthInput.value);
600
  const outputHeight = parseInt(outputHeightInput.value);
601
  outputCanvas.width = outputWidth * 10;
602
  outputCanvas.height = outputHeight * 10;
603
  outputCtx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
604
 
605
-
606
- await sleep(10); // Allow UI to update
607
 
608
  try {
609
  statusDiv.textContent = '1. Parsing input patterns...';
@@ -613,15 +552,16 @@ document.addEventListener('DOMContentLoaded', () => {
613
 
614
  let visualizeCallback = null;
615
  if (visualize) {
616
- visualizeCallback = (entropyGrid, maxEntropy) => {
617
  const pixelW = outputCanvas.width / outputWidth;
618
  const pixelH = outputCanvas.height / outputHeight;
619
  outputCtx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
620
  for(let y = 0; y < outputHeight; y++) {
621
  for (let x = 0; x < outputWidth; x++) {
622
- const entropy = entropyGrid[y][x];
623
  if(entropy === 1) {
624
- outputCtx.fillStyle = '#00ff00'; // Green for collapsed
 
625
  } else {
626
  const grayscale = Math.floor(255 * (1 - (entropy / maxEntropy)));
627
  outputCtx.fillStyle = `rgb(${grayscale}, ${grayscale}, ${grayscale})`;
@@ -634,7 +574,7 @@ document.addEventListener('DOMContentLoaded', () => {
634
 
635
  const wfc = new WFC(inputGrid, N, outputWidth, outputHeight, useAugmentations, visualizeCallback);
636
 
637
- statusDiv.textContent = '2. Running WFC algorithm... (this may take a moment)';
638
  await sleep(10);
639
 
640
  const resultGrid = await wfc.run();
@@ -642,14 +582,13 @@ document.addEventListener('DOMContentLoaded', () => {
642
  statusDiv.textContent = '3. Rendering output...';
643
  await sleep(10);
644
 
645
- // Draw final result
646
  const pixelW = outputCanvas.width / outputWidth;
647
  const pixelH = outputCanvas.height / outputHeight;
648
  outputCtx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
649
  for(let y = 0; y < outputHeight; y++) {
650
  for (let x = 0; x < outputWidth; x++) {
651
  const colorIndex = resultGrid[y][x];
652
- outputCtx.fillStyle = colors[colorIndex] || '#ff00ff'; // Magenta for errors
653
  outputCtx.fillRect(x * pixelW, y * pixelH, pixelW, pixelH);
654
  }
655
  }
@@ -668,12 +607,15 @@ document.addEventListener('DOMContentLoaded', () => {
668
 
669
  generateBtn.addEventListener('click', handleGenerate);
670
 
671
- // Initial setup
672
- createDefaultPattern();
673
- updatePalette();
674
- drawInputGrid();
 
 
 
 
675
  });
676
-
677
  </script>
678
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=EnovinxSchool/wave-collapse-function-not-designed-by-me" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
679
  </html>
 
24
  gap: 2rem;
25
  justify-content: center;
26
  width: 100%;
27
+ max-width: 1400px;
28
  }
29
  .canvas-container {
30
  display: flex;
 
55
  display: flex;
56
  flex-direction: column;
57
  gap: 1rem;
58
+ min-width: 280px;
59
  }
60
  .control-group {
61
  display: flex;
 
69
  padding: 0.5rem;
70
  border: 1px solid #ccc;
71
  border-radius: 4px;
72
+ box-sizing: border-box;
73
+ width: 100%;
74
  }
75
  .control-group input[type="checkbox"] {
76
  width: 20px;
 
150
  <div class="control-group">
151
  <label for="n-size">Pattern Size (N)</label>
152
  <input type="number" id="n-size" value="3" min="2" max="5">
153
+ </div>
154
+ <div class="control-group">
155
+ <label for="input-dim">Input Grid Size</label>
156
+ <input type="number" id="input-dim" value="20" min="10" max="40">
157
  </div>
158
  <div class="control-group">
159
  <label for="output-width">Output Width</label>
 
189
 
190
  <div class="canvas-container">
191
  <h2>Input Canvas</h2>
192
+ <canvas id="input-canvas" width="300" height="300"></canvas>
193
  </div>
194
 
195
  <div class="canvas-container">
 
206
  const outputCanvas = document.getElementById('output-canvas');
207
  const outputCtx = outputCanvas.getContext('2d');
208
  const nSizeInput = document.getElementById('n-size');
209
+ const inputDimInput = document.getElementById('input-dim'); // New
210
  const outputWidthInput = document.getElementById('output-width');
211
  const outputHeightInput = document.getElementById('output-height');
212
  const augmentCheckbox = document.getElementById('augment');
 
218
  const colorPicker = document.getElementById('color-picker');
219
 
220
  // --- DRAWING STATE ---
221
+ let inputDim = parseInt(inputDimInput.value);
222
+ let pixelSize = inputCanvas.width / inputDim;
223
  let isDrawing = false;
224
+ // Expanded color palette
225
+ let colors = ['#ffffff', '#000000', '#f44336', '#ffeb3b', '#4caf50', '#2196f3', '#9c27b0', '#ff9800'];
226
  let currentColorIndex = 1;
227
+ let inputGrid = [];
228
 
229
  // --- HELPER FUNCTIONS ---
230
  const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
 
258
 
259
  function drawInputGrid() {
260
  inputCtx.clearRect(0, 0, inputCanvas.width, inputCanvas.height);
261
+ for (let y = 0; y < inputDim; y++) {
262
+ for (let x = 0; x < inputDim; x++) {
263
  inputCtx.fillStyle = colors[inputGrid[y][x]];
264
+ inputCtx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
265
  }
266
  }
267
  // Draw grid lines for clarity
268
  inputCtx.strokeStyle = '#ddd';
269
  inputCtx.lineWidth = 0.5;
270
+ for (let i = 0; i <= inputDim; i++) {
271
  inputCtx.beginPath();
272
+ inputCtx.moveTo(i * pixelSize, 0);
273
+ inputCtx.lineTo(i * pixelSize, inputCanvas.height);
274
  inputCtx.stroke();
275
  inputCtx.beginPath();
276
+ inputCtx.moveTo(0, i * pixelSize);
277
+ inputCtx.lineTo(inputCanvas.width, i * pixelSize);
278
  inputCtx.stroke();
279
  }
280
  }
281
+
282
+ // NEW: Function to reset the input canvas based on new dimensions
283
+ function resetInputCanvas() {
284
+ inputDim = parseInt(inputDimInput.value);
285
+ pixelSize = inputCanvas.width / inputDim;
286
+ inputGrid = Array.from({ length: inputDim }, () => Array(inputDim).fill(0));
287
+ drawInputGrid();
288
+ }
289
 
290
  function handleDraw(event) {
291
  if (!isDrawing) return;
292
  const rect = inputCanvas.getBoundingClientRect();
293
+ const x = Math.floor((event.clientX - rect.left) / pixelSize);
294
+ const y = Math.floor((event.clientY - rect.top) / pixelSize);
295
+ if (x >= 0 && x < inputDim && y >= 0 && y < inputDim) {
296
  if (inputGrid[y][x] !== currentColorIndex) {
297
  inputGrid[y][x] = currentColorIndex;
298
  drawInputGrid();
 
304
  inputCanvas.addEventListener('mouseup', () => { isDrawing = false; });
305
  inputCanvas.addEventListener('mouseleave', () => { isDrawing = false; });
306
  inputCanvas.addEventListener('mousemove', handleDraw);
 
 
 
 
 
307
 
308
+ // NEW: Event listener for changing input grid size
309
+ inputDimInput.addEventListener('change', resetInputCanvas);
310
+ clearBtn.addEventListener('click', resetInputCanvas); // Clear button now also uses the reset function
311
+
312
+ // Default pattern, now scales with inputDim
313
  function createDefaultPattern() {
314
+ inputGrid = Array.from({ length: inputDim }, () => Array(inputDim).fill(0));
315
+ const center = Math.floor(inputDim / 2);
316
+ const start = Math.floor(inputDim / 4);
317
+ const end = inputDim - start;
318
+ for(let i = start; i < end; i++) {
319
+ inputGrid[center][i] = 1; // Black line
320
+ inputGrid[i][center] = 2; // Red line
321
+ }
322
+ inputGrid[center][center] = 3; // Yellow center
323
  }
324
 
325
 
326
+ // --- WFC ALGORITHM (No changes needed in this class) ---
 
327
  class WFC {
328
  constructor(inputGrid, N, outputWidth, outputHeight, useAugmentations, visualizeCallback) {
329
  this.N = N;
 
338
  this.parseInput(inputGrid);
339
  this.buildAdjacency();
340
 
 
341
  this.wave = [];
 
342
  this.entropy = [];
343
  this.allPatternIndices = Array.from({ length: this.patterns.length }, (_, i) => i);
344
 
 
346
  this.wave[y] = [];
347
  this.entropy[y] = [];
348
  for (let x = 0; x < outputWidth; x++) {
 
349
  this.wave[y][x] = [...this.allPatternIndices];
350
  this.entropy[y][x] = this.patterns.length;
351
  }
352
  }
353
  }
 
 
354
  parseInput(grid) {
355
  const patternMap = new Map();
356
+ const gridHeight = grid.length;
357
+ const gridWidth = grid[0].length;
358
 
359
+ for (let y = 0; y <= gridHeight - this.N; y++) {
360
+ for (let x = 0; x <= gridWidth - this.N; x++) {
361
  const pattern = [];
362
  for (let dy = 0; dy < this.N; dy++) {
363
  pattern.push(...grid[y + dy].slice(x, x + this.N));
 
386
  throw new Error("No patterns found. Try a larger or more varied input, or a smaller N.");
387
  }
388
  }
 
 
389
  getAugmentations(pattern) {
390
  const augmentations = new Set();
391
  let current = pattern;
 
396
  }
397
  return Array.from(augmentations).map(hash => hash.split(',').map(Number));
398
  }
 
399
  rotate(p) {
400
  const newPattern = Array(this.N * this.N);
401
  for (let y = 0; y < this.N; y++) {
 
405
  }
406
  return newPattern;
407
  }
 
408
  reflect(p) {
409
  const newPattern = Array(this.N * this.N);
410
  for (let y = 0; y < this.N; y++) {
 
414
  }
415
  return newPattern;
416
  }
 
 
417
  buildAdjacency() {
418
+ this.adjacency = {};
419
  for (let i = 0; i < this.patterns.length; i++) {
420
+ this.adjacency[i] = { '1,0': [], '-1,0': [], '0,1': [], '0,-1': [] };
 
 
 
 
 
421
  }
 
422
  for (let i = 0; i < this.patterns.length; i++) {
423
  for (let j = 0; j < this.patterns.length; j++) {
424
+ if (this.checkOverlap(this.patterns[i], this.patterns[j], 1, 0)) this.adjacency[i]['1,0'].push(j);
425
+ if (this.checkOverlap(this.patterns[i], this.patterns[j], -1, 0)) this.adjacency[i]['-1,0'].push(j);
426
+ if (this.checkOverlap(this.patterns[i], this.patterns[j], 0, 1)) this.adjacency[i]['0,1'].push(j);
427
+ if (this.checkOverlap(this.patterns[i], this.patterns[j], 0, -1)) this.adjacency[i]['0,-1'].push(j);
 
 
 
 
 
 
 
428
  }
429
  }
430
  }
 
431
  checkOverlap(p1, p2, dx, dy) {
 
432
  for (let y = 0; y < this.N; y++) {
433
  for (let x = 0; x < this.N; x++) {
434
  const nx = x - dx;
435
  const ny = y - dy;
436
  if (nx >= 0 && nx < this.N && ny >= 0 && ny < this.N) {
437
+ if (p1[y * this.N + x] !== p2[ny * this.N + nx]) return false;
 
 
438
  }
439
  }
440
  }
441
  return true;
442
  }
 
 
 
443
  async run() {
444
  let iterations = this.outputWidth * this.outputHeight;
445
  while(iterations > 0) {
446
  const collapsedCoords = this.observe();
447
+ if (collapsedCoords === null) return this.generateOutput();
 
 
 
 
448
  const contradiction = this.propagate(collapsedCoords);
449
+ if (contradiction) throw new Error("Contradiction reached. Cannot continue.");
 
 
 
450
  iterations--;
451
  if (this.visualizeCallback) {
452
+ await this.visualizeCallback(this.wave, this.patterns.length);
453
+ await sleep(0);
454
  }
455
  }
 
456
  return this.generateOutput();
457
  }
 
 
458
  observe() {
459
  let minEntropy = Infinity;
460
  let minCoords = null;
 
 
461
  for (let y = 0; y < this.outputHeight; y++) {
462
  for (let x = 0; x < this.outputWidth; x++) {
463
+ const possibilities = this.wave[y][x].length;
464
+ if (possibilities > 1 && possibilities < minEntropy) {
465
+ minEntropy = possibilities;
466
  minCoords = [{x, y}];
467
+ } else if (possibilities > 1 && possibilities === minEntropy) {
468
  minCoords.push({x, y});
469
  }
470
  }
471
  }
472
+ if (minCoords === null) return null;
 
 
 
 
 
473
  const choice = minCoords[Math.floor(Math.random() * minCoords.length)];
474
  const {x, y} = choice;
 
 
475
  const possiblePatterns = this.wave[y][x];
476
  const totalWeight = possiblePatterns.reduce((sum, pIndex) => sum + this.patternWeights[pIndex], 0);
477
  let rnd = Math.random() * totalWeight;
 
478
  let chosenPattern;
479
  for(const pIndex of possiblePatterns) {
480
  rnd -= this.patternWeights[pIndex];
 
483
  break;
484
  }
485
  }
 
486
  this.wave[y][x] = [chosenPattern];
 
 
487
  return {x, y};
488
  }
 
 
489
  propagate(startCoords) {
490
  const stack = [startCoords];
 
491
  while(stack.length > 0) {
492
  const {x, y} = stack.pop();
493
  const currentPatterns = this.wave[y][x];
 
 
494
  for (const [dx, dy] of [[0,1], [0,-1], [1,0], [-1,0]]) {
495
  const nx = x + dx;
496
  const ny = y + dy;
 
497
  if (nx < 0 || nx >= this.outputWidth || ny < 0 || ny >= this.outputHeight) continue;
 
498
  let neighborPatterns = this.wave[ny][nx];
499
  if(neighborPatterns.length <= 1) continue;
 
 
500
  const validNeighboringPatterns = new Set();
501
+ const directionKey = `${-dx},${-dy}`;
502
  for (const pIndex of currentPatterns) {
503
+ this.adjacency[pIndex][directionKey].forEach(validIndex => validNeighboringPatterns.add(validIndex));
 
 
504
  }
 
 
505
  const originalLength = neighborPatterns.length;
506
  const newNeighborPatterns = neighborPatterns.filter(pIndex => validNeighboringPatterns.has(pIndex));
507
+ if (newNeighborPatterns.length === 0) return true;
 
 
 
 
508
  if (newNeighborPatterns.length < originalLength) {
509
  this.wave[ny][nx] = newNeighborPatterns;
 
510
  stack.push({x: nx, y: ny});
511
  }
512
  }
513
  }
514
+ return false;
515
  }
 
 
516
  generateOutput() {
517
  const outputGrid = Array.from({ length: this.outputHeight }, () => Array(this.outputWidth));
518
  for (let y = 0; y < this.outputHeight; y++) {
519
  for (let x = 0; x < this.outputWidth; x++) {
520
  if (this.wave[y][x].length > 0) {
521
+ const pattern = this.patterns[this.wave[y][x][0]];
 
 
522
  outputGrid[y][x] = pattern[0];
523
  } else {
524
+ outputGrid[y][x] = 0;
525
  }
526
  }
527
  }
 
531
 
532
 
533
  // --- MAIN EXECUTION ---
 
534
  async function handleGenerate() {
535
  generateBtn.disabled = true;
536
  statusDiv.textContent = 'Starting...';
537
  statusDiv.style.color = '#333';
538
 
 
539
  const outputWidth = parseInt(outputWidthInput.value);
540
  const outputHeight = parseInt(outputHeightInput.value);
541
  outputCanvas.width = outputWidth * 10;
542
  outputCanvas.height = outputHeight * 10;
543
  outputCtx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
544
 
545
+ await sleep(10);
 
546
 
547
  try {
548
  statusDiv.textContent = '1. Parsing input patterns...';
 
552
 
553
  let visualizeCallback = null;
554
  if (visualize) {
555
+ visualizeCallback = (wave, maxEntropy) => {
556
  const pixelW = outputCanvas.width / outputWidth;
557
  const pixelH = outputCanvas.height / outputHeight;
558
  outputCtx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
559
  for(let y = 0; y < outputHeight; y++) {
560
  for (let x = 0; x < outputWidth; x++) {
561
+ const entropy = wave[y][x].length;
562
  if(entropy === 1) {
563
+ const colorIndex = wfc.patterns[wave[y][x][0]][0];
564
+ outputCtx.fillStyle = colors[colorIndex];
565
  } else {
566
  const grayscale = Math.floor(255 * (1 - (entropy / maxEntropy)));
567
  outputCtx.fillStyle = `rgb(${grayscale}, ${grayscale}, ${grayscale})`;
 
574
 
575
  const wfc = new WFC(inputGrid, N, outputWidth, outputHeight, useAugmentations, visualizeCallback);
576
 
577
+ statusDiv.textContent = '2. Running WFC algorithm...';
578
  await sleep(10);
579
 
580
  const resultGrid = await wfc.run();
 
582
  statusDiv.textContent = '3. Rendering output...';
583
  await sleep(10);
584
 
 
585
  const pixelW = outputCanvas.width / outputWidth;
586
  const pixelH = outputCanvas.height / outputHeight;
587
  outputCtx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
588
  for(let y = 0; y < outputHeight; y++) {
589
  for (let x = 0; x < outputWidth; x++) {
590
  const colorIndex = resultGrid[y][x];
591
+ outputCtx.fillStyle = colors[colorIndex] || '#ff00ff';
592
  outputCtx.fillRect(x * pixelW, y * pixelH, pixelW, pixelH);
593
  }
594
  }
 
607
 
608
  generateBtn.addEventListener('click', handleGenerate);
609
 
610
+ // --- INITIALIZATION ---
611
+ function initialize() {
612
+ updatePalette();
613
+ createDefaultPattern();
614
+ drawInputGrid();
615
+ }
616
+
617
+ initialize();
618
  });
 
619
  </script>
620
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=EnovinxSchool/wave-collapse-function-not-designed-by-me" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
621
  </html>