|
| 1 | +package y24 |
| 2 | + |
| 3 | +import common.puzzle.solvePuzzle |
| 4 | +import common.puzzle.Input |
| 5 | +import common.puzzle.Puzzle |
| 6 | +import common.datastructures.* |
| 7 | +import common.ext.* |
| 8 | +import common.util.* |
| 9 | +import java.util.* |
| 10 | +import kotlin.math.* |
| 11 | +import kotlin.system.exitProcess |
| 12 | + |
| 13 | + |
| 14 | +fun main() = solvePuzzle(year = 2024, day = 6, dryRun = true) { Day6(it) } |
| 15 | + |
| 16 | +class Day6(val input: Input) : Puzzle { |
| 17 | + |
| 18 | + private fun parseInput(lines: List<String>): Pair<Grid<Boolean>, Point> { |
| 19 | + var guard: Point? = null |
| 20 | + val grid = Grid(lines.size, lines[0].length) { row, col -> |
| 21 | + when (val c = lines[row][col]) { |
| 22 | + '.' -> false |
| 23 | + '#' -> true |
| 24 | + '^' -> { |
| 25 | + guard = Point(col, row) |
| 26 | + false |
| 27 | + } |
| 28 | + else -> error("unknown cell value $c at $row, $col") |
| 29 | + } |
| 30 | + } |
| 31 | + |
| 32 | + val g = guard ?: error("did not find guard") |
| 33 | + return grid to g |
| 34 | + } |
| 35 | + |
| 36 | + data class PathResult( |
| 37 | + val points: Set<Point>, |
| 38 | + val loop: Boolean, |
| 39 | + ) |
| 40 | + |
| 41 | + private fun findPath(grid: Grid<Boolean>, start: Point): PathResult { |
| 42 | + var cur = start |
| 43 | + val visited = mutableSetOf<Point>() |
| 44 | + val visitedWithDir = mutableSetOf<Pair<Point, Dir>>() |
| 45 | + |
| 46 | + var dir = 1 |
| 47 | + while (grid.withinBounds(cur.row, cur.col)) { |
| 48 | + visited += cur |
| 49 | + if (!visitedWithDir.add(cur to directions[dir])) { |
| 50 | + return PathResult(visited, true) |
| 51 | + } |
| 52 | + |
| 53 | + // Assumes no bad input that could lead to an infinite loop |
| 54 | + while (true) { |
| 55 | + val d = directions[dir] |
| 56 | + |
| 57 | + val next = cur + d.toPoint() |
| 58 | + if (!grid.withinBounds(next.row, next.col)) { |
| 59 | + cur = next |
| 60 | + break |
| 61 | + } |
| 62 | + |
| 63 | + if (grid[next.row][next.col].value) { |
| 64 | + // Obstacle, rotate |
| 65 | + dir = (dir + 1) % 4 |
| 66 | + continue |
| 67 | + } |
| 68 | + |
| 69 | + cur = next |
| 70 | + break |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + return PathResult(visited, false) |
| 75 | + } |
| 76 | + |
| 77 | + override fun solveLevel1(): Any { |
| 78 | + val (grid, guard) = parseInput(input.lines) |
| 79 | + return findPath(grid, guard).points.size |
| 80 | + } |
| 81 | + |
| 82 | + override fun solveLevel2(): Any { |
| 83 | + val (grid, guard) = parseInput(input.lines) |
| 84 | + |
| 85 | + val normalPath = findPath(grid, guard) |
| 86 | + |
| 87 | + var numLoops = 0 |
| 88 | + for (row in 0 until grid.numRows) { |
| 89 | + for (col in 0 until grid.numCols) { |
| 90 | + val p = Point(col, row) |
| 91 | + if (p == guard) continue |
| 92 | + if (grid[row][col].value) continue |
| 93 | + |
| 94 | + if (p !in normalPath.points) { |
| 95 | + // Optimisation: p is never reached, so guard exits map. |
| 96 | + // Can skip this obstacle. |
| 97 | + continue |
| 98 | + } |
| 99 | + |
| 100 | + grid[row][col].value = true |
| 101 | + val path = findPath(grid, guard) |
| 102 | + if (path.loop) { |
| 103 | + numLoops++ |
| 104 | + } |
| 105 | + grid[row][col].value = false |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + return numLoops |
| 110 | + } |
| 111 | +} |
0 commit comments