Skip to Content

Advent of Code 2019 - Day 13, in Kotlin

Kotlin solutions to parts 1 and 2 of Advent of Code 2019, Day 13: 'Care Package'

Posted on

Are you ready, player one? Today we’ll write a powerful new AI (ok, a while loop and an if statement) to play video games!

If you’d rather just view code, the GitHub Repository is here .

Problem Input

Since this is another IntCode input, we’ll parse it as we have in the past:

@ExperimentalCoroutinesApi
class Day13(input: String) {

    private val program: MutableMap<Long, Long> = input
        .split(",")
        .withIndex()
        .associateTo(mutableMapOf()) { it.index.toLong() to it.value.toLong() }
}

We will also annotate our class with @ExperimentalCoroutinesApi here, because we use an opt-in property that’s still experimental.

Finally, we’ll define some constants:

// In Day13

companion object {
    private const val block = 2
    private const val paddle = 3
    private const val ball = 4
    private const val freePlay = 2L
}

Don’t worry about freePlay, we use that in Part 2.

Day 13, Part 1

The puzzle text can be found here.

Thankfully we have a fully working IntCodeComputerMk2 so we can start right in on our solution. Let’s go over our strategy first:

  1. Create our computer.
  2. Listen for output and record the number of blocks we see.
  3. Return the number of blocks.

Let’s get started!

fun solvePart1(): Int = runBlocking {
    var blocks = 0
    val computer = IntCodeComputerMk2(program = program, output = Channel(Channel.UNLIMITED))
    computer.runProgram()

    while (!computer.output.isClosedForReceive) {
        computer.output.receive()
        computer.output.receive()
        if(computer.output.receive().toInt() == block) {
            blocks++
        }
    }
    blocks
}

Because we aren’t feeding our computer any input, we can just run our program and then analyze the output. We don’t have to run our computer in a coroutine in the background. We’ll start by setting up our blocks counter which will keep track of how many blocks we’ve seen. Next, we create our computer, feeding it our program. One important detail here is the output channel. Because IntCodeComputerMk2 defaults to a CONFLATED channel, and we want more than one output, we have to manually create an UNLIMITED channel. Once the computer is created we can have it run the program, and wait for it to finish (because it has all it needs, there are no inputs).

So long as output is generating, well, output, we can read it in groups of three. The only value we care about is the item, and only when it’s a block. If it is a block, we update the blocks counter. Since the item is the third read of three, we can discard the first two, we won’t use them.

Return blocks when we run out of output and there’s our answer.

Star earned!

Day 13, Part 2

The puzzle text can be found here.

Let’s talk about strategy before we write too much code. I found out through experimentation that it’s enough to just follow the x axis of where the ball and paddle are. Every time the ball moves, we want to tell the joystick to move the paddle to meet the ball. We don’t need to do anything fancy with directions, angles, or timing. We just need to be where the ball is and eventually (within a few milliseconds) the game will end.

So that’s the strategy we’ll go with: follow the ball and move the paddle towards the ball every time the ball moves.

We start by creating our computer and hacking it to allow free play (value 2 at memory address 0). That part turns out to be somewhat important, I totally misread that and was sending 2 to the computer’s input and nothing worked. Reading the instructions carefully turns out to be somewhat important! We also need to remember to change the output channel from CONFLATED to UNLIMITED, like in Part 1. After setting up our computer, we launch a coroutine to run its program.

fun solvePart2(): Int = runBlocking {
    program[0] = freePlay // We r l33t H4x0rz now.
    val computer = IntCodeComputerMk2(program = program, output = Channel(Channel.UNLIMITED))

    launch {
        computer.runProgram()
    }

    var paddleX = 0
    var score = 0
    while (!computer.output.isClosedForReceive) {
        val x = computer.output.receive().toInt()
        computer.output.receive()
        val item = computer.output.receive().toInt()
        when {
            x == -1 -> score = item
            item == paddle -> paddleX = x
            item == ball -> {
                computer.input.send((x - paddleX).sign.toLong())
            }
        }
    }
    score
}
Thanks to Evan R. in the #advent-of-code room in the Kotlin Slack who pointed out to me that runBlocking is a coroutine scope and I didn’t need to start the read loop in an async block!

We’ll read from the computer’s output channel until it closes. Since we don’t really care about the y value of anything we can just read it and discard it. So in effect we end up reading x, discarding the next read, and reading item which is either a place on the board or the score, depending on the context. Whenever x is -1, we’re updating the score. When the item is the paddle, we store the x location of the paddle off. Whenever the item is the ball, we figure out which direction the paddle needs to move (using sign, which comes in handy two days in a row now!), and send an input to the computer to move the joystick.

Once the computer halts, and we have no more output, we can return our score.

Star earned!

We’re just now over half way through with Advent of Code 2019. Just as a reminder, I’m always up for feedback on my solutions or how I explain them. Use whatever contact method you like, they are all at the bottom of the page.

Further Reading

  1. Index of All Solutions - All posts and solutions for 2019, 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!