function CellSet(definition) { definition = definition || {}; this.cells = {}; if(definition.type == 'rectangle') { this.fromRectangle(definition); } else if(definition.type == 'isochrome') { this.fromIsochrome(definition); } } CellSet.prototype.fromRectangle = function(definition) { var xMax = definition.x + definition.width; var yMax = definition.y + definition.height; for(var i = definition.x; i < xMax; i++) { for(var j = definition.y; j < yMax; j++) { this.add(i, j); } } }; CellSet.prototype.fromIsochrome = function(definition) { var originColor = definition.data[definition.x][definition.y]; var queue = [{i: definition.x, j: definition.y}]; while(queue.length > 0) { var p = queue[0]; this.add(p.i, p.j); for(var d = -1; d < 2; d += 2) { [{i: p.i + d, j: p.j}, {i: p.i, j: p.j + d}].forEach( gateKeeper(this, definition.data, queue, originColor) ); } queue.shift(); } }; CellSet.prototype.add = function(i, j) { this.cells[id(i, j)] = true; }; CellSet.prototype.remove = function(i, j) { delete this.cells[id(i, j)]; }; CellSet.prototype.contains = function(i, j) { return !!this.cells[id(i, j)]; }; CellSet.prototype.isEmpty = function() { return Object.keys(this.cells).length < 1; }; CellSet.prototype.iter = function(f) { for(var key in this.cells) { var coordinates = key.split(':'); f(coordinates[0], coordinates[1]); } } return { make: function(definition) {return new CellSet(definition);}, }; function id(i, j) { return i + ':' + j; } function gateKeeper(cellSet, data, queue, originColor) { return function(p1) { if(p1.i >= 0 && p1.i < data.length && p1.j >= 0 && p1.j < data.length && !cellSet.contains(p1.i, p1.j) && data[p1.i][p1.j] == originColor) { queue.push(p1); } } }