const fs = require('fs'); const input = fs.readFileSync(__dirname + '/input.txt', 'utf8'); interface Coord { x: number, y: number, }; enum Dir { Up, Right, Down, Left, }; const map: Array> = input.split("\n").map((row: string) => row.split('')); const width = map[0].length; const height = map.length; const snakeIndex = input.indexOf('S'); let snakePos = { x: snakeIndex % (width + 1), y: Math.floor(snakeIndex / height) }; let snakeDir = Dir.Right function get(pos: Coord): string { return map[pos.y][pos.x]; } function set(pos: Coord, val: string): void { map[pos.y][pos.x] = val; } function nextDir(dir: Dir): Dir { const directions = [Dir.Up, Dir.Right, Dir.Down, Dir.Left]; const i = (directions.indexOf(dir) + 1) % directions.length; return directions[i]; } function prevDir(dir: Dir): Dir { const directions = [Dir.Up, Dir.Right, Dir.Down, Dir.Left]; const i = directions.indexOf(dir) - 1; if (i < 0) { return directions[directions.length - 1]; } return directions[i]; } function printMap() { const mapClone = map.slice().map((row: Array) => row.slice()); console.log(mapClone.map((row: Array) => row.join('')).join("\n")); } function nextCoord(pos: Coord, direction: Dir): Coord { return direction === Dir.Up ? { x: pos.x, y: pos.y - 1 } : (direction === Dir.Right ? { x: pos.x + 1, y: pos.y } : (direction === Dir.Down ? { x: pos.x, y: pos.y + 1 } : { x: pos.x - 1, y: pos.y }) ); } function isDeadEnd(pos: Coord): boolean { const adjs = [Dir.Up, Dir.Right, Dir.Down, Dir.Left].map((dir) => get(nextCoord(pos, dir))).join(''); return adjs === '###.' || adjs === '##.#' || adjs === '#.##' || adjs === '.###'; } function isBranch(pos: Coord, dir: Dir): boolean { const leftDir = prevDir(dir); const rightDir = nextDir(dir); const next = nextCoord(pos, dir); const left = nextCoord(pos, leftDir); const right = nextCoord(pos, rightDir); const nextChar = get(next); const leftChar = get(left); const rightChar = get(right); return nextChar === '.' && leftChar === '.' || nextChar === '.' && rightChar === '.' || leftChar === '.' && rightChar === '.'; } function fillDeadEnd(pos: Coord): void { set(pos, '#') for (let dir of [Dir.Up, Dir.Right, Dir.Down, Dir.Left]) { const next = nextCoord(pos, dir); if (get(next) === '.') { if (isDeadEnd(next)) { fillDeadEnd(next); } } }; } for (let y = 1; y < height - 1; y++) { for (let x = 1; x < width - 1; x++) { const pos = { x, y }; const char = get(pos); if (char !== '.') { continue; } if (isDeadEnd(pos)) { fillDeadEnd(pos); } } } let fastest: number|null = null; const branchCache: { [key: string]: number } = {}; const scores: Array<[number, Array]> = []; function getScoresAfterPosition(pos: Coord, dir: Dir, currentScore = 0, path: Array = [], lastBranch: [number, Coord]|null = null): void { if (fastest !== null && currentScore >= fastest) { return; } const branch = isBranch(pos, dir); if (branch) { const cacheKey = `${pos.x},${pos.y},${dir}`; const cache = branchCache[cacheKey]; if (cache && cache < currentScore) { return; } else { branchCache[cacheKey] = currentScore; } lastBranch = [currentScore, pos]; } [dir, nextDir(dir), prevDir(dir)].forEach((newDir, i) => { const next = nextCoord(pos, newDir); const nextStr = `${next.x},${next.y}`; if (!path.includes(nextStr)) { const add = i === 0 ? 1 : 1001; const score = currentScore + add; const nextChar = get(next); if (nextChar === 'E') { if (fastest === null || score < fastest) { fastest = score; } lastBranch = null; scores.push([score, [...path, nextStr]]); return; } else if (nextChar === '.') { getScoresAfterPosition(next, newDir, score, [...path, nextStr], branch ? [score, next] : lastBranch); } } }); return; } getScoresAfterPosition(snakePos, snakeDir); const fastestScore = Math.min(...scores.map(([score]) => score)); let coordsOnPaths: Set = new Set(); scores.forEach(([score, path]) => { if (score === fastestScore) { path.forEach((coord) => coordsOnPaths.add(coord)); } }); console.log(coordsOnPaths.size + 1);