You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
6.1 KiB
256 lines
6.1 KiB
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);
|
|
|