import fs from "node:fs"; import { printTime, now } from "../util"; type tile = { guard: boolean; blocked: boolean; visited: boolean; hit: { north: number; east: number; south: number; west: number }; }; const test = false; const data = fs.readFileSync( test ? "./inputs/testinput" : "./inputs/input", "utf8" ); const generateFloor = (data: string): Record => { return data .split("\n") .slice(0, -1) .reduce((floor, row, i) => { row.split("").forEach((v, col) => { floor[`${i}|${col}`] = { guard: v === "^", blocked: v === "#", visited: false, hit: { north: 0, south: 0, east: 0, west: 0, }, }; }); return floor; }, {} as Record); }; const keyToCoord = (string): [number, number] => { return string.split("|").map((v) => parseInt(v, 10)); }; let timer = now.instant(); console.log( "part one:", (() => { let floor = generateFloor(data); let direction = "north"; let [guard] = Object.entries(floor).filter((tile) => tile[1].guard)[0]; while (true) { let [x, y] = keyToCoord(guard); floor[guard].visited = true; let next = ""; switch (direction) { case "north": next = `${x - 1}|${y}`; break; case "south": next = `${x + 1}|${y}`; break; case "east": next = `${x}|${y + 1}`; break; case "west": next = `${x}|${y - 1}`; break; } if (floor[next] === undefined) { break; } if (!floor[next].blocked) { floor[next].guard = true; floor[guard].guard = false; guard = next; } else { if (direction === "north") { direction = "east"; } else if (direction === "east") { direction = "south"; } else if (direction === "south") { direction = "west"; } else if (direction === "west") { direction = "north"; } } } return Object.entries(floor).reduce((total, tile) => { return total + (tile[1].visited ? 1 : 0); }, 0); })(), printTime(now.instant().since(timer)) ); timer = now.instant(); console.log( "part two:", (() => { const floor = generateFloor(data); let direction = "north"; let guard = Object.entries(floor).filter((tile) => tile[1].guard)[0][0]; const initialGuardPosition = guard; while (true) { let [x, y] = keyToCoord(guard); floor[guard].visited = true; let next = ""; switch (direction) { case "north": next = `${x - 1}|${y}`; break; case "south": next = `${x + 1}|${y}`; break; case "east": next = `${x}|${y + 1}`; break; case "west": next = `${x}|${y - 1}`; break; } if (floor[next] === undefined) { // guard left the floor, exit break; } if (!floor[next].blocked) { // move the guard floor[next].guard = true; floor[guard].guard = false; guard = next; } else { // guard is blocked, rotate if (direction === "north") { direction = "east"; } else if (direction === "east") { direction = "south"; } else if (direction === "south") { direction = "west"; } else if (direction === "west") { direction = "north"; } } } let newObstructions = Object.entries(floor).reduce((total, tile) => { if (tile[1].visited && tile[0] !== initialGuardPosition) { return [...total, tile[0]]; } return total; }, [] as string[]); return newObstructions.reduce((total, curr) => { const floor = generateFloor(data); floor[curr].blocked = true; // iterate as above, but this time we tract how many times the guard hits any obstruction from the same direction. // if the guard ever hits one from the same direction twice, we know they're in a loop. let direction = "north"; let guard = Object.entries(floor).filter((tile) => tile[1].guard)[0][0]; while (true) { let [x, y] = keyToCoord(guard); let next = ""; switch (direction) { case "north": next = `${x - 1}|${y}`; break; case "south": next = `${x + 1}|${y}`; break; case "east": next = `${x}|${y + 1}`; break; case "west": next = `${x}|${y - 1}`; break; } if (floor[next] === undefined) { // guard left the floor, exit break; } if (!floor[next].blocked) { // move the guard floor[next].guard = true; floor[guard].guard = false; guard = next; } else { // guard is blocked, log impact and rotate if (direction === "north") { floor[next].hit.north++; if (floor[next].hit.north === 2) { return total + 1; } direction = "east"; } else if (direction === "east") { floor[next].hit.east++; if (floor[next].hit.east === 2) { return total + 1; } direction = "south"; } else if (direction === "south") { floor[next].hit.south++; if (floor[next].hit.south === 2) { return total + 1; } direction = "west"; } else if (direction === "west") { floor[next].hit.west++; if (floor[next].hit.west === 2) { return total + 1; } direction = "north"; } } } return total; }, 0); })(), printTime(now.instant().since(timer)) );