2024-12-16 22:01:40 +00:00
|
|
|
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<Array<string>> = 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<string>) => row.slice());
|
|
|
|
|
console.log(mapClone.map((row: Array<string>) => 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 scoreCache: { [key: string]: [number, Coord, Dir] } = {};
|
|
|
|
|
|
2024-12-16 22:23:04 +00:00
|
|
|
const branchCache: { [key: string]: number } = {};
|
|
|
|
|
|
|
|
|
|
const scores: Array<[number, Array<string>]> = [];
|
2024-12-16 22:01:40 +00:00
|
|
|
|
|
|
|
|
function getScoresAfterPosition(pos: Coord, dir: Dir, currentScore = 0, path: Array<string> = [], lastBranch: [number, Coord]|null = null): void {
|
|
|
|
|
if (fastest !== null && currentScore >= fastest) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const branch = isBranch(pos, dir);
|
|
|
|
|
if (branch) {
|
2024-12-16 22:23:04 +00:00
|
|
|
const cacheKey = `${pos.x},${pos.y},${dir}`;
|
|
|
|
|
const cache = branchCache[cacheKey];
|
|
|
|
|
if (cache && cache < currentScore) {
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
branchCache[cacheKey] = currentScore;
|
|
|
|
|
}
|
2024-12-16 22:01:40 +00:00
|
|
|
if (lastBranch) {
|
|
|
|
|
scoreCache[`${lastBranch[1].x},${lastBranch[1].y}`] = [currentScore - lastBranch[0], pos, dir];
|
|
|
|
|
}
|
|
|
|
|
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;
|
2024-12-16 22:23:04 +00:00
|
|
|
scores.push([score, [...path, nextStr]]);
|
2024-12-16 22:01:40 +00:00
|
|
|
return;
|
|
|
|
|
} else if (nextChar === '.') {
|
|
|
|
|
getScoresAfterPosition(next, newDir, score, [...path, nextStr], branch ? [score, next] : lastBranch);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getScoresAfterPosition(snakePos, snakeDir);
|
|
|
|
|
|
2024-12-16 22:23:04 +00:00
|
|
|
const fastestScore = Math.min(...scores.map(([score]) => score));
|
|
|
|
|
|
|
|
|
|
let coordsOnPaths: Set<string> = new Set();
|
|
|
|
|
scores.forEach(([score, path]) => {
|
|
|
|
|
if (score === fastestScore) {
|
|
|
|
|
path.forEach((coord) => coordsOnPaths.add(coord));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log(coordsOnPaths.size + 1);
|