Skip to content

Commit 4ff096b

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 a207826 commit 4ff096b

File tree

3 files changed

+94
-69
lines changed

3 files changed

+94
-69
lines changed

p5js/tiled-model/cell.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
class Cell {
2-
constructor(value) {
2+
constructor(i, j, value) {
3+
this.i = i;
4+
this.j = j;
35
// Initialize the cell as not collapsed
46
this.collapsed = false;
57
// Is an array passed in?
@@ -13,5 +15,29 @@ class Cell {
1315
this.options[i] = i;
1416
}
1517
}
18+
this.collapsed = this.options.length == 1;
19+
}
20+
21+
draw(w, h) {
22+
if (this.collapsed) {
23+
let index = this.options[0];
24+
image(tiles[index].img, this.i * w, this.j * h, w, h);
25+
} else {
26+
noFill();
27+
stroke(51);
28+
rect(this.i * w, this.j * h, w, h);
29+
}
30+
}
31+
32+
validOptions(dir) {
33+
let validOptions = new Set();
34+
for (let option of this.options) {
35+
let valid = tiles[option].compatibles(dir);
36+
for (let opt of valid) {
37+
validOptions.add(opt);
38+
}
39+
}
40+
41+
return validOptions;
1642
}
1743
}

p5js/tiled-model/sketch.js

+44-63
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;
@@ -70,15 +69,15 @@ function setup() {
7069
function startOver() {
7170
// Initialize grid with cells
7271
for (let i = 0; i < DIM * DIM; i++) {
73-
grid[i] = new Cell(tiles.length);
72+
grid[i] = new Cell(i % DIM, floor(i / DIM), tiles.length);
7473
}
7574
}
7675

7776
function checkValid(arr, valid) {
7877
// Remove invalid options from array
7978
for (let i = arr.length - 1; i >= 0; i--) {
8079
let element = arr[i];
81-
if (!valid.includes(element)) {
80+
if (!valid.has(element)) {
8281
arr.splice(i, 1);
8382
}
8483
}
@@ -92,15 +91,7 @@ function draw() {
9291
const h = height / DIM;
9392
for (let j = 0; j < DIM; j++) {
9493
for (let i = 0; i < DIM; i++) {
95-
let cell = grid[i + j * DIM];
96-
if (cell.collapsed) {
97-
let index = cell.options[0];
98-
image(tiles[index].img, i * w, j * h, w, h);
99-
} else {
100-
noFill();
101-
stroke(51);
102-
rect(i * w, j * h, w, h);
103-
}
94+
grid[posIdx(i, j)].draw(w, h);
10495
}
10596
}
10697

@@ -134,60 +125,50 @@ function draw() {
134125
}
135126
cell.options = [pick];
136127

137-
// Update grid with new options
138-
const nextGrid = [];
139-
for (let j = 0; j < DIM; j++) {
140-
for (let i = 0; i < DIM; i++) {
141-
let index = i + j * DIM;
142-
if (grid[index].collapsed) {
143-
nextGrid[index] = grid[index];
144-
} else {
145-
let options = new Array(tiles.length).fill(0).map((x, i) => i);
146-
// Look up
147-
if (j > 0) {
148-
let up = grid[i + (j - 1) * DIM];
149-
let validOptions = [];
150-
for (let option of up.options) {
151-
let valid = tiles[option].down;
152-
validOptions = validOptions.concat(valid);
153-
}
154-
checkValid(options, validOptions);
155-
}
156-
// Look right
157-
if (i < DIM - 1) {
158-
let right = grid[i + 1 + j * DIM];
159-
let validOptions = [];
160-
for (let option of right.options) {
161-
let valid = tiles[option].left;
162-
validOptions = validOptions.concat(valid);
163-
}
164-
checkValid(options, validOptions);
165-
}
166-
// Look down
167-
if (j < DIM - 1) {
168-
let down = grid[i + (j + 1) * DIM];
169-
let validOptions = [];
170-
for (let option of down.options) {
171-
let valid = tiles[option].up;
172-
validOptions = validOptions.concat(valid);
173-
}
174-
checkValid(options, validOptions);
175-
}
176-
// Look left
177-
if (i > 0) {
178-
let left = grid[i - 1 + j * DIM];
179-
let validOptions = [];
180-
for (let option of left.options) {
181-
let valid = tiles[option].right;
182-
validOptions = validOptions.concat(valid);
183-
}
184-
checkValid(options, validOptions);
185-
}
128+
grid = optimizedNextGrid(cell);
129+
}
130+
131+
// propagate options from src to dest. If dest is above src, dir == UP.
132+
function propagate(src, dest, dir) {
133+
let oldLen = dest.options.length;
134+
checkValid(dest.options, src.validOptions(dir));
135+
return oldLen != dest.options.length;
136+
}
186137

187-
nextGrid[index] = new Cell(options);
138+
function optimizedNextGrid(pick) {
139+
let touched = [posIdx(pick.i, pick.j)];
140+
141+
while (touched.length > 0) {
142+
let cell = grid[touched.pop()];
143+
144+
let check = function (i, j, dir) {
145+
const idx = posIdx(i, j);
146+
if (propagate(cell, grid[idx], dir)) {
147+
if (!touched.includes(idx)) {
148+
touched.push(idx);
149+
}
188150
}
151+
};
152+
153+
if (cell.i > 0) {
154+
check(cell.i - 1, cell.j, LEFT);
155+
}
156+
157+
if (cell.i < DIM - 1) {
158+
check(cell.i + 1, cell.j, RIGHT);
159+
}
160+
161+
if (cell.j > 0) {
162+
check(cell.i, cell.j - 1, UP);
163+
}
164+
165+
if (cell.j < DIM - 1) {
166+
check(cell.i, cell.j + 1, DOWN);
189167
}
190168
}
169+
return grid;
170+
}
191171

192-
grid = nextGrid;
172+
function posIdx(i, j) {
173+
return i + j * DIM;
193174
}

p5js/tiled-model/tile.js

+23-5
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ function reverseString(s) {
66
}
77

88
// Compare if one edge matches the reverse of another
9-
function compareEdge(a, b) {
9+
function compatibleEdges(a, b) {
1010
return a == reverseString(b);
1111
}
1212

13+
const UP = 0;
14+
const RIGHT = 1;
15+
const DOWN = 2;
16+
const LEFT = 3;
17+
1318
// Class for each tile
1419
class Tile {
1520
constructor(img, edges, i) {
@@ -34,24 +39,37 @@ class Tile {
3439
if (tile.index == 5 && this.index == 5) continue;
3540

3641
// Check if the current tile's bottom edge matches this tile's top edge
37-
if (compareEdge(tile.edges[2], this.edges[0])) {
42+
if (compatibleEdges(tile.edges[DOWN], this.edges[UP])) {
3843
this.up.push(i);
3944
}
4045
// Check if the current tile's left edge matches this tile's right edge
41-
if (compareEdge(tile.edges[3], this.edges[1])) {
46+
if (compatibleEdges(tile.edges[LEFT], this.edges[RIGHT])) {
4247
this.right.push(i);
4348
}
4449
// Check if the current tile's top edge matches this tile's bottom edge
45-
if (compareEdge(tile.edges[0], this.edges[2])) {
50+
if (compatibleEdges(tile.edges[UP], this.edges[DOWN])) {
4651
this.down.push(i);
4752
}
4853
// Check if the current tile's right edge matches this tile's left edge
49-
if (compareEdge(tile.edges[1], this.edges[3])) {
54+
if (compatibleEdges(tile.edges[RIGHT], this.edges[LEFT])) {
5055
this.left.push(i);
5156
}
5257
}
5358
}
5459

60+
compatibles(dir) {
61+
switch (dir) {
62+
case UP:
63+
return this.up;
64+
case RIGHT:
65+
return this.right;
66+
case DOWN:
67+
return this.down;
68+
case LEFT:
69+
return this.left;
70+
}
71+
}
72+
5573
// Rotate the tile image and edges
5674
rotate(num) {
5775
const w = this.img.width;

0 commit comments

Comments
 (0)