213 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			213 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
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));
 |