|
| 1 | +import { getPuzzle } from "../../utils"; |
| 2 | + |
| 3 | +const puzzleInput = getPuzzle(__dirname).trim(); |
| 4 | + |
| 5 | +const map = puzzleInput.split("\n").map((row) => row.split("")); |
| 6 | +const GUARD_STARTING_CHAR = "^"; |
| 7 | +const OBSTACLE_CHAR = "#"; |
| 8 | +const DIRECTIONS = { |
| 9 | + UP: { x: 0, y: -1 }, |
| 10 | + RIGHT: { x: 1, y: 0 }, |
| 11 | + DOWN: { x: 0, y: 1 }, |
| 12 | + LEFT: { x: -1, y: 0 }, |
| 13 | +} as const; |
| 14 | + |
| 15 | +const DIRECTION_KEYS = Object.keys(DIRECTIONS) as Array< |
| 16 | + keyof typeof DIRECTIONS |
| 17 | +>; |
| 18 | + |
| 19 | +const MAP_WIDTH = map[0].length; |
| 20 | +const MAP_HEIGHT = map.length; |
| 21 | + |
| 22 | +type GuardPosition = { |
| 23 | + x: number; |
| 24 | + y: number; |
| 25 | + direction: keyof typeof DIRECTIONS; |
| 26 | +}; |
| 27 | + |
| 28 | +const startY = map.findIndex((row) => row.includes(GUARD_STARTING_CHAR)); |
| 29 | +const startX = map[startY].indexOf(GUARD_STARTING_CHAR); |
| 30 | + |
| 31 | +const getVisitedCells = ( |
| 32 | + mapInput: typeof map, |
| 33 | + keyFormatter: (guardPos: GuardPosition) => string = ({ x, y }) => `${x},${y}`, |
| 34 | + enableLoopDetection = false |
| 35 | +) => { |
| 36 | + let guardPosition: GuardPosition = { x: startX, y: startY, direction: "UP" }; |
| 37 | + let loopDetected = false; |
| 38 | + |
| 39 | + const visitedCells = new Set<string>(); |
| 40 | + visitedCells.add(keyFormatter(guardPosition)); |
| 41 | + |
| 42 | + while (true) { |
| 43 | + const nextMove = DIRECTIONS[guardPosition.direction]; |
| 44 | + const nextPosition = { |
| 45 | + x: Math.abs(guardPosition.x + nextMove.x), |
| 46 | + y: Math.abs(guardPosition.y + nextMove.y), |
| 47 | + }; |
| 48 | + |
| 49 | + if ( |
| 50 | + nextPosition.x < 0 || |
| 51 | + nextPosition.x >= MAP_WIDTH || |
| 52 | + nextPosition.y < 0 || |
| 53 | + nextPosition.y >= MAP_HEIGHT |
| 54 | + ) { |
| 55 | + break; |
| 56 | + } |
| 57 | + |
| 58 | + const nextCell = mapInput[nextPosition.y][nextPosition.x]; |
| 59 | + |
| 60 | + if (nextCell === OBSTACLE_CHAR) { |
| 61 | + const nextDirection = |
| 62 | + DIRECTION_KEYS[ |
| 63 | + (DIRECTION_KEYS.indexOf(guardPosition.direction) + 1) % |
| 64 | + DIRECTION_KEYS.length |
| 65 | + ]; |
| 66 | + |
| 67 | + guardPosition.direction = nextDirection; |
| 68 | + continue; |
| 69 | + } |
| 70 | + |
| 71 | + guardPosition.x = nextPosition.x; |
| 72 | + guardPosition.y = nextPosition.y; |
| 73 | + const nextKey = keyFormatter(guardPosition); |
| 74 | + |
| 75 | + if (enableLoopDetection && visitedCells.has(nextKey)) { |
| 76 | + loopDetected = true; |
| 77 | + break; |
| 78 | + } |
| 79 | + |
| 80 | + visitedCells.add(nextKey); |
| 81 | + } |
| 82 | + |
| 83 | + return { loopDetected, visitedCells }; |
| 84 | +}; |
| 85 | + |
| 86 | +// Part 1 |
| 87 | +(() => { |
| 88 | + console.time("part 1"); |
| 89 | + |
| 90 | + const { visitedCells } = getVisitedCells(map); |
| 91 | + |
| 92 | + console.log("part 1 visitedCells count ::", visitedCells.size); |
| 93 | + console.timeEnd("part 1"); |
| 94 | +})(); |
0 commit comments