Advent of Code 2024 - Day 13, in Kotlin - Claw Contraption
Kotlin solutions to parts 1 and 2 of Advent of Code 2024, Day 13: 'Claw Contraption'
I’m not a big fan of the entirely math-based puzzles, and this one is no exception. There are one or two every year and at this point I just accept that I’m not going to enjoy the struggle like I would with a more programming-based puzzle and seek help. Not everybody can be a fan of every puzzle every year, and that’s fine. I ended up learning something, which I guess is the goal of Advent of Code in the first place.
Per my personal help and disclosure policy , I got help on this one from this great thread on Reddit , by /u/ThunderChaser. Thank you!
If you’d rather just view the code, my GitHub Repository is here.
Puzzle Input
Let’s create a ClawMachine
(Oooooo The Claw! The Claw Chooses.)
to store all of our various coordinates. Originally I had written this with a data class to represent each x/y pair, but in the end it seemed just about as clear to represent them all individually. So we’ll have six variables to represent three x/y pairs. We’ll also write an of
function in the companion object
so we can create a new ClawMachine
from a List<String>
(our input).
// In Day13
private data class ClawMachine(
val aX: Long, val aY: Long,
val bX: Long, val bY: Long,
val prizeX: Long, val prizeY: Long,
) {
companion object {
fun of(input: List<String>): ClawMachine =
ClawMachine(
input[0].substringAfter("+").substringBefore(",").toLong(),
input[0].substringAfterLast("+").toLong(),
input[1].substringAfter("+").substringBefore(",").toLong(),
input[1].substringAfterLast("+").toLong(),
input[2].substringAfter("=").substringBefore(",").toLong(),
input[2].substringAfterLast("=").toLong()
)
}
}
While it is tempting (well not that tempting) to write a regular expression to parse the input, I find functions like substringAfter
, substringBefore
and substringAfterLast
(all from the Kotlin Standard Library) easier to follow. Especially for people who don’t read regular expressions, uh… regularly.
Next, we can take our input
as a List<String>
and break it up into 4-length chunks via chunked
. This turns our List<String>
into a List<List<String>>
where each inner List<String>
is 4 elements long. Pretty handy! It also takes an argument which allows us to map the List<String>
to something else; ClawMachine
in our case.
class Day13(input: List<String>) {
private val clawMachines: List<ClawMachine> =
input.chunked(4) { ClawMachine.of(it) }
}
⭐ Day 13, Part 1
The puzzle text can be found here.
Most of the setup is done, but we still need a way to calculate the number of button presses it will take to win the prize in each ClawMachine
. To calculate this we’ll add a pressButtons
function to ClawMachine
that returns a Long
representing the number of tokens to spend (which may be zero in the case where the prize cannot be won).
Once again, the pressButtons
function wouldn’t be possible without this Reddit thread by /u/ThunderChaser
.
// In ClawMachine
fun pressButtons(): Long {
val det = aX * bY - aY * bX
val a = (prizeX * bY - prizeY * bX) / det
val b = (aX * prizeY - aY * prizeX) / det
return if (aX * a + bX * b == prizeX && aY * a + bY * b == prizeY) {
a * 3 + b
} else 0
}
Go see ThunderChaser’s thread for a very good explanation of what is happening here. I won’t do it justice, I just reinterpreted it in Kotlin.
Now that we have a proper function to determine the number of tokens we need to spend per ClawMachine
, we can go through them one at a time and get the sumOf
all the pressButtons
calls. This gives us the answer to part 1.
// In Day13
fun solvePart1(): Long =
clawMachines.sumOf { it.pressButtons() }
Star earned (with help)! Onward!
⭐ Day 13, Part 2
The puzzle text can be found here.
Our solution to part 1 will work with one minor change, relocating the prize
. Let’s add a movePrize
function to the ClawMachine
, which takes a factor
of how far to move the prize and returns a new instance of ClawMachine
.
// In ClawMachine
fun movePrize(factor: Long): ClawMachine =
copy(
prizeX = prizeX + factor,
prizeY = prizeY + factor
)
In this method you can see one of the best features of data classes - copy constructors. In this case we create a full copy of ClawMachine
except with new prizeX
and prizeY
variables.
Mapping each of our ClawMachine
instances to new instances with the prize moved and summing the results of the button presses gives us the answer to part 2.
// In Day13
fun solvePart2(): Long =
clawMachines
.map { it.movePrize(10000000000000) }
.sumOf { it.pressButtons() }
Star earned! See you tomorrow!
PS - If you’ve made it this far, you have 26 stars - just over half way!
Further Reading
- Index of All Solutions - All posts and solutions for 2024, in Kotlin.
- My Github repo - Solutions and tests for each day.
- Solution - Full code for day 13
- Advent of Code - Come join in and do these challenges yourself!