Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize grid updates #40

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions cell.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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;
}
}
166 changes: 75 additions & 91 deletions sketch.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
let tiles = [];
const tileImages = [];

let grid = [];

const DIM = 25;
const NB_UPDATES_PER_TICK=5;

function preload() {
// const path = 'rail';
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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);
}
}
Expand All @@ -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;
}
28 changes: 23 additions & 5 deletions tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down