import * as fs from "fs"; function load(filename) { let data = fs.readFileSync(filename, "utf8").trim(); const l = data.length; let i = 0; return { next: function () { const c = data[i]; i = (i + 1) % l; return c; } }; } const masks = [ 0b1000000, 0b0100000, 0b0010000, 0b0001000, 0b0000100, 0b0000010, 0b0000001, ]; const shapes = [ [0b0011110], [0b0001000, 0b0011100, 0b0001000], [0b0000100, 0b0000100, 0b0011100], [0b0010000, 0b0010000, 0b0010000, 0b0010000], [0b0011000, 0b0011000], ]; const shapes_len = shapes.length; class Shape { constructor(shape) { this.shape = shape; this.length = shape.length; } fall(field) { // returns true if the shape fell without colliding for (let i=0; i<this.length; ++i) { const next = field.get(i+1); if (next === undefined || next & this.shape[i]) { //collision field.solidify(this.shape); return false; } } field.fall(); return true; } shift(field, direction) { let collision = false; let tmp; switch(direction) { case ">": tmp = this.shape.map(line => { if (line & 0b1) { // we touch the right edge already collision = true; } return line >>> 1; }); break; case "<": tmp = this.shape.map(line => { if (line & 0b1000000) { // we touch the left edge already collision = true; } return line << 1; }); break; default: throw "invalid direction character in shape shift: " + direction; } if (collision) { return; } for (let i=0; i<this.length; ++i) { if (field.get(i) & tmp[i]) { //collision return; } } this.shape = tmp; } print() { this.shape.forEach(line => { console.log( line & 0b1000000 ? "#" : ".", line & 0b0100000 ? "#" : ".", line & 0b0010000 ? "#" : ".", line & 0b0001000 ? "#" : ".", line & 0b0000100 ? "#" : ".", line & 0b0000010 ? "#" : ".", line & 0b0000001 ? "#" : ".", ); }); console.log("======="); } } class Shaper { constructor() { this.index = 0; } next(field) { const s = new Shape(shapes[this.index]); field.accomodate(shapes[this.index].length + 3); this.index = (this.index + 1) % shapes_len; return s; } } class Field { constructor() { this.data = []; this.offset = 0; } accomodate(n) { this.offset = 0; for(let i=0; i<n; ++i) { this.data.unshift(0); } } fall() { if (this.data[0] !== 0) { // we need to fall bellow the top line? this.offset++; } else { this.data.shift(); } } get(offset) { return this.data[this.offset + offset]; } heightMap() { const max = this.data.length; let heights = [max, max, max, max, max, max, max]; heights.forEach((h, i) => { for(let j=0; j<this.data.length; ++j) { if (this.data[j] & masks[i]) { heights[i] = j; break; } } }); return heights; } print() { this.data.forEach(line => { console.log( line & 0b1000000 ? "#" : ".", line & 0b0100000 ? "#" : ".", line & 0b0010000 ? "#" : ".", line & 0b0001000 ? "#" : ".", line & 0b0000100 ? "#" : ".", line & 0b0000010 ? "#" : ".", line & 0b0000001 ? "#" : ".", ); }); console.log("-------"); } solidify(shape) { for(let i=0; i<shape.length; ++i) { this.data[i+this.offset] |= shape[i]; } } } let example = load("example"); let input = load("input"); function solve(input) { let field = new Field(); let shaper = new Shaper(); let height = 0; let heightmaps = []; const sky = 1000000000000; for(let i=0; i<sky; ++i) { let shape = shaper.next(field); do { shape.shift(field, input.next()); } while(shape.fall(field)); const heightmap = field.heightMap(); let cycle = -1; let cycleHeight = 0; heightmaps.forEach((h, n) => { if (h.heightmap.every((v, idx) => v === heightmap[idx])) { let nextCycle = heightmaps[(n+1)*2-1]; if (nextCycle !== undefined && nextCycle.heightmap.every((v, idx) => v === heightmap[idx])) { let nextCycle2 = heightmaps[(n+1)*3-1]; if (nextCycle2 !== undefined && nextCycle2.heightmap.every((v, idx) => v === heightmap[idx])) { cycle = n+1; cycleHeight = field.data.length - h.height; } } } }); if (cycle > 0) { console.log("got cycle of", cycle, "with a height of", cycleHeight, "after", i, "iterations"); const cycles = Math.floor((sky - i) / cycle); i += cycles * cycle; height = cycles * cycleHeight; console.log("computed a height of", height, "after", cycles * cycle, "additional cycles"); heightmaps = []; } heightmaps.unshift({ heightmap: heightmap, height: field.data.length }); } return height + field.data.length; } const exampleOutput = solve(example); if (exampleOutput !== 1514285714288) { console.log("Example failed with height " + exampleOutput, "diff is", exampleOutput - 1514285714288, "positive means too high"); process.exit(1); // eslint-disable-line } console.log(solve(input));