# Tile and grid abstraction

This question is a small subset of the problem I am trying to solve. I am trying to learn to break problems into better abstractions.

The original problem I am trying to solve is:

Write a JS library which allows an user to select an Image and then divide the image into square tiles. Calculate the average color of each tile and render the equivalent number of svg elements from top to bottom and left to right direction. The request for each svg element should be an XHR request.

I decided to create two abstractions for now:

1. Grid
2. Tile

here is the code:

class Tile {
constructor(shape='👽', width = Tile.Width, height = Tile.Height) {
this.shape = shape;
this.width = width;
this.height = height;
}

render() {
return this.shape;
}
}
Tile.Width = 50;
Tile.Height = 50;

class Grid {
constructor(rows, cols) {
this.cells = [];
this.R = rows;
this.C = cols;
for (let row = 0; row < rows; row++) {
const cells = [];
for (let col = 0; col < cols; col++) {
cells.push({
x: col * Tile.Width,
y: row * Tile.Height,
tile: new Tile()
});
}
this.cells.push(cells);
}
}

setTile(shape, {row, col}) {
if (row < 0 || row >= this.R || col < 0 || col >= this.C) {
throw new Error("Invalid tile position");
}
this.cells[row][col] = {
x: col * Tile.Width,
y: row * Tile.Height,
tile: new Tile(shape)
};
}

render() {
let output = [];
this.cells.forEach(row => {
output.push(this.renderRow(row));
});
console.log(output.join('\n'));
}

renderRow(cells) {
let output = [];
cells.forEach(cell => {
output.push(cell.tile.render());
});
return output.join(' ');
}
}

const grid = new Grid(2, 2);
grid.setTile('👾', {row: 0, col: 1});
grid.setTile('💩', {row: 1, col: 0});
grid.setTile('👻', {row: 0, col: 1});
grid.setTile('🎃', {row: 1, col: 1});
console.log(grid.render());


Here is the live example.

Question

Does the design of the grid system is compatible with the requirement so far? I want it to be used in the canvas or some other rendering platform so is this a good design?

Does the design of the grid system is compatible with the requirement so far?

Well, maybe I don't understand how setting characters (be them Unicode if necessary, as your example shows) on each tile relates to dividing an image into squares but it seems like you would want to allow the user to upload or choose an image (perhaps from a list), which might likely be a binary file. Then you would need to do some graphical analysis of the image and divide up the area of the image (perhaps using the dimensions).

## Feedback on code

### Design

It appears that when a Grid object is created, it creates cells as plain objects that have an x, y and tile property. Then when the setTile() method is called, it re-assigns those cells, but the only thing that really changes is the tile, so the calculations of x and y seem unnecessary. Additionally, the tile object doesn't need to be recreated - the shape property could be simply re-assigned - perhaps via a setter method, but that property is public so it could be modified outside the class unless it is made private (though that is only experimental1.

### forEach with push

The render() method of the Grid uses foreach with push:

let output = [];
this.cells.forEach(row => {
output.push(this.renderRow(row));
});


That is essentially the map method re-implemented... Why not use that method?

const output = this.cells.map(row => this.renderRow(row));


Notice that const is used because that value is never re-assigned. This can even be used with the forEach, since calling push() doesn't re-assign the value.

The same principle applies for the renderRow() method.

### render method

This method does not return any values, it merely logs output to the console. However, the method is called inside a call to console.log(), which results in undefined being output to the console. Maybe that is not a concern for you if you are the only one using the console (perhaps for debugging purposes) but be aware of this if you intend on primarily using the console for output.

👽 👻
💩 🎃
undefined
=> undefined