AOC Solutions

For a description of this problem, please check the Advent of Code website.

Example Input:

###############
#.......#....E#
#.#.###.#.###.#
#.....#.#...#.#
#.###.#####.#.#
#.#.#.......#.#
#.#.#####.###.#
#...........#.#
###.#.#####.#.#
#...#.....#.#.#
#.#.#.###.#.#.#
#.....#...#.#.#
#.###.#.#.#.#.#
#S..#.....#...#
###############

Part 1 Solution:

const fs = require('fs');
const input = fs.readFileSync('./example-input.txt', 'utf8').trim().split('\n');
const maze = input.map(line => line.split(''));
const height = maze.length;
const width = maze[0].length;
const DIRECTIONS = ['E', 'S', 'W', 'N'];
const DELTAS = {
E: { dx: 1, dy: 0 },
S: { dx: 0, dy: 1 },
W: { dx: -1, dy: 0 },
N: { dx: 0, dy: -1 },
};
let start = null;
let end = null;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (maze[y][x] === 'S') start = { x, y };
if (maze[y][x] === 'E') end = { x, y };
}
}
if (!start || !end) {
console.error('Start or end position not found in the maze.');
process.exit(1);
}
class PriorityQueue {
constructor() {
this.elements = [];
}
enqueue(priority, item) {
this.elements.push({ priority, item });
this.elements.sort((a, b) => a.priority - b.priority);
}
dequeue() {
return this.elements.shift().item;
}
isEmpty() {
return this.elements.length === 0;
}
}
const bfs = () => {
const queue = new PriorityQueue();
queue.enqueue(0, {
x: start.x,
y: start.y,
direction: 'E',
score: 0,
});
const visited = new Map();
while (!queue.isEmpty()) {
const { x, y, direction, score } = queue.dequeue();
if (x === end.x && y === end.y) {
return score;
}
const stateKey = `${x},${y},${direction}`;
if (visited.has(stateKey) && visited.get(stateKey) <= score) {
continue;
}
visited.set(stateKey, score);
const { dx, dy } = DELTAS[direction];
const nx = x + dx;
const ny = y + dy;
if (
nx >= 0 &&
nx < width &&
ny >= 0 &&
ny < height &&
maze[ny][nx] !== '#'
) {
queue.enqueue(score + 1, { x: nx, y: ny, direction, score: score + 1 });
}
for (let i = -1; i <= 1; i += 2) {
const newDirection = DIRECTIONS[
(DIRECTIONS.indexOf(direction) + i + DIRECTIONS.length) % DIRECTIONS.length
];
const newStateKey = `${x},${y},${newDirection}`;
const newScore = score + 1000;
if (!visited.has(newStateKey) || visited.get(newStateKey) > newScore) {
queue.enqueue(newScore, { x, y, direction: newDirection, score: newScore });
}
}
}
return Infinity;
};
const lowestScore = bfs();
console.log(lowestScore);

Part 2 Solution:

const fs = require('fs');
const input = fs.readFileSync('./example-input.txt', 'utf8').trim().split('\n');
const maze = input.map(line => line.split(''));
const height = maze.length;
const width = maze[0].length;
const DIRECTIONS = ['E', 'S', 'W', 'N'];
const DELTAS = {
E: { dx: 1, dy: 0 },
S: { dx: 0, dy: 1 },
W: { dx: -1, dy: 0 },
N: { dx: 0, dy: -1 },
};
let start = null;
let end = null;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (maze[y][x] === 'S') start = { x, y };
if (maze[y][x] === 'E') end = { x, y };
}
}
class PriorityQueue {
constructor() {
this.elements = [];
}
enqueue(priority, item) {
this.elements.push({ priority, item });
this.elements.sort((a, b) => a.priority - b.priority);
}
dequeue() {
return this.elements.shift().item;
}
isEmpty() {
return this.elements.length === 0;
}
}
const explorePaths = () => {
const queue = new PriorityQueue();
queue.enqueue(0, {
x: start.x,
y: start.y,
direction: 'E',
score: 0,
path: []
});
let minScore = Infinity;
const bestPaths = [];
const visited = new Map();
while (!queue.isEmpty()) {
const { x, y, direction, score, path } = queue.dequeue();
const newPath = [...path, `${x},${y}`];
if (x === end.x && y === end.y) {
if (score < minScore) {
minScore = score;
bestPaths.length = 0;
}
if (score === minScore) {
bestPaths.push(newPath);
}
continue;
}
const stateKey = `${x},${y},${direction}`;
if (visited.has(stateKey) && visited.get(stateKey) < score) {
continue;
}
visited.set(stateKey, score);
const { dx, dy } = DELTAS[direction];
const nx = x + dx;
const ny = y + dy;
if (
nx >= 0 &&
nx < width &&
ny >= 0 &&
ny < height &&
maze[ny][nx] !== '#'
) {
queue.enqueue(score + 1, {
x: nx,
y: ny,
direction,
score: score + 1,
path: newPath
});
}
for (const i of [-1, 1]) {
const newDirection = DIRECTIONS[
(DIRECTIONS.indexOf(direction) + i + DIRECTIONS.length) % DIRECTIONS.length
];
queue.enqueue(score + 1000, {
x,
y,
direction: newDirection,
score: score + 1000,
path: newPath
});
}
}
return { minScore, bestPaths };
};
const { minScore, bestPaths } = explorePaths();
const bestPathTiles = new Set();
bestPaths.forEach(path => {
path.forEach(tile => bestPathTiles.add(tile));
});
let pathCount = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (bestPathTiles.has(`${x},${y}`)) {
maze[y][x] = 'O';
pathCount++;
}
}
}
const mazeWithPaths = maze.map(row => row.join('')).join('\n');
console.log(mazeWithPaths);
console.log(pathCount);