Skip to Content

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'

Posted on

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

  1. Index of All Solutions - All posts and solutions for 2024, in Kotlin.
  2. My Github repo - Solutions and tests for each day.
  3. Solution - Full code for day 13
  4. Advent of Code - Come join in and do these challenges yourself!