Day 24

import Solution from "./solution.ts";

type Vector = [number, number, number];
type Hail = [Vector, Vector];
class Coord {
  #params: Vector;
  #hail: Hail;

  private constructor(params: Vector, hail: Hail) {
    this.#params = params;
    this.#hail = hail;
  }

  static fromParameter(h: Hail) {
    const a = -h[1][1];
    const b = h[1][0];
    const c = -(h[0][0] * a + h[0][1] * b);
    return new Coord([a / c, b / c, 1], h);
  }

  get a() {
    return this.#params[0];
  }

  get b() {
    return this.#params[1];
  }

  get c() {
    return this.#params[2];
  }

  intersect(other: Coord): [number, number] {
    return [
      (this.b - other.b) / (this.a * other.b - other.a * this.b),
      -(this.a - other.a) / (this.a * other.b - other.a * this.b),
    ];
  }

  inFuture(p: [number, number]) {
    return p.every(
      (c, i) => Math.sign(c - this.#hail[0][i]) === Math.sign(this.#hail[1][i])
    );
  }
}

const intersectsInArea = (hails: Hail[], bounds: [number, number]) => {
  let count = 0;
  for (let i = 0; i < hails.length; i++) {
    for (let j = i + 1; j < hails.length; j++) {
      const a = Coord.fromParameter(hails[i]);
      const b = Coord.fromParameter(hails[j]);
      const s = a.intersect(b);
      if (
        s.every(
          (p) =>
            p !== Number.NEGATIVE_INFINITY && p >= bounds[0] && p <= bounds[1]
        ) &&
        a.inFuture(s) &&
        b.inFuture(s)
      ) {
        count++;
      }
    }
  }
  return count;
};

const subtract = (a: Vector, b: Vector) => a.map((n, i) => n - b[i]) as Vector;

const cross = (a: Vector, b: Vector): Vector => {
  return [
    a[1] * b[2] - a[2] * b[1],
    a[2] * b[0] - a[0] * b[2],
    a[0] * b[1] - a[1] * b[0],
  ];
};

const dot = (a: Vector, b: Vector) => {
  return a.map((n, i) => n * b[i]).reduce((p, c) => p + c);
};

const solveLinear = (
  r: number,
  a: Vector,
  s: number,
  b: Vector,
  t: number,
  c: Vector
) => {
  const x = r * a[0] + s * b[0] + t * c[0];
  const y = r * a[1] + s * b[1] + t * c[1];
  const z = r * a[2] + s * b[2] + t * c[2];
  return [x, y, z] as Vector;
};
const independent = (a: Vector, b: Vector) => cross(a, b).some((p) => p != 0);

const findPlane = (l1: Hail, l2: Hail): [Vector, number] => {
  const point = subtract(l1[0], l2[0]);
  const velocity = subtract(l1[1], l2[1]);
  const vv = cross(l1[1], l2[1]);
  return [cross(point, velocity), dot(point, vv)];
};

const findRock = (l1: Hail, l2: Hail, l3: Hail): [Vector, number] => {
  const [a, A] = findPlane(l1, l2);
  const [b, B] = findPlane(l1, l3);
  const [c, C] = findPlane(l2, l3);

  const t = dot(a, cross(b, c));
  const w = solveLinear(A, cross(b, c), B, cross(c, a), C, cross(a, b)).map(
    (n) => Math.round(n / t)
  ) as Vector;

  const w1 = subtract(l1[1], w);
  const w2 = subtract(l2[1], w);
  const ww = cross(w1, w2);

  const E = dot(ww, cross(l2[0], w2));
  const F = dot(ww, cross(l1[0], w1));
  const G = dot(l1[0], ww);
  const S = dot(ww, ww);

  const rock = solveLinear(E, w1, -F, w2, G, ww);
  return [rock, S];
};

const task2 = (hails: Hail[]) => {
  // big thanks to https://www.reddit.com/r/adventofcode/comments/18pnycy/comment/kersplf/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
  const lines = [hails[0]];
  for (let i = 0; i < hails.length && lines.length < 3; i++) {
    if (lines.every((l) => independent(l[1], hails[i][1]))) {
      lines.push(hails[i]);
    }
  }
  const [rock, S] = findRock(...(lines as [Hail, Hail, Hail]));
  return rock.reduce((p, c) => p + c) / S;
};

const task = new Solution(
  (arr: Hail[]) => {
    return intersectsInArea(
      arr,
      arr.length === 5 ? [7, 27] : [200000000000000, 400000000000000]
    );
  },
  task2,
  {
    transform: (l) =>
      l
        .split(" @ ")
        .map((n) => n.split(", ").map((n) => Number.parseInt(n))) as Hail,
    sep: "\n",
  }
);
task.expect(2, 47);

export default task;

Last edited 04. April 2025 13:29