class Grid {
constructor(tilesGroup, tileSize, tileSet) {
this.tilesGroup = tilesGroup;
this.tileSize = tileSize;
this.tileSet = tileSet;
this._columns = Math.floor(width / this.tileSize);
this._rows = Math.floor(height / this.tileSize);
}
drawTiles(indices2d) {
this.tilesGroup
.classed("tiles", true)
.attr("transform", `translate(${this.tileSize / 2}, ${this.tileSize / 2}) scale(${this.tileSize})`);
const allRows = this.tilesGroup
.selectAll("g.row")
.data(indices2d)
.join("g")
.classed("row", true)
.attr("transform", (_, i) => `translate(0, ${i})`);
const allTileGroups = allRows
.selectAll("g.tile")
.data(row => row)
.join("g")
.classed("tile", true)
.attr("transform", (_, i) => `translate(${i}, 0) rotate(180)`);
const allWangTiles = allTileGroups
.selectAll("path.side")
.data(tileIndex => this.tileSet.getTile(tileIndex))
.join("path")
.classed("side", true)
.attr("d", "M 0,0 l 0.5,0.5 h -1 Z")
.attr("fill", this.tileSet.colorScale)
.attr("stroke", borders ? "black" : "none")
.attr("vector-effect", "non-scaling-stroke")
.attr("transform", (_, i) => `rotate(${90 * i})`);
}
*getRandomTileIndicesWithWalls() {
const indices2d = new Array(this._rows);
for (let row = 0; row < this._rows; row++) {
indices2d[row] = new Array(this._columns);
for (let col = 0; col < this._columns; col++) {
indices2d[row][col] = null;
}
}
// fill in initial constraints
// top and bottom walls
const lastRow = this._rows - 1;
for (let col = 0; col < this._columns; col++) {
indices2d[0][col] = 0;
indices2d[lastRow][col] = 0;
}
// left and right walls
const lastColumn = this._columns - 1;
for (let row = 0; row < this._columns; row++) {
indices2d[row][0] = 0;
indices2d[row][lastColumn] = 0;
}
// yield indices2d[0];
// the center of the tiling is a random choice at every node from the set of possible tiles
// each possible tile must satisfy the constraint given by tile.allowedBy(top, right, bottom, left)
const root = new Tree(null);
let current = root;
const start = 0;
const endRows = this._rows;
const endColumns = this._columns;
let row = start;
while (row < endRows) {
let col = start;
while (col < endColumns) {
// clear current tile
indices2d[row][col] = null;
let possibleTiles;
if (current.hasChildren()) {
// we have visited here before and already removed bad nodes, try again with another node
possibleTiles = current.children.map(child => child.data);
} else {
// first time here, add all possible tiles for this location
possibleTiles = Array.from(this.getPossibleTileIndices(indices2d, row, col));
}
// if no possible tiles, go back to parent, clear subtree, and retry
if (possibleTiles.length === 0) {
if (col === start) {
// reached beginning of row, go back to end of previous row
if (row === start) {
// reached first tile, no possible tiling
return;
}
col = endColumns - 1;
row--;
} else {
col--;
}
current = current.parent;
current.clearChildren();
} else {
// add them to the tree
possibleTiles.map(tile => new Tree(tile, current)).forEach(child => current.addChild(child));
// pop a random one and set it in the grid
const possibleTile = current.popRandomChild();
if (possibleTile === undefined) {
console.log("current grid:", indices2d);
console.log("current tree:", current);
console.log("current possibilities:", possibleTiles);
console.log(`(row, col): (${row}, ${col})`);
}
indices2d[row][col] = possibleTile.data;
// try next cell
current = possibleTile;
col++;
}
}
row++;
}
for (let row = 0; row < this._rows; row++) {
yield indices2d[row];
}
// for (let row = 1; row < this._rows - 1; row++) {
// for (let col = 1; col < this._columns - 1; col++) {
// const possibleTiles = Array.from(this.getPossibleTileIndices(indices2d, row, col));
// if (possibleTiles.length === 0) {
// // TODO: walk back up tree of random choices and try another
// return;
// }
// indices2d[row][col] = chooseRandom(possibleTiles);
// }
// // yield the entire row instead of individual tiles
// // therefore this generator yields a 2D grid
// yield indices2d[row];
// }
// yield indices2d[this._rows - 1];
}
*getPossibleTileIndices(grid, row, column) {
// generate an iterable of possible tiles for a given coordinate
// the possible tiles should satisfy all neighboring tiles' constraints
const top = row > 0 ? this.tileSet.getTile(grid[row - 1][column]) : null;
const right = column < this._columns - 1 ? this.tileSet.getTile(grid[row][column + 1]) : null;
const bottom = row < this._rows - 1 ? this.tileSet.getTile(grid[row + 1][column]) : null;
const left = column > 0 ? this.tileSet.getTile(grid[row][column - 1]) : null;
for (let i = 0; i < this.tileSet.tiles.length; i++) {
const tile = this.tileSet.getTile(i);
if (tile.allowedBy(top, right, bottom, left)) {
yield i;
}
}
}
indexToRowColumn(index) {
const row = Math.floor(index / this._columns);
const column = Math.floor(index % this._columns);
return [row, column];
}
}