Day 17

import Solution from "./solution.ts";

const stones = ["-", "+", "⅃", "|", "■"] as const;

class Rock {
  #formation: number[][];
  #type: (typeof stones)[number];
  static #i = 0;

  constructor(shape: (typeof stones)[number], y = Number.MAX_SAFE_INTEGER) {
    this.#type = shape;
    switch (shape) {
      case "-": {
        this.#formation = Array.from({ length: 7 }, (_, i) =>
          i > 1 && i < 6 ? [y] : []
        );
        break;
      }
      case "+": {
        this.#formation = Array.from({ length: 7 }, (_, i) => {
          if (i < 2 || i > 4) return [];
          if (i === 3) return [y, y + 1, y + 2];
          return [y + 1];
        });
        break;
      }
      case "⅃": {
        this.#formation = Array.from({ length: 7 }, (_, i) => {
          if (i < 2 || i > 4) return [];
          if (i === 4) return [y, y + 1, y + 2];
          return [y];
        });
        break;
      }
      case "|": {
        this.#formation = Array.from({ length: 7 }, (_, i) =>
          i === 2 ? [y, y + 1, y + 2, y + 3] : []
        );
        break;
      }
      case "■": {
        this.#formation = Array.from({ length: 7 }, (_, i) =>
          i > 1 && i < 4 ? [y, y + 1] : []
        );
        break;
      }
    }
  }

  get type() {
    return this.#type;
  }

  get bot() {
    return this.#formation
      .map((y, x) => ({ x, y: y[0] ?? -1 }))
      .filter(({ y }) => y !== -1);
  }

  get left() {
    return this.#formation
      .map((ys, x) => ys.map((y) => ({ x, y })))
      .filter((x) => x.length !== 0)
      .reduce(
        (prev, curr) =>
          prev.concat(
            curr.filter(({ y }) => !prev.some(({ y: py }) => py === y))
          ),
        []
      );
  }

  get right() {
    return this.#formation
      .map((ys, x) => ys.map((y) => ({ x, y })))
      .filter((x) => x.length !== 0)
      .reduceRight(
        (prev, curr) =>
          prev.concat(
            curr.filter(({ y }) => !prev.some(({ y: py }) => py === y))
          ),
        []
      );
  }

  get points() {
    return this.#formation.flatMap((ys, x) => ys.map((y) => ({ x, y })));
  }

  move(dir: ">" | "<" | "v"): void {
    switch (dir) {
      case ">": {
        const right = this.#formation.pop()!;
        if (right.length === 0) {
          this.#formation.unshift(right);
        } else {
          this.#formation.push(right);
        }
        break;
      }
      case "<": {
        const right = this.#formation.shift()!;
        if (right.length === 0) {
          this.#formation.push(right);
        } else {
          this.#formation.unshift(right);
        }
        break;
      }
      case "v": {
        for (let x = 0; x < this.#formation.length; x++) {
          for (let y = 0; y < this.#formation[x].length; y++) {
            this.#formation[x][y] -= 1;
          }
        }
        break;
      }
    }
  }

  static get i() {
    return this.#i;
  }

  static *rocks(max: number, offset = 5) {
    let y = 0 + offset;
    for (this.#i = 0; this.#i < max; this.#i++) {
      const shape = stones[this.#i % stones.length];
      y = yield new Rock(shape, y);
      y += offset;
    }
  }

  [Symbol.for("Deno.customInspect")]() {
    return Deno.inspect(this.#formation);
  }
}

class Wind {
  static #i = 0;

  static *gust(gusts: string[]) {
    for (this.#i = 0; this.#i < gusts.length; this.#i++) {
      yield gusts[this.#i] as ">" | "<";
      if (this.#i === gusts.length - 1) {
        this.#i = -1;
      }
    }
  }

  static get i() {
    return this.#i;
  }
}

function simulate(steps: number) {
  return (gusts: string[]) => {
    const cache = new Map<string, [number, number]>();
    const wind = Wind.gust(gusts);
    const cave = Array.from({ length: 7 }, () => [0]);
    const spawn = Rock.rocks(steps);
    let height = 0;
    let rock: IteratorResult<Rock, void>;
    while (!(rock = spawn.next(height)).done) {
      let settled = false;
      const key = [rock.value.type, Wind.i].join(",");
      if (cache.has(key)) {
        const [step, top] = cache.get(key)!;
        if ((steps - Rock.i) % (Rock.i - step) === 0) {
          return (
            height +
            (height - top) * Math.floor((steps - Rock.i) / (Rock.i - step))
          );
        }
      }
      while (!settled) {
        rock.value.move("v");
        const dir = wind.next().value!;
        if (
          !rock.value[dir === ">" ? "right" : "left"].some(({ x, y }) => {
            if (x === (dir === ">" ? cave.length - 1 : 0)) return true;
            return cave[x + (dir === ">" ? 1 : -1)].includes(y);
          })
        ) {
          rock.value.move(dir);
        }
        settled = rock.value.bot.some(({ x, y }) => {
          return cave[x].includes(y - 1);
        });
      }
      cache.set(key, [Rock.i, height]);
      rock.value.points.forEach(({ x, y }) => cave[x].push(y));
      height = Math.max(...cave.flat());
    }
    return height;
  };
}

const task = new Solution(simulate(2022), simulate(1000000000000), {
  sep: "",
});
task.expect(3068, 1514285714288);

export default task;

Last edited 04. April 2025 13:29