Skip to content

Commit 82242e7

Browse files
committed
Optimize grid updates
instead of going through all cells to get their tile options, we keep a cache of options, and only modify cells that might have changed. Idea: We start from the picked cell, and we check its neighbours. For each neighbour (up, right, down, left), if the list of options has changed, we add its own neighbours to the list of cells to check. If it didn't change, we just skip its neighbours. It is even better than going through the old grid because it propagates changes until we come to a stable state. In the old way, we only check cells once, according to the state at t-1, which can miss some option reductions and lead to infeasibility.
1 parent f1b34e5 commit 82242e7

File tree

3 files changed

+95
-71
lines changed

3 files changed

+95
-71
lines changed

cell.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
class Cell {
2-
constructor(value) {
3-
this.collapsed = false;
2+
constructor(i, j, value) {
3+
this.i = i;
4+
this.j = j;
45
if (value instanceof Array) {
56
this.options = value;
67
} else {
@@ -9,5 +10,29 @@ class Cell {
910
this.options[i] = i;
1011
}
1112
}
13+
this.collapsed = this.options.length == 1;
14+
}
15+
16+
draw(w, h) {
17+
if (this.collapsed) {
18+
let index = this.options[0];
19+
image(tiles[index].img, this.i * w, this.j * h, w, h);
20+
} else {
21+
noFill();
22+
stroke(51);
23+
rect(this.i * w, this.j * h, w, h);
24+
}
25+
}
26+
27+
validOptions(dir) {
28+
let validOptions = new Set();
29+
for (let option of this.options) {
30+
let valid = tiles[option].compatibles(dir);
31+
for (let opt of valid) {
32+
validOptions.add(opt);
33+
}
34+
}
35+
36+
return validOptions;
1237
}
1338
}

sketch.js

Lines changed: 45 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
let tiles = [];
22
const tileImages = [];
3-
43
let grid = [];
54

65
const DIM = 25;
@@ -20,7 +19,7 @@ function preload() {
2019
function removeDuplicatedTiles(tiles) {
2120
const uniqueTilesMap = {};
2221
for (const tile of tiles) {
23-
const key = tile.edges.join(','); // ex: "ABB,BCB,BBA,AAA"
22+
const key = tile.edges.join(','); // ex: 'ABB,BCB,BBA,AAA'
2423
uniqueTilesMap[key] = tile;
2524
}
2625
return Object.values(uniqueTilesMap);
@@ -80,7 +79,7 @@ function setup() {
8079
function startOver() {
8180
// Create cell for each spot on the grid
8281
for (let i = 0; i < DIM * DIM; i++) {
83-
grid[i] = new Cell(tiles.length);
82+
grid[i] = new Cell(i % DIM, floor(i / DIM), tiles.length);
8483
}
8584
}
8685

@@ -92,7 +91,7 @@ function checkValid(arr, valid) {
9291
// result in removing UP, DOWN, LEFT
9392
let element = arr[i];
9493
// console.log(element, valid.includes(element));
95-
if (!valid.includes(element)) {
94+
if (!valid.has(element)) {
9695
arr.splice(i, 1);
9796
}
9897
}
@@ -111,15 +110,7 @@ function draw() {
111110
const h = height / DIM;
112111
for (let j = 0; j < DIM; j++) {
113112
for (let i = 0; i < DIM; i++) {
114-
let cell = grid[i + j * DIM];
115-
if (cell.collapsed) {
116-
let index = cell.options[0];
117-
image(tiles[index].img, i * w, j * h, w, h);
118-
} else {
119-
noFill();
120-
stroke(51);
121-
rect(i * w, j * h, w, h);
122-
}
113+
grid[posIdx(i, j)].draw(w, h);
123114
}
124115
}
125116

@@ -155,60 +146,50 @@ function draw() {
155146
}
156147
cell.options = [pick];
157148

158-
const nextGrid = [];
159-
for (let j = 0; j < DIM; j++) {
160-
for (let i = 0; i < DIM; i++) {
161-
let index = i + j * DIM;
162-
if (grid[index].collapsed) {
163-
nextGrid[index] = grid[index];
164-
} else {
165-
let options = new Array(tiles.length).fill(0).map((x, i) => i);
166-
// Look up
167-
if (j > 0) {
168-
let up = grid[i + (j - 1) * DIM];
169-
let validOptions = [];
170-
for (let option of up.options) {
171-
let valid = tiles[option].down;
172-
validOptions = validOptions.concat(valid);
173-
}
174-
checkValid(options, validOptions);
175-
}
176-
// Look right
177-
if (i < DIM - 1) {
178-
let right = grid[i + 1 + j * DIM];
179-
let validOptions = [];
180-
for (let option of right.options) {
181-
let valid = tiles[option].left;
182-
validOptions = validOptions.concat(valid);
183-
}
184-
checkValid(options, validOptions);
185-
}
186-
// Look down
187-
if (j < DIM - 1) {
188-
let down = grid[i + (j + 1) * DIM];
189-
let validOptions = [];
190-
for (let option of down.options) {
191-
let valid = tiles[option].up;
192-
validOptions = validOptions.concat(valid);
193-
}
194-
checkValid(options, validOptions);
195-
}
196-
// Look left
197-
if (i > 0) {
198-
let left = grid[i - 1 + j * DIM];
199-
let validOptions = [];
200-
for (let option of left.options) {
201-
let valid = tiles[option].right;
202-
validOptions = validOptions.concat(valid);
203-
}
204-
checkValid(options, validOptions);
205-
}
149+
grid = optimizedNextGrid(cell);
150+
}
151+
152+
// propagate options from src to dest. If dest is above src, dir == UP.
153+
function propagate(src, dest, dir) {
154+
let oldLen = dest.options.length;
155+
checkValid(dest.options, src.validOptions(dir));
156+
return oldLen != dest.options.length;
157+
}
206158

207-
// I could immediately collapse if only one option left?
208-
nextGrid[index] = new Cell(options);
159+
function optimizedNextGrid(pick) {
160+
let touched = [posIdx(pick.i, pick.j)];
161+
162+
while (touched.length > 0) {
163+
let cell = grid[touched.pop()];
164+
165+
let check = function (i, j, dir) {
166+
const idx = posIdx(i, j);
167+
if (propagate(cell, grid[idx], dir)) {
168+
if (!touched.includes(idx)) {
169+
touched.push(idx);
170+
}
209171
}
172+
};
173+
174+
if (cell.i > 0) {
175+
check(cell.i - 1, cell.j, LEFT);
176+
}
177+
178+
if (cell.i < DIM - 1) {
179+
check(cell.i + 1, cell.j, RIGHT);
180+
}
181+
182+
if (cell.j > 0) {
183+
check(cell.i, cell.j - 1, UP);
184+
}
185+
186+
if (cell.j < DIM - 1) {
187+
check(cell.i, cell.j + 1, DOWN);
210188
}
211189
}
190+
return grid;
191+
}
212192

213-
grid = nextGrid;
193+
function posIdx(i, j) {
194+
return i + j * DIM;
214195
}

tile.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ function reverseString(s) {
44
return arr.join('');
55
}
66

7-
function compareEdge(a, b) {
7+
function compatibleEdges(a, b) {
88
return a == reverseString(b);
99
}
1010

11+
const UP = 0;
12+
const RIGHT = 1;
13+
const DOWN = 2;
14+
const LEFT = 3;
15+
1116
class Tile {
1217
constructor(img, edges, i) {
1318
this.img = img;
@@ -30,24 +35,37 @@ class Tile {
3035
if (tile.index == 5 && this.index == 5) continue;
3136

3237
// UP
33-
if (compareEdge(tile.edges[2], this.edges[0])) {
38+
if (compatibleEdges(tile.edges[DOWN], this.edges[UP])) {
3439
this.up.push(i);
3540
}
3641
// RIGHT
37-
if (compareEdge(tile.edges[3], this.edges[1])) {
42+
if (compatibleEdges(tile.edges[LEFT], this.edges[RIGHT])) {
3843
this.right.push(i);
3944
}
4045
// DOWN
41-
if (compareEdge(tile.edges[0], this.edges[2])) {
46+
if (compatibleEdges(tile.edges[UP], this.edges[DOWN])) {
4247
this.down.push(i);
4348
}
4449
// LEFT
45-
if (compareEdge(tile.edges[1], this.edges[3])) {
50+
if (compatibleEdges(tile.edges[RIGHT], this.edges[LEFT])) {
4651
this.left.push(i);
4752
}
4853
}
4954
}
5055

56+
compatibles(dir) {
57+
switch (dir) {
58+
case UP:
59+
return this.up;
60+
case RIGHT:
61+
return this.right;
62+
case DOWN:
63+
return this.down;
64+
case LEFT:
65+
return this.left;
66+
}
67+
}
68+
5169
rotate(num) {
5270
const w = this.img.width;
5371
const h = this.img.height;

0 commit comments

Comments
 (0)