function readAll(cb) { require("fs").readFile('./input', 'utf8', (err, data) => { var world = new World(); data.split("\n").forEach(line => { if (!line) return; var numbers = line.match(/(\d+)/g).map(i => parseInt(i)); if (line.charAt(0) == 'x') world.addData(numbers[0], numbers[0], numbers[1], numbers[2], 'x'); else world.addData(numbers[1], numbers[2], numbers[0], numbers[0], 'x'); }); cb(world); }); } function World() { this.data = {}; this.sources = [[ 500, 0 ]]; this.minX = 500; this.maxX = 500; this.minY = 0; this.maxY = 0; } World.prototype.addData = function(x1, x2, y1, y2, type) { while (y1 <= y2) { for (var j = x1; j <= x2; ++j) { this.data[y1] = this.data[y1] || {}; this.data[y1][j] = type; } ++y1; } this.minX = Math.min(x1, this.minX); this.maxX = Math.max(x2, this.maxX); if (type == 'x') { this.minY = Math.min(y1, this.minY); this.maxY = Math.max(y2, this.maxY); } } World.prototype.getChar = function(x, y, defaultValue) { return this.data[y] ? this.data[y][x] || defaultValue : defaultValue; } World.prototype.isEmpty = function(x, y) { var c = this.getChar(x, y, false); return !c || c == '|'; } World.prototype.nextClay = function(x, y, direction) { for (; x >= this.minX && x <= this.maxX; x += direction) { if (this.isEmpty(x, y +1)) return false; if (!this.isEmpty(x, y)) return x; } return false; } World.prototype.trySettle = function(minX, maxX, y) { if (minX === false || maxX === false || minX === maxX) return false; this.addData(minX +1, maxX -1, y, y, '~'); return true; } World.prototype.water = function() { var x = this.sources[0][0], y = this.sources[0][1]; while (this.isEmpty(x, y +1) && y <= this.maxY) ++y; if (y <= this.maxY) { var minX = this.nextClay(x, y, -1), maxX = this.nextClay(x, y, +1); if (this.trySettle(minX, maxX, y)) return; var found = false; for (var tmp = x; this.isEmpty(tmp, y); ++tmp) { this.addData(tmp, tmp, y, y, '|'); if (this.isEmpty(tmp, y +1)) { this.sources.push([ tmp, y ]); break; } } for (var tmp = x; this.isEmpty(tmp, y); --tmp) { this.addData(tmp, x, y, y, '|'); if (this.isEmpty(tmp, y +1)) { this.addData(tmp, x, y, y, '|'); this.sources.push([ tmp, y ]); break; } } } this.addData(x, x, this.sources[0][1], y, '|'); this.sources.splice(0, 1); } World.prototype.countWater = function() { var count = { settled: 0, unsettled: 0 }; for (var i in this.data) if (i != 0) for (var j in this.data[i]) switch (this.data[i][j]) { case '~': ++count.settled; break; case '|': ++count.unsettled; break; } count.total = count.settled +count.unsettled; return count; } World.prototype.print = function() { for (var i =this.minY; i <= this.maxY; ++i) { for (var j =this.minX; j <= this.maxX; ++j) process.stdout.write(this.getChar(j, i, ' ')); process.stdout.write("\n"); } } function run() { readAll(world => { while (world.sources.length) world.water(); //world.print(); console.log("Water cell: ", world.countWater()); }); } run();