AdventOfCode/JavaScript-TypeScript/day10/grid.ts

205 lines
5.2 KiB
TypeScript

// @link https://en.wikipedia.org/wiki/Euclidean_algorithm#Implementations
function gcd(a: number, b: number): number {
if (b === 0) return a;
return gcd(b, a % b);
}
/**
* Given an x,y point, returns an angle 0-360
* such that the top of the circle is 0, and then
* we rotate clockwise.
*
* So in the below ascii circle, if you start at
* point `0`, then you'll visit the points 1, 2, 3,
* etc., in order.
*
* 9 0 1
* 8 2
* 7 3
* 6 5 4
*
* In addition, my plane has negative y's going up
* and positive y's going down.
*
* -y ^
* |
* -x <---+---> +x
* |
* +y v
*/
function coordToAngle([x, y]: [number, number]) {
let deg = (Math.atan2(-y, x) * 180) / Math.PI;
// Pretty sure this can be simplified with a modulus, but can't see it
if (deg <= 90 && deg >= 0) {
deg = Math.abs(deg - 90);
} else if (deg < 0) {
deg = Math.abs(deg) + 90;
} else {
deg = 450 - deg;
}
return deg;
}
export class Grid {
min_x: number;
min_y: number;
max_x: number;
max_y: number;
grid: number[][]; // @TODO
asteroids: [number, number][];
constructor(input: (1|0)[][]) {
// `1` is an asteroid, `0` is open space
this.grid = JSON.parse(JSON.stringify(input));
this.asteroids = this.getAsteroidsList();
this.min_x = 0;
this.min_y = 0;
this.max_x = this.grid[0].length - 1;
this.max_y = this.grid.length - 1;
}
getAsteroidsList() {
let asteroids: [number, number][] = [];
for (let y = 0; y < this.grid.length; y++) {
for (let x = 0; x < this.grid[y].length; x++) {
if (this.grid[y][x]) {
asteroids.push([x, y]);
}
}
}
return asteroids;
}
getVectorsFromPoint(coord: [number, number], sorted_clockwise = false) {
let slopes: Record<string, boolean> = {};
const [x1, y1] = coord;
// This isn't optimized (its O(n^2)) but it works for grids of this size.
for (let y2 = 0; y2 <= this.max_y; y2++) {
for (let x2 = 0; x2 <= this.max_x; x2++) {
if (x1 === x2 && y1 === y2) {
continue;
}
let dy = y2 - y1;
let dx = x2 - x1;
let divisor = Math.abs(gcd(dy, dx));
dy /= divisor;
dx /= divisor;
// Technically I'm storing inverse slopes of `x/y` but that is so my `map` function below spits out `[x, y]` coords
slopes[`${dx}/${dy}`] = true;
}
}
const vectors_to_travel = Object.keys(slopes).map((slope_str) =>
slope_str.split("/").map((v) => parseInt(v, 10))
) as [number, number][];
if (sorted_clockwise) {
vectors_to_travel.sort((p1, p2) => {
let p1_d = coordToAngle(p1);
let p2_d = coordToAngle(p2);
return p1_d - p2_d;
});
}
return vectors_to_travel;
}
// Part one
getAsteroidWithHighestCountInLineOfSight() {
let best_count = -1;
let best_coords = null;
for (let asteroid of this.asteroids) {
let vectors = this.getVectorsFromPoint(asteroid);
let count = vectors
.map((vector) => (this.getCollisionAlongVector(asteroid, vector) ? 1 : 0))
.reduce((a: number, b) => a + b, 0);
if (count > best_count) {
best_count = count;
best_coords = asteroid;
}
}
return {
best_count,
best_coords,
};
}
// Part two
vaporizeAsteroidsFrom(start_from: [number, number] | null): number {
if (!start_from) {
({ best_coords: start_from } = this.getAsteroidWithHighestCountInLineOfSight());
const throw_error = (): [number, number] => {
throw new Error("start_from is null");
};
start_from = start_from ?? throw_error();
}
let total_vaporized = 0;
let vaporized = [];
do {
let clockwise_vectors_from_start = this.getVectorsFromPoint(start_from, true);
for (let vector of clockwise_vectors_from_start) {
let collision_coord = this.getCollisionAlongVector(start_from, vector);
if (collision_coord) {
total_vaporized++;
this.vaporize(collision_coord);
vaporized.push(collision_coord);
}
if (total_vaporized === 200) {
let [x, y] = collision_coord ?? [0, 0];
return x * 100 + y;
}
}
// } while (this.sumAllAsteroids() > 1);
} while (total_vaporized < 200);
return -Infinity;
}
getCollisionAlongVector(from: [number, number], vector: [number, number]) {
let collision_coord: [number, number] | null = null;
const [x, y] = from;
const [vx, vy] = vector;
let new_x = x + vx;
let new_y = y + vy;
while (this.pointInGrid(new_x, new_y)) {
if (this.grid[new_y][new_x]) {
collision_coord = [new_x, new_y];
break;
}
new_x += vx;
new_y += vy;
}
return collision_coord;
}
vaporize([x, y]: [number, number]) {
this.grid[y][x] = 0;
}
pointInGrid(x: number, y: number) {
return x >= this.min_x && x <= this.max_x && y >= this.min_y && y <= this.max_y;
}
sumAllAsteroids() {
let sum = 0;
for (let y = 0; y < this.grid.length; y++) {
for (let x = 0; x < this.grid[y].length; x++) {
sum += this.grid[y][x];
}
}
return sum;
}
}