Ben Ashton
1 year ago
2 changed files with 458 additions and 0 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,256 @@ |
|||||||
|
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); |
Loading…
Reference in new issue