Advent-of-Code-2022/22/solution.mjs
2022-12-31 11:16:40 -07:00

257 lines
6.1 KiB
JavaScript

import { readFileSync } from 'node:fs';
const input = readFileSync('input', 'utf-8');
const [mapData, pathData] = input.split('\n\n');
const F = {
R: 0,
D: 1,
L: 2,
U: 3,
invert: (facing) => {
switch (facing) {
case F.R: return F.L;
case F.D: return F.U;
case F.L: return F.R;
case F.U: return F.D;
}
},
rotateR: (facing) => {
switch (facing) {
case F.R: return F.D;
case F.D: return F.L;
case F.L: return F.U;
case F.U: return F.R;
}
},
rotateL: (facing) => {
switch (facing) {
case F.R: return F.U;
case F.U: return F.L;
case F.L: return F.D;
case F.D: return F.R;
}
}
};
class Map {
constructor(mapData) {
this.data = mapData
.split('\n')
.filter(line => !!line.trim());
this.height = this.data.length;
this.width = this.data.reduce((max, row) => Math.max(max, row.length), 0);
this.portals = {};
}
get startingPosition() {
const pos = [1, 1];
while (this.get(...pos) !== '.') {
pos[0]++;
}
return pos;
}
cubeMode() {
if (this.width !== 150 || this.height !== 200) {
throw new Error('Cube mode only works with full size map');
}
this.portals = {};
const e1 = this._edge([51, 1], [100, 1]);
const e2 = this._edge([101, 1], [150, 1]);
const e3 = this._edge([51, 1], [51, 50]);
const e4 = this._edge([150, 1], [150, 50]);
const e5 = this._edge([101, 50], [150, 50]);
const e6 = this._edge([51, 51], [51, 100]);
const e7 = this._edge([100, 51], [100, 100]);
const e8 = this._edge([1, 101], [50, 101]);
const e9 = this._edge([1, 101], [1, 150]);
const e10 = this._edge([100, 101], [100, 150]);
const e11 = this._edge([51, 150], [100, 150]);
const e12 = this._edge([1, 151], [1, 200]);
const e13 = this._edge([50, 151], [50, 200]);
const e14 = this._edge([1, 200], [50, 200]);
this._portalEdge(e1, F.U, e12, F.R);
this._portalEdge(e2, F.U, e14, F.U);
this._portalEdge([...e3].reverse(), F.L, e9, F.R);
this._portalEdge([...e4].reverse(), F.R, e10, F.L);
this._portalEdge(e5, F.D, e7, F.L);
this._portalEdge(e6, F.L, e8, F.D);
this._portalEdge(e11, F.D, e13, F.L);
}
flatMode() {
this.portals = {};
for (let row = 1; row <= this.height; row++) {
let col = 1;
while (this.get(col, row) === ' ') col++;
const start = [col, row];
while (this.get(col + 1, row) !== ' ') col++;
const end = [col, row];
this.portals[[start, F.L]] = [end, F.L];
this.portals[[end, F.R]] = [start, F.R];
}
for (let col = 1; col <= this.width; col++) {
let row = 1;
while (this.get(col, row) === ' ') row++;
const start = [col, row];
while (this.get(col, row + 1) !== ' ') row++;
const end = [col, row];
this.portals[[start, F.U]] = [end, F.U];
this.portals[[end, F.D]] = [start, F.D];
}
}
get(col, row) {
const x = col - 1;
const y = row - 1;
if (y < 0 || y >= this.data.length || x < 0 || x >= this.data[y].length) {
return ' ';
}
return this.data[y][x];
}
nextPos(pos, facing) {
if (this.portals[[pos, facing]]) return this.portals[[pos, facing]];
return [[
pos[0] + (facing === F.R ? 1 : (facing === F.L ? -1 : 0)),
pos[1] + (facing === F.D ? 1 : (facing === F.U ? -1 : 0)),
], facing];
}
_portalEdge(fromEdge, fromFacing, toEdge, toFacing) {
if (fromEdge.length !== toEdge.length) {
throw new Error('fromEdge must be the same length as toEdge');
}
for (let i = 0; i < fromEdge.length; i++) {
const fromPos = fromEdge[i];
const toPos = toEdge[i];
this.portals[[fromPos, fromFacing]] = [toPos, toFacing];
this.portals[[toPos, F.invert(toFacing)]] =
[fromPos, F.invert(fromFacing)];
}
}
_edge(start, end) {
if (start[0] !== end[0] && start[1] !== end[1]) {
throw new Error("Edges can't be diagonal");
}
const colDiff = end[0] - start[0];
const colIncr = colDiff / (Math.abs(colDiff) || 1);
const rowDiff = end[1] - start[1];
const rowIncr = rowDiff / (Math.abs(rowDiff) || 1);
const edge = [];
let col = start[0];
let row = start[1];
edge.push([col, row]);
do {
col += colIncr;
do {
row += rowIncr;
edge.push([col, row]);
} while (row !== end[1])
} while (col !== end[0])
return edge;
}
}
class Walker {
constructor(map) {
this.map = map;
this.facing = F.R;
this.pos = this.map.startingPosition;
}
walk(paces) {
for (let p = 0; p < paces; p++) {
const [nextPos, nextFacing] = this.map.nextPos(this.pos, this.facing);
let nextTile = this.map.get(...nextPos);
if (nextTile === ' ') {
throw new Error('Escaped Map!');
}
if (nextTile === '#') {
break;
}
this.pos = nextPos;
this.facing = nextFacing;
}
}
get password() {
return (
this.pos[1] * 1000 +
this.pos[0] * 4 +
this.facing
);
}
rotateCounterclockwise() {
this.facing = F.rotateL(this.facing);
}
rotateClockwise() {
this.facing = F.rotateR(this.facing);
}
}
const instructions = pathData
.split('\n')
.filter(line => !!line.trim())
.join('')
.split(/(L|R)/)
.filter(instruction => !!instruction.trim())
.map(instruction => {
const num = parseInt(instruction);
return isNaN(num) ? instruction : num
});
function runInstructions(walker) {
for (const instruction of instructions) {
if (typeof instruction === 'number') {
walker.walk(instruction);
} else if (instruction === 'L') {
walker.rotateCounterclockwise();
} else if (instruction === 'R') {
walker.rotateClockwise();
} else {
throw new Error(`Invalid instruction: ${instruction}`);
}
}
}
const map = new Map(mapData);
map.flatMode();
const flatWalker = new Walker(map);
runInstructions(flatWalker);
console.log('The password for the flat map is:', flatWalker.password);
map.cubeMode();
const cubeWalker = new Walker(map)
runInstructions(cubeWalker);
console.log('The password for the cube map is:', cubeWalker.password);