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