Day 17
This commit is contained in:
		
							parent
							
								
									d0e924934e
								
							
						
					
					
						commit
						bc6c2a9426
					
				
							
								
								
									
										290
									
								
								17/solution.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								17/solution.mjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,290 @@
 | 
				
			|||||||
 | 
					import { readFileSync } from 'node:fs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const input = readFileSync('input', 'utf-8');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const shapePatterns = `
 | 
				
			||||||
 | 
					  ####   #     #  #  ##
 | 
				
			||||||
 | 
					        ###    #  #  ##
 | 
				
			||||||
 | 
					         #   ###  #
 | 
				
			||||||
 | 
					                  #
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isTouching(coordA, coordB) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    (coordA[0] === coordB[0] && Math.abs(coordA[1] - coordB[1]) === 1) ||
 | 
				
			||||||
 | 
					    (coordA[1] === coordB[1] && Math.abs(coordA[0] - coordB[0]) === 1)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isEqual(coordA, coordB) {
 | 
				
			||||||
 | 
					  return coordA[0] === coordB[0] && coordA[1] === coordB[1];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getAllConnected(coords, coordA) {
 | 
				
			||||||
 | 
					  const connected = coords.filter(coordB => isTouching(coordA, coordB));
 | 
				
			||||||
 | 
					  if (!connected.length) {
 | 
				
			||||||
 | 
					    return [coordA];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const notConnected = coords.filter(coordC =>
 | 
				
			||||||
 | 
					    !isEqual(coordC, coordA) &&
 | 
				
			||||||
 | 
					    !connected.find(coordD => isEqual(coordC, coordD))
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  return [
 | 
				
			||||||
 | 
					    coordA,
 | 
				
			||||||
 | 
					    ...connected
 | 
				
			||||||
 | 
					      .flatMap(coordE => getAllConnected(notConnected, coordE))
 | 
				
			||||||
 | 
					      .filter((coordF, i, connectedCoords) => connectedCoords.findLastIndex(
 | 
				
			||||||
 | 
					        coordG => isEqual(coordF, coordG)
 | 
				
			||||||
 | 
					      ) === i)
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Shape {
 | 
				
			||||||
 | 
					  constructor(coords, id) {
 | 
				
			||||||
 | 
					    this.id = id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!coords.length) {
 | 
				
			||||||
 | 
					      throw new Error('Empty shape coords');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Normalize coords
 | 
				
			||||||
 | 
					    const minX = coords.reduce((min, [x, _]) => Math.min(min, x), Infinity);
 | 
				
			||||||
 | 
					    const minY = coords.reduce((min, [_, y]) => Math.min(min, y), Infinity);
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					    coords = coords.map(([x, y]) => [x - minX, y - minY]);
 | 
				
			||||||
 | 
					    this.coords = coords;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.width = 1 + coords.reduce((max, [x, _]) => Math.max(max, x), 0);
 | 
				
			||||||
 | 
					    this.height = 1 + coords.reduce((max, [_, y]) => Math.max(max, y), 0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getShapes(patterns) {
 | 
				
			||||||
 | 
					  let shapeCoords = patterns
 | 
				
			||||||
 | 
					    .split('\n')
 | 
				
			||||||
 | 
					    .flatMap((line, y) => line
 | 
				
			||||||
 | 
					      .split('')
 | 
				
			||||||
 | 
					      .flatMap((c, x) => c === '#' ? [[x, y]] : [])
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const shapes = [];
 | 
				
			||||||
 | 
					  while (shapeCoords.length) {
 | 
				
			||||||
 | 
					    const shape = getAllConnected(shapeCoords, shapeCoords[0]);
 | 
				
			||||||
 | 
					    shapeCoords = shapeCoords.filter(coordA => !shape.find(coordB =>
 | 
				
			||||||
 | 
					      isEqual(coordA, coordB)
 | 
				
			||||||
 | 
					    ));
 | 
				
			||||||
 | 
					    shapes.push(shape);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return shapes.map((coords, i) => new Shape(coords, i));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Chamber {
 | 
				
			||||||
 | 
					  constructor(width) {
 | 
				
			||||||
 | 
					    this.width = width;
 | 
				
			||||||
 | 
					    this.rows = [];
 | 
				
			||||||
 | 
					    this.placements = [];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get height() {
 | 
				
			||||||
 | 
					    return this.rows.length;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get rockCount() {
 | 
				
			||||||
 | 
					    return this.placements.length;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Test if a shape will collide with anything at the given coordinates
 | 
				
			||||||
 | 
					  collides(shape, x, y) {
 | 
				
			||||||
 | 
					    for (const coord of shape.coords) {
 | 
				
			||||||
 | 
					      const gX = x + coord[0];
 | 
				
			||||||
 | 
					      const gY = y - coord[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (gX < 0 || gX > this.width - 1 || gY < 0) {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (gY > this.rows.length - 1) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.rows[gY][gX] !== ' ') {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Add a shape at the given coordinates
 | 
				
			||||||
 | 
					  add(shape, x, y) {
 | 
				
			||||||
 | 
					    const nRowsAdded = Math.max(0, y + 1 - this.rows.length);
 | 
				
			||||||
 | 
					    if (nRowsAdded > 0) {
 | 
				
			||||||
 | 
					      this.rows.push(...new Array(nRowsAdded).fill(' '.repeat(this.width)));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const lastY = this.placements.length ?
 | 
				
			||||||
 | 
					      this.placements[this.placements.length - 1].y :
 | 
				
			||||||
 | 
					      0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.placements.push({
 | 
				
			||||||
 | 
					      shapeId: shape.id,
 | 
				
			||||||
 | 
					      x,
 | 
				
			||||||
 | 
					      y,
 | 
				
			||||||
 | 
					      relativeY: y - lastY
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const coord of shape.coords) {
 | 
				
			||||||
 | 
					      const gX = x + coord[0];
 | 
				
			||||||
 | 
					      const gY = y - coord[1];
 | 
				
			||||||
 | 
					      this.rows[gY] = (
 | 
				
			||||||
 | 
					        this.rows[gY].slice(0, gX) +
 | 
				
			||||||
 | 
					        '#' +
 | 
				
			||||||
 | 
					        this.rows[gY].slice(gX + 1)
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  print() {
 | 
				
			||||||
 | 
					    for (let i = this.rows.length - 1; i >= 0; i--) {
 | 
				
			||||||
 | 
					      console.log(this.rows[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const shapes = getShapes(shapePatterns);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function* jetGenerator() {
 | 
				
			||||||
 | 
					  while (true) {
 | 
				
			||||||
 | 
					    for (const jet of input.split('').filter(c => /[<>]/.test(c))) {
 | 
				
			||||||
 | 
					      yield jet === '<' ? -1 : 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function* rockGenerator() {
 | 
				
			||||||
 | 
					  while (true) {
 | 
				
			||||||
 | 
					    for (const shape of shapes) {
 | 
				
			||||||
 | 
					      yield shape;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const chamber = new Chamber(7);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const jets = jetGenerator();
 | 
				
			||||||
 | 
					const rocks = rockGenerator();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function processRock() {
 | 
				
			||||||
 | 
					  const rock = rocks.next().value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let x = 2;
 | 
				
			||||||
 | 
					  let y = chamber.height + 2 + rock.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  while (true) {
 | 
				
			||||||
 | 
					    const jet = jets.next().value;
 | 
				
			||||||
 | 
					    if (!chamber.collides(rock, x + jet, y)) {
 | 
				
			||||||
 | 
					      x += jet;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!chamber.collides(rock, x, y - 1)) {
 | 
				
			||||||
 | 
					      y -= 1;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      chamber.add(rock, x, y);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					while (chamber.rockCount < 2022) {
 | 
				
			||||||
 | 
					  processRock();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.log(
 | 
				
			||||||
 | 
					  `The tower will be: ${chamber.height} units tall after 2022 rocks`
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function findRepetition(source, isEqual) {
 | 
				
			||||||
 | 
					  let halfSize = Math.floor(source.length / 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  while (halfSize > 0) {
 | 
				
			||||||
 | 
					    let skip = false;
 | 
				
			||||||
 | 
					    for (let i = 0; i < halfSize; i++) {
 | 
				
			||||||
 | 
					      const first = source[source.length - (2 * halfSize) + i];
 | 
				
			||||||
 | 
					      const second = source[source.length - halfSize + i];
 | 
				
			||||||
 | 
					      if (!isEqual(first, second)) {
 | 
				
			||||||
 | 
					        skip = true;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!skip) {
 | 
				
			||||||
 | 
					      return halfSize;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    halfSize--;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Add rocks until a period of repetition can be identified
 | 
				
			||||||
 | 
					function findPeriodOfRepetition() {
 | 
				
			||||||
 | 
					  while (true) {
 | 
				
			||||||
 | 
					    const periodOfRepetition = findRepetition(chamber.placements, (p1, p2) =>
 | 
				
			||||||
 | 
					      p1.shapeId === p2.shapeId &&
 | 
				
			||||||
 | 
					      p1.x === p2.x &&
 | 
				
			||||||
 | 
					      p1.relativeY === p2.relativeY
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (periodOfRepetition !== undefined) {
 | 
				
			||||||
 | 
					      return periodOfRepetition;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // Add 1000 rocks
 | 
				
			||||||
 | 
					    for (let i = 0; i < 1000; i++) {
 | 
				
			||||||
 | 
					      processRock();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const periodOfRepetition = findPeriodOfRepetition();
 | 
				
			||||||
 | 
					let changeInHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Confirm period of repetition
 | 
				
			||||||
 | 
					for (let round = 0; round < 5; round++) {
 | 
				
			||||||
 | 
					  const initialHeight = chamber.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Add period of repetition rocks
 | 
				
			||||||
 | 
					  for (let i = 0; i < periodOfRepetition; i++) {
 | 
				
			||||||
 | 
					    processRock();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const change = chamber.height - initialHeight;
 | 
				
			||||||
 | 
					  if (changeInHeight === undefined) {
 | 
				
			||||||
 | 
					    changeInHeight = change;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    if (changeInHeight !== change) {
 | 
				
			||||||
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					        `Unable to confirm period of repetition, expected change in height ` +
 | 
				
			||||||
 | 
					          `of: ${changeInHeight}, got ${change}`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rockTarget = 1000000000000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rocksRemaining = rockTarget - chamber.rockCount;
 | 
				
			||||||
 | 
					const multiple = Math.floor(rocksRemaining / periodOfRepetition);
 | 
				
			||||||
 | 
					const remainder = rocksRemaining % periodOfRepetition;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Add remaining rocks
 | 
				
			||||||
 | 
					for (let i = 0; i < remainder; i++) {
 | 
				
			||||||
 | 
					  processRock();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const targetRockHeight = chamber.height + multiple * changeInHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					console.log(
 | 
				
			||||||
 | 
					  `The tower will be: ${targetRockHeight} units tall after ${rockTarget} rocks`
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user