Skip to content

Commit b9d3e9a

Browse files
committed
Day 16
1 parent 5e6372c commit b9d3e9a

File tree

3 files changed

+178
-0
lines changed

3 files changed

+178
-0
lines changed

src/main/kotlin/common/datastructures/Grid.kt

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ private val neighsYDiags = listOf(0, 1, 1, 1, 0, -1, -1, -1)
1010

1111
data class Dir(val dRow: Int, val dCol: Int)
1212
fun Dir.toPoint() = Point(dCol, dRow)
13+
fun Point.toDir() = Dir(row, col)
1314
val directions: List<Dir> = (0..3).map { i -> Dir(neighsY[i], neighsX[i]) }
1415
val directionsWithDiagonals: List<Dir> = neighsYDiags.zip(neighsXDiags) { y, x -> Dir(y, x) }
1516

src/main/kotlin/y24/Day16.kt

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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 java.util.*
8+
9+
10+
fun main() = solvePuzzle(year = 2024, day = 16) { Day16(it) }
11+
12+
class Day16(val input: Input) : Puzzle {
13+
14+
data class Parsed(
15+
val maze: Grid<Boolean>,
16+
val start: Point,
17+
val end: Point,
18+
)
19+
20+
private fun parseInput(lines: List<String>): Parsed {
21+
var start: Point? = null
22+
var end: Point? = null
23+
val grid = Grid(lines) { row, col, c ->
24+
when (c) {
25+
'#' -> true
26+
'.' -> false
27+
'S' -> {
28+
start = Point(col, row)
29+
false
30+
}
31+
'E' -> {
32+
end = Point(col, row)
33+
false
34+
}
35+
else -> error("unknown char $c")
36+
}
37+
}
38+
39+
start ?: error("no start found")
40+
end ?: error("no end found")
41+
return Parsed(grid, start!!, end!!)
42+
}
43+
44+
data class Dist(
45+
val p: Point,
46+
val dir: Dir,
47+
val cost: Int,
48+
val prev: Dist?
49+
)
50+
51+
data class Path(
52+
val cost: Int,
53+
val points: List<Point>,
54+
)
55+
56+
private fun constructPath(dist: Dist): Path {
57+
val path = mutableListOf<Point>()
58+
var d: Dist? = dist
59+
while (d != null) {
60+
path += d.p
61+
d = d.prev
62+
}
63+
64+
return Path(dist.cost, path)
65+
}
66+
67+
fun bestPaths(grid: Grid<Boolean>, start: Point, end: Point): List<Path> {
68+
val queue = PriorityQueue<Dist>(compareBy { it.cost })
69+
val visited = mutableMapOf<Pair<Point, Dir>, Int>()
70+
queue += Dist(start, Dir(0, 1), 0, null)
71+
72+
var bestCost: Int? = null
73+
val paths = mutableListOf<Path>()
74+
75+
while (queue.isNotEmpty()) {
76+
val next = queue.poll()
77+
if (bestCost != null && next.cost > bestCost) {
78+
// Found all best paths.
79+
return paths
80+
}
81+
82+
if (next.p == end) {
83+
bestCost = next.cost
84+
paths += constructPath(next)
85+
continue
86+
}
87+
88+
val key = next.p to next.dir
89+
if (key in visited && visited.getValue(key) != next.cost) {
90+
// Already visited at lower cost.
91+
// If equal cost, use this path as well, it's also a best path.
92+
continue
93+
}
94+
95+
visited[key] = next.cost
96+
97+
grid.neighbors(grid[next.p]).forEach { neigh ->
98+
if (neigh.value) {
99+
// Wall
100+
return@forEach
101+
}
102+
103+
val dirToNeigh = (neigh.toPoint() - next.p).toDir()
104+
val cost = when (dirToNeigh) {
105+
next.dir -> {
106+
// No change in direction
107+
next.cost + 1
108+
}
109+
110+
(next.dir.toPoint() * -1).toDir() -> {
111+
// 180 turn
112+
next.cost + 1 + 2000
113+
}
114+
115+
else -> {
116+
// 90 turn
117+
next.cost + 1 + 1000
118+
}
119+
}
120+
121+
queue += Dist(neigh.toPoint(), dirToNeigh, cost, next)
122+
}
123+
}
124+
125+
return paths
126+
}
127+
128+
override fun solveLevel1(): Any {
129+
val (grid, start, end) = parseInput(input.lines)
130+
return bestPaths(grid, start, end).first().cost
131+
}
132+
133+
override fun solveLevel2(): Any {
134+
val (grid, start, end) = parseInput(input.lines)
135+
return bestPaths(grid, start, end)
136+
.flatMap { it.points }
137+
.distinct()
138+
.size
139+
}
140+
}

src/test/kotlin/y24/Day16Test.kt

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 Day16Test {
8+
private val sample = Input("""
9+
###############
10+
#.......#....E#
11+
#.#.###.#.###.#
12+
#.....#.#...#.#
13+
#.###.#####.#.#
14+
#.#.#.......#.#
15+
#.#.#####.###.#
16+
#...........#.#
17+
###.#.#####.#.#
18+
#...#.....#.#.#
19+
#.#.#.###.#.#.#
20+
#.....#...#.#.#
21+
#.###.#.#.#.#.#
22+
#S..#.....#...#
23+
###############
24+
""".trimIndent())
25+
26+
private val day = Day16(sample)
27+
28+
@Test
29+
fun solveLevel1() {
30+
assertEquals(7036, day.solveLevel1())
31+
}
32+
33+
@Test
34+
fun solveLevel2() {
35+
assertEquals(45, day.solveLevel2())
36+
}
37+
}

0 commit comments

Comments
 (0)