Skip to content

Commit 473e6c1

Browse files
committed
Day 6
1 parent 2321b65 commit 473e6c1

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

src/main/kotlin/y24/Day6.kt

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
}

src/test/kotlin/y24/Day6Test.kt

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package y24
2+
3+
import common.puzzle.Input
4+
import org.junit.jupiter.api.Assertions.assertEquals
5+
import org.junit.jupiter.api.Test
6+
7+
internal class Day6Test {
8+
private val sample = Input("""
9+
....#.....
10+
.........#
11+
..........
12+
..#.......
13+
.......#..
14+
..........
15+
.#..^.....
16+
........#.
17+
#.........
18+
......#...
19+
""".trimIndent())
20+
21+
private val day = Day6(sample)
22+
23+
@Test
24+
fun solveLevel1() {
25+
assertEquals(41, day.solveLevel1())
26+
}
27+
28+
@Test
29+
fun solveLevel2() {
30+
assertEquals(6, day.solveLevel2())
31+
}
32+
}

0 commit comments

Comments
 (0)