Skip to content

Commit 29781bc

Browse files
authored
Day 13 2024 (#272)
1 parent f6a193d commit 29781bc

File tree

5 files changed

+163
-1
lines changed

5 files changed

+163
-1
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ as the value which passed the given day/phase combination
103103
#### 2024
104104
* Day 03
105105
* Regex shining today! Sorting by the match location made the single list of all matches easy to scan through.
106+
* Day 12
107+
* Not a day where I had the best solution; but a solution nonetheless. Slow scan of each field's border; ain't much, but it's honest work.
106108

107109
### Interesting approaches:
108110

@@ -153,7 +155,11 @@ as the value which passed the given day/phase combination
153155
#### 2023
154156
* Day 05 The fact that the numbers are "small" meant just starting at 0 and iterating over every location until we found one that matches a seed helps avoid the "forever" brute force solutions
155157
* Day 14 Rather than trying to make sure you found the right indices for tilting rocks east, west, or south just rotate the mirror and keep tilting north
156-
* Day 18 Learning about the Minkowski Sum for thick edge convex hull areas
158+
* Day 18 Learning about the Minkowski Sum for thick edge convex hull areas
159+
160+
#### 2024
161+
* Day 11 The fact that the order of stones didn't actually matter helped to allow us to just keep a list of how many we had.
162+
* Day 13 Being able to solve the basic with shortest path while needing math for part two is always fun re-learning linear algebra.
157163

158164
## Takeaways
159165

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package me.peckb.aoc._2024.calendar.day13
2+
3+
import com.microsoft.z3.BitVecNum
4+
import com.microsoft.z3.BitVecSort
5+
import com.microsoft.z3.Context
6+
import com.microsoft.z3.Expr
7+
import com.microsoft.z3.Status
8+
import me.peckb.aoc._2024.calendar.day13.ClawGameDijkstra.GameNode
9+
import javax.inject.Inject
10+
import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory
11+
import me.peckb.aoc.pathing.GenericIntDijkstra
12+
13+
class Day13 @Inject constructor(
14+
private val generatorFactory: InputGeneratorFactory,
15+
) {
16+
fun partOne(filename: String) = generatorFactory.forFile(filename).read { input ->
17+
input.chunked(4)
18+
.mapNotNull { (a, b, p, _) ->
19+
val aSpeed = parse(a.split("A: ").last()).let{ (x, y) -> Speed(x, y) }
20+
val bSpeed = parse(b.split("B: ").last()).let{ (x, y) -> Speed(x, y) }
21+
val prize = parse(p.split(": ").last()).let{ (x, y) -> GameNode(x, y) }
22+
23+
val game = ClawGameDijkstra(aSpeed, bSpeed, prize)
24+
25+
val solutions = game.solve(GameNode(0, 0).withGame(game), prize.withGame(game))
26+
27+
solutions[prize]
28+
}
29+
.sum()
30+
}
31+
32+
fun partTwo(filename: String) = generatorFactory.forFile(filename).read { input ->
33+
input.chunked(4)
34+
.mapNotNull { (a, b, p, _) ->
35+
val aSpeed = parse(a.split("A: ").last()).let{ (x, y) -> Speed(x, y) }
36+
val bSpeed = parse(b.split("B: ").last()).let{ (x, y) -> Speed(x, y) }
37+
val prize = parse(p.split(": ").last()).let{ (x, y) -> x + BUFFER to y + BUFFER }
38+
39+
Context().use { ctx ->
40+
val solver = ctx.mkSolver()
41+
42+
val longType = ctx.mkBitVecSort(64)
43+
fun variableOf(name: String) = ctx.mkConst(name, longType)
44+
fun valueOf(value: Long) = ctx.mkNumeral(value, longType) as BitVecNum
45+
operator fun Expr<BitVecSort>.times(t: Expr<BitVecSort>) = ctx.mkBVMul(this, t)
46+
operator fun Expr<BitVecSort>.plus(t: Expr<BitVecSort>) = ctx.mkBVAdd(this, t)
47+
infix fun Expr<BitVecSort>.equalTo(t: Expr<BitVecSort>) = ctx.mkEq(this, t)
48+
infix fun Expr<BitVecSort>.greaterThan(t: Expr<BitVecSort>) = ctx.mkBVSGT(this, t)
49+
infix fun Expr<BitVecSort>.lessThan(t: Expr<BitVecSort>) = ctx.mkBVSLT(this, t)
50+
51+
val zero = valueOf(0)
52+
val max = valueOf(500_000_000_000)
53+
val aPress = variableOf("x")
54+
val bPress = variableOf("y")
55+
val aXSpeed = valueOf(aSpeed.x.toLong())
56+
val aYSpeed = valueOf(aSpeed.y.toLong())
57+
val bXSpeed = valueOf(bSpeed.x.toLong())
58+
val bYSpeed = valueOf(bSpeed.y.toLong())
59+
val prizeX = valueOf(prize.first)
60+
val prizeY = valueOf(prize.second)
61+
62+
solver.add(aPress greaterThan zero)
63+
solver.add(bPress greaterThan zero)
64+
solver.add(aPress lessThan max)
65+
solver.add(bPress lessThan max)
66+
solver.add((aXSpeed * aPress) + (bXSpeed * bPress) equalTo prizeX)
67+
solver.add((aYSpeed * aPress) + (bYSpeed * bPress) equalTo prizeY)
68+
69+
val status = solver.check()
70+
71+
if (status == Status.SATISFIABLE) {
72+
val model = solver.model
73+
val aButtonCost = (model.evaluate(aPress, true) as BitVecNum).long * A_COST
74+
val bButtonCost = (model.evaluate(bPress, true) as BitVecNum).long * B_COST
75+
76+
aButtonCost + bButtonCost
77+
} else {
78+
null
79+
}
80+
}
81+
}
82+
.sum()
83+
}
84+
85+
private fun parse(data: String): Pair<Int, Int> {
86+
return data.split(", ").map{ it.drop(2).toInt() }.let { (x, y) -> x to y }
87+
}
88+
89+
companion object {
90+
private const val BUFFER = 10000000000000
91+
private const val A_COST = 3
92+
private const val B_COST = 1
93+
}
94+
}
95+
96+
class ClawGameDijkstra(val speedA: Speed, val speedB: Speed, val destination: GameNode) : GenericIntDijkstra<GameNode>() {
97+
98+
data class GameNode(val x: Int, val y: Int) : DijkstraNode<GameNode> {
99+
lateinit var game: ClawGameDijkstra
100+
101+
fun withGame(game: ClawGameDijkstra) = apply { this.game = game }
102+
103+
override fun neighbors(): Map<GameNode, Int> {
104+
return mutableMapOf<GameNode, Int>().apply {
105+
if (x + game.speedA.x <= game.destination.x && y + game.speedA.y <= game.destination.y) {
106+
put(GameNode(x + game.speedA.x, y + game.speedA.y).withGame(game), A_COST)
107+
}
108+
if (x + game.speedB.x <= game.destination.x && y + game.speedB.y <= game.destination.y) {
109+
put(GameNode(x + game.speedB.x, y + game.speedB.y).withGame(game), B_COST)
110+
}
111+
}
112+
}
113+
}
114+
115+
companion object {
116+
private const val A_COST = 3
117+
private const val B_COST = 1
118+
}
119+
}
120+
121+
data class Speed(val x: Int, val y: Int)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
## [Day 13: Claw Contraption](https://adventofcode.com/2024/day/13)

src/test/kotlin/me/peckb/aoc/_2024/TestDayComponent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import me.peckb.aoc._2024.calendar.day09.Day09Test
1212
import me.peckb.aoc._2024.calendar.day10.Day10Test
1313
import me.peckb.aoc._2024.calendar.day11.Day11Test
1414
import me.peckb.aoc._2024.calendar.day12.Day12Test
15+
import me.peckb.aoc._2024.calendar.day13.Day13Test
1516
import javax.inject.Singleton
1617
import me.peckb.aoc.DayComponent
1718
import me.peckb.aoc.InputModule
@@ -32,4 +33,5 @@ internal interface TestDayComponent : DayComponent {
3233
fun inject(day10Test: Day10Test)
3334
fun inject(day11Test: Day11Test)
3435
fun inject(day12Test: Day12Test)
36+
fun inject(day13Test: Day13Test)
3537
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package me.peckb.aoc._2024.calendar.day13
2+
3+
import javax.inject.Inject
4+
5+
import me.peckb.aoc._2024.DaggerTestDayComponent
6+
import org.junit.jupiter.api.Assertions.assertEquals
7+
import org.junit.jupiter.api.BeforeEach
8+
import org.junit.jupiter.api.Test
9+
10+
internal class Day13Test {
11+
@Inject
12+
lateinit var day13: Day13
13+
14+
@BeforeEach
15+
fun setup() {
16+
DaggerTestDayComponent.create().inject(this)
17+
}
18+
19+
@Test
20+
fun testDay13PartOne() {
21+
assertEquals(33209, day13.partOne(DAY_13))
22+
}
23+
24+
@Test
25+
fun testDay13PartTwo() {
26+
assertEquals(83102355665474, day13.partTwo(DAY_13))
27+
}
28+
29+
companion object {
30+
private const val DAY_13: String = "advent-of-code-input/2024/day13.input"
31+
}
32+
}

0 commit comments

Comments
 (0)