const fs = require('fs/promises'); const sum = (arr) => arr.reduce((total, num) => total+num, 0); const arrayOfLength = n => Array.from(Array(n).keys()).map(() => {}); const main = async () => { const input = (await fs.readFile("./input.txt", 'utf8')).split("\n").map(x => x.split("").map(x => x.charCodeAt(0))); const start = input.reduce((found, row, i) => { if(found) return found; const j = row.indexOf(83); if(j !== -1) { return [j, i]; } }, undefined); let minPath = Infinity; const maxY = input.length; const maxX = input[0].length; //Wrapped most of the context into a function that could be called over and //over to try again from differnt points const exploreMapFrom = (start) => { const visited = arrayOfLength(maxY).map(_ => arrayOfLength(maxX)); const shortestPaths = arrayOfLength(maxY).map(_ => arrayOfLength(maxX).map(_ => Infinity)); // Added this print function so I could watch an algorithm to see how // quickly it was progressing. The final algorithm ended up being so quick // that this can't even render it properly let nextPrint = 0; const print = () => { if(Date.now() > nextPrint) { process.stdout.write('\u001b[2J'); process.stdout.write('\u001b[;H'); console.log(visited.map(row => row.map(x => x ? "X": ".").join("")).join("\n")); console.log("\n\n\n") console.log("Paths found: ", minPath); nextPrint = Date.now() + 10; } }; const canVisit = ([fx,fy], [tx,ty]) => { // Don't go off the map if(Math.min(tx, ty) < 0 || tx >= maxX || ty >= maxY) { return false; } // Don't visit squares we've already visited. if(visited[ty][tx]) { return false; } // S = a and E = z let fz = input[fy][fx] === 83 ? 97 : input[fy][fx]; let tz = input[ty][tx] === 69 ? 122 : input[ty][tx]; // No climbing gear allowed return tz <= fz + 1; } const explore = (visited, loc, stepCount) => { const [x, y] = loc; //If we've already found a solution that is shorter than the path so far, //there is no reason to continue if(stepCount >= minPath) { return false; } //If we've alreayd found a shorter path to this square then there is no //sense in trying a longer path. This ended up shortening the time by //alot. if(stepCount >= shortestPaths[y][x]) { return false; } else { shortestPaths[y][x] = stepCount; } //Print the rendering that is actually usesless once I found the solution print(); if(input[y][x] === 69) { minPath = Math.min(minPath, stepCount); return true; } //Track what has been visited on the current path visited[y][x] = true const nextSteps = [[x + 1, y],[x, y + 1],[x, y - 1],[x - 1, y]]; //Try each direction const found = nextSteps.map(nextStep => { if(canVisit(loc, nextStep)) { return explore(visited, nextStep, stepCount + 1); } return false }); //Mark this one as no longer visited so we can try it again on another path. visited[y][x] = false; return false; } //start exploring explore(visited, start, 0); } // Try starting from "S" exploreMapFrom(start); //Try starting from all the "a"s for(let x = 0; x < maxX; ++x) { for(let y = 0; y < maxY; ++y) { if(input[y][x] === 97) { exploreMapFrom([x, y]); } } } console.log(minPath); } main();