expeditaet
Version:
Advent of Code Solutions
126 lines (111 loc) • 3.86 kB
text/typescript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import {
BoundingBox,
DOUBLE_NEWLINE,
Direction,
GridGraphNode,
NEWLINE,
mapFirst,
task,
zip,
type Vec2String,
} from '@alexaegis/advent-of-code-lib';
import packageJson from '../package.json';
import { CubeMap } from './cube-map.class.js';
import { getFacingScore, parseMovementInstructions } from './parse.function.js';
export type Vec2EdgeString = `${Vec2String}-${Vec2String}`;
const getShortestSegment = (input: string): number =>
input
.split(NEWLINE)
.map((line) =>
[...line.matchAll(/ +/g), ...line.matchAll(/[#.]+/g)]
.map((matches) => matches[0].length)
.min(),
)
.min();
export const p2 = (input: string): number => {
const [map, rawInstructions] = input.splitIntoStringPair(DOUBLE_NEWLINE);
const instructions = parseMovementInstructions(rawInstructions);
const graph = map.toGridGraph();
const sideLength = getShortestSegment(input);
const flatCubeBox = graph.boundingBox();
const horizontalFaceCount = flatCubeBox.horizontal.length / sideLength;
const verticalFaceCount = flatCubeBox.vertical.length / sideLength;
const flatCubeFaceBox = BoundingBox.fromSize({ x: horizontalFaceCount, y: verticalFaceCount });
const flatCube = flatCubeFaceBox.createBlankMatrix((flatCubeCell) => {
const topLeft = flatCubeCell.clone().applyChange((n) => n * sideLength);
if (graph.nodes.has(topLeft.toString())) {
const bottomRight = flatCubeCell.clone().applyChange((n) => (n + 1) * sideLength - 1);
return BoundingBox.fromVectors([topLeft, bottomRight]);
} else {
return undefined;
}
});
const cube = new CubeMap(flatCube);
cube.solve();
// For every cubeface in the simplistic representation where faces are already connected
for (const cubeFace of cube.cubeGraph.nodes.values()) {
// get the box of the face on the flat representation
const faceBox = cubeFace.value;
// Then in every direction
for (const direction of Direction.cardinalDirections) {
// Then for the source edge, get all the coordinates for the nodes along that edge
const sourceEdge = faceBox
.getEdge(direction)
.map((coordinate) => graph.getNode(coordinate)!);
// Find the matching edge on the target cubeface
const targetCubeface = cubeFace.neighbours.get(direction)!.to;
const [reverseDirection] = [...targetCubeface.neighbours.entries()].find(
([, edge]) => edge.to === cubeFace,
)!;
// And collect the edge of the target face
const targetEdge = targetCubeface.value
.getEdge(reverseDirection)
.map((coordinate) => graph.getNode(coordinate)!)
.reverse();
for (const [from, to] of zip(sourceEdge, targetEdge)) {
from.neighbours.set(direction, {
from,
to,
data: reverseDirection.turn(180),
weight: from.value === to.value ? 0 : Number.POSITIVE_INFINITY,
direction,
});
}
}
}
const start = mapFirst(flatCubeBox.horizontal.collectValues(), (x) => {
const node = graph.getNode({ x, y: 0 });
return node?.value === '.' ? node : undefined;
})!;
const path: GridGraphNode[] = [start];
let position = start;
let direction = Direction.EAST;
for (const instruction of instructions) {
if (instruction === 'L') {
direction = direction.left();
} else if (instruction === 'R') {
direction = direction.right();
} else {
for (let i = 0; i < instruction; i++) {
const edge = position.neighbours.get(direction);
if (edge && edge.weight !== Number.POSITIVE_INFINITY) {
position = edge.to;
path.push(position);
if (edge.data) {
direction = edge.data as Direction;
}
}
}
}
}
if (process.env['RUN']) {
graph.printPath(path);
}
return (
1000 * (position.coordinate.y + 1) +
4 * (position.coordinate.x + 1) +
getFacingScore(direction)
);
};
await task(p2, packageJson.aoc); // 120175 ~63.39ms