diff --git a/cell.js b/cell.js index 28e592f..c6cca2c 100644 --- a/cell.js +++ b/cell.js @@ -1,6 +1,7 @@ class Cell { - constructor(value) { - this.collapsed = false; + constructor(i, j, value) { + this.i = i; + this.j = j; if (value instanceof Array) { this.options = value; } else { @@ -9,5 +10,29 @@ class Cell { this.options[i] = i; } } + this.collapsed = this.options.length == 1; + } + + draw(w, h) { + if (this.collapsed) { + let index = this.options[0]; + image(tiles[index].img, this.i * w, this.j * h, w, h); + } else { + noFill(); + stroke(51); + rect(this.i * w, this.j * h, w, h); + } + } + + validOptions(dir) { + let validOptions = new Set(); + for (let option of this.options) { + let valid = tiles[option].compatibles(dir); + for (let opt of valid) { + validOptions.add(opt); + } + } + + return validOptions; } } diff --git a/sketch.js b/sketch.js index a3696aa..e6ab7dd 100644 --- a/sketch.js +++ b/sketch.js @@ -1,9 +1,9 @@ let tiles = []; const tileImages = []; - let grid = []; const DIM = 25; +const NB_UPDATES_PER_TICK=5; function preload() { // const path = 'rail'; @@ -20,7 +20,7 @@ function preload() { function removeDuplicatedTiles(tiles) { const uniqueTilesMap = {}; for (const tile of tiles) { - const key = tile.edges.join(','); // ex: "ABB,BCB,BBA,AAA" + const key = tile.edges.join(','); // ex: 'ABB,BCB,BBA,AAA' uniqueTilesMap[key] = tile; } return Object.values(uniqueTilesMap); @@ -80,7 +80,7 @@ function setup() { function startOver() { // Create cell for each spot on the grid for (let i = 0; i < DIM * DIM; i++) { - grid[i] = new Cell(tiles.length); + grid[i] = new Cell(i % DIM, floor(i / DIM), tiles.length); } } @@ -92,7 +92,7 @@ function checkValid(arr, valid) { // result in removing UP, DOWN, LEFT let element = arr[i]; // console.log(element, valid.includes(element)); - if (!valid.includes(element)) { + if (!valid.has(element)) { arr.splice(i, 1); } } @@ -111,104 +111,88 @@ function draw() { const h = height / DIM; for (let j = 0; j < DIM; j++) { for (let i = 0; i < DIM; i++) { - let cell = grid[i + j * DIM]; - if (cell.collapsed) { - let index = cell.options[0]; - image(tiles[index].img, i * w, j * h, w, h); - } else { - noFill(); - stroke(51); - rect(i * w, j * h, w, h); - } + grid[posIdx(i, j)].draw(w, h); } } - // Pick cell with least entropy - let gridCopy = grid.slice(); - gridCopy = gridCopy.filter((a) => !a.collapsed); - // console.table(grid); - // console.table(gridCopy); + for (let idxUpdate = 0; idxUpdate < NB_UPDATES_PER_TICK; idxUpdate++) { + // Pick cell with least entropy + let gridCopy = grid.slice(); + gridCopy = gridCopy.filter((a) => !a.collapsed); + // console.table(grid); + // console.table(gridCopy); - if (gridCopy.length == 0) { - return; - } - gridCopy.sort((a, b) => { - return a.options.length - b.options.length; - }); - - let len = gridCopy[0].options.length; - let stopIndex = 0; - for (let i = 1; i < gridCopy.length; i++) { - if (gridCopy[i].options.length > len) { - stopIndex = i; - break; + if (gridCopy.length == 0) { + return; } - } + gridCopy.sort((a, b) => { + return a.options.length - b.options.length; + }); + + let len = gridCopy[0].options.length; + let stopIndex = 0; + for (let i = 1; i < gridCopy.length; i++) { + if (gridCopy[i].options.length > len) { + stopIndex = i; + break; + } + } + + if (stopIndex > 0) gridCopy.splice(stopIndex); + const cell = random(gridCopy); + cell.collapsed = true; + const pick = random(cell.options); + if (pick === undefined) { + startOver(); + return; + } + cell.options = [pick]; - if (stopIndex > 0) gridCopy.splice(stopIndex); - const cell = random(gridCopy); - cell.collapsed = true; - const pick = random(cell.options); - if (pick === undefined) { - startOver(); - return; + grid = optimizedNextGrid(cell); } - cell.options = [pick]; +} - const nextGrid = []; - for (let j = 0; j < DIM; j++) { - for (let i = 0; i < DIM; i++) { - let index = i + j * DIM; - if (grid[index].collapsed) { - nextGrid[index] = grid[index]; - } else { - let options = new Array(tiles.length).fill(0).map((x, i) => i); - // Look up - if (j > 0) { - let up = grid[i + (j - 1) * DIM]; - let validOptions = []; - for (let option of up.options) { - let valid = tiles[option].down; - validOptions = validOptions.concat(valid); - } - checkValid(options, validOptions); - } - // Look right - if (i < DIM - 1) { - let right = grid[i + 1 + j * DIM]; - let validOptions = []; - for (let option of right.options) { - let valid = tiles[option].left; - validOptions = validOptions.concat(valid); - } - checkValid(options, validOptions); - } - // Look down - if (j < DIM - 1) { - let down = grid[i + (j + 1) * DIM]; - let validOptions = []; - for (let option of down.options) { - let valid = tiles[option].up; - validOptions = validOptions.concat(valid); - } - checkValid(options, validOptions); - } - // Look left - if (i > 0) { - let left = grid[i - 1 + j * DIM]; - let validOptions = []; - for (let option of left.options) { - let valid = tiles[option].right; - validOptions = validOptions.concat(valid); - } - checkValid(options, validOptions); - } +// propagate options from src to dest. If dest is above src, dir == UP. +function propagate(src, dest, dir) { + let oldLen = dest.options.length; + checkValid(dest.options, src.validOptions(dir)); + return oldLen != dest.options.length; +} + +function optimizedNextGrid(pick) { + let touched = [posIdx(pick.i, pick.j)]; + + while (touched.length > 0) { + let cell = grid[touched.pop()]; - // I could immediately collapse if only one option left? - nextGrid[index] = new Cell(options); + let check = function (i, j, dir) { + const idx = posIdx(i, j); + if (propagate(cell, grid[idx], dir)) { + if (!touched.includes(idx)) { + touched.push(idx); + } } + }; + + if (cell.i > 0) { + check(cell.i - 1, cell.j, LEFT); + } + + if (cell.i < DIM - 1) { + check(cell.i + 1, cell.j, RIGHT); + } + + if (cell.j > 0) { + check(cell.i, cell.j - 1, UP); + } + + if (cell.j < DIM - 1) { + check(cell.i, cell.j + 1, DOWN); } } + return grid; +} - grid = nextGrid; +function posIdx(i, j) { + return i + j * DIM; } diff --git a/tile.js b/tile.js index faac022..a8bd3df 100644 --- a/tile.js +++ b/tile.js @@ -4,10 +4,15 @@ function reverseString(s) { return arr.join(''); } -function compareEdge(a, b) { +function compatibleEdges(a, b) { return a == reverseString(b); } +const UP = 0; +const RIGHT = 1; +const DOWN = 2; +const LEFT = 3; + class Tile { constructor(img, edges, i) { this.img = img; @@ -30,24 +35,37 @@ class Tile { if (tile.index == 5 && this.index == 5) continue; // UP - if (compareEdge(tile.edges[2], this.edges[0])) { + if (compatibleEdges(tile.edges[DOWN], this.edges[UP])) { this.up.push(i); } // RIGHT - if (compareEdge(tile.edges[3], this.edges[1])) { + if (compatibleEdges(tile.edges[LEFT], this.edges[RIGHT])) { this.right.push(i); } // DOWN - if (compareEdge(tile.edges[0], this.edges[2])) { + if (compatibleEdges(tile.edges[UP], this.edges[DOWN])) { this.down.push(i); } // LEFT - if (compareEdge(tile.edges[1], this.edges[3])) { + if (compatibleEdges(tile.edges[RIGHT], this.edges[LEFT])) { this.left.push(i); } } } + compatibles(dir) { + switch (dir) { + case UP: + return this.up; + case RIGHT: + return this.right; + case DOWN: + return this.down; + case LEFT: + return this.left; + } + } + rotate(num) { const w = this.img.width; const h = this.img.height;