# Advent of Code 2020 - Day 12, in Kotlin - Rain Risk

Kotlin solutions to parts 1 and 2 of Advent of Code 2020, Day 12: 'Rain Risk'

Posted on

Today we’ll define some reusable classes for direction and points, which will help us today and hopefully in future puzzles as well.

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

Problem Input

We will take in the puzzle input as a `List<String>` and not alter it further. While we could convert our Strings into some kind of data class, we only have to go through the input once, and I’m not sure it is worth the effort.

Having said that, it’s time we commit to a `Point2D` implementation, and like previous years, a `Direction` sealed class structure. These strike me as generally useful to have.

First, let’s talk about our `Direction` sealed class:

``````// In Movement.kt

sealed class Direction {
abstract val turnLeft: Direction
abstract val turnRight: Direction
abstract val offset: Point2D

operator fun invoke(dir: String): Direction =
when (dir) {
"N" -> North
"S" -> South
"E" -> East
"W" -> West
else -> throw IllegalArgumentException("No such direction \$dir")
}

object North : Direction() {
override val turnLeft = West
override val turnRight = East
override val offset = Point2D(-1, 0)
}

object South : Direction() {
override val turnLeft = East
override val turnRight = West
override val offset = Point2D(1, 0)
}

object West : Direction() {
override val turnLeft = South
override val turnRight = North
override val offset = Point2D(0, -1)
}

object East : Direction() {
override val turnLeft = North
override val turnRight = South
override val offset = Point2D(0, 1)
}
}
``````

One might ask why we use a sealed class with sealed objects here, rather than an enumeration. The problem with using an enumeration is that we will not be able to reference all of the enumerated values properly in `turnLeft` and `turnRight` if they haven’t been defined yet. So we’ve resorted to this sealed class.

The first thing to notice is the `invoke` operator. This lets us call `Direction("W")` to create a new `Direction`, instead of having to define a function in the companion and calling `Direction.of("W")` or something similar.

Next, we have four directions and each implements the abstract `turnLeft`, `turnRight`, and `offset` values which are specified in the parent class.

Because we reference `Point2D` in direction, now is a good time to discuss that implementation:

``````// In Movement.kt

data class Point2D(val x: Int, val y: Int) {
operator fun plus(other: Point2D): Point2D =
Point2D(x + other.x, y + other.y)

operator fun times(by: Int): Point2D =
Point2D(x * by, y * by)

infix fun distanceTo(other: Point2D): Int =
(x - other.x).absoluteValue + (y - other.y).absoluteValue

companion object {
val ORIGIN = Point2D(0, 0)
}
}
``````

Our simple `Point2D` has an `x` and a `y` coordinate. This should make point-based logic simpler for future puzzles instead of having to resort to `Pair<Int,Int>` like we have been. We’ll define a `plus` operator so we can add two points together, and we’ll add a `times` operator so we can multiply one point by a fixed amount (which we do in the puzzle). I can eventually see a need to multiply one `Point2D` by another, but we don’t need it today and we can easily add it later.

We will define an `infix` function so we can calculate the distance between two `Point2D` objects, as described in the puzzle. An `infix` function allows us to leave off the dot and the parenthesis when calling it, making it look like an operator. For example `here distanceTo there`, assuming `here` and `there` are `Point2D` objects.

Finally, we’ll declare an `ORIGIN` as I can see us using that a lot. I almost made `0,0` the default `x,y` values for this point but couldn’t come up with a great reason for doing that.

#### ⭐ Day 12, Part 1

The puzzle text can be found here.

Now that we have a concept of `Direction` and place (via `Point2D`), we should have an easier time with future puzzles, starting with today!

Let’s define another data class for our `Ship`:

``````// In Day12

data class Ship(val at: Point2D = Point2D.ORIGIN, val facing: Direction = Direction.East) {

fun forward(amount: Int): Ship =
copy(at = at + (facing.offset * amount))

fun move(dir: Direction, amount: Int): Ship =
copy(at = at + (dir.offset * amount))

fun turnLeft(times: Int): Ship =
(0 until times).fold(this) { carry, _ ->
carry.copy(facing = carry.facing.turnLeft)
}

fun turnRight(times: Int): Ship =
(0 until times).fold(this) { carry, _ ->
carry.copy(facing = carry.facing.turnRight)
}
}
``````

As you can see, our `Ship` is made up of its location (`at`) and the direction it is `facing`. Unfortunately we don’t get to put fun names or passengers in this ship. Maybe another day.

The `Ship` has a few functions and each one of them returns a new instance of `Ship`, making this an immutable data structure. The `forward` function generates a new `Ship` in a new location, taking advantage of both the `plus` and `times` operators we defined in `Point2D`! Move works similarly, but uses the specified direction rather than the ship’s facing.

The `turnLeft` and `turnRight` functions use a `fold` to generate new `Ship` objects that are turned in the direction we specify.

Now that we have all that, we can write a solution to part 1:

``````// In Day12

fun solvePart1(): Int =
input.fold(Ship(Point2D.ORIGIN, Direction.East)) { ship, instruction ->
val command = instruction.first()
val amount = instruction.substring(1).toInt()
when (command) {
'N' -> ship.move(Direction.North, amount)
'S' -> ship.move(Direction.South, amount)
'E' -> ship.move(Direction.East, amount)
'W' -> ship.move(Direction.West, amount)
'F' -> ship.forward(amount)
'L' -> ship.turnLeft(amount / 90)
'R' -> ship.turnRight(amount / 90)
else -> throw IllegalArgumentException("Unknown instruction: \$instruction")
}
}.at distanceTo Point2D.ORIGIN
``````

Because every time we call a function on our `Ship`, we can `fold` over the input, generating a new `Ship` each time through. We’ll parse out the `command` (the first character of the input) and the `amount` (anything after the command), and use a `when` expression to interpret the `command`s. I won’t go into detail, mostly this should be self explanatory as we’re delegating the work to our `Ship` class, and in turn to either `Direction` or `Point2D`.

We can use our infix `distanceTo` function to figure out the distance from the origin point to the final `Ship` that our `fold` generates, giving us the answer to part 1. Star earned!

#### Intermission

If you’ve earned every star up until this point, congratulations! We are half way through with Advent of Code 2020!

#### ⭐ Day 12, Part 2

The puzzle text can be found here.

While most of the code we’ve written is useful for Part 2, we’ll need to make a couple of changes. First, we need to add the concept of rotation to our `Point2D` class, by adding some new functions:

``````// In Point2D

fun rotateLeft(): Point2D =
Point2D(x = y * -1, y = x)

fun rotateRight(): Point2D =
Point2D(x = y, y = x * -1)
``````

These functions too me a while to write because I was having a hard time understanding the instructions as they were written. But after I wrote down and closely followed what we were being told, it eventually made sense. Essentially, we flip the x and y axis and negate one of them, depending on which direction we are turning.

And because we have a `Ship`, we might as well have a `Waypoint` in today’s puzzle as well:

``````// In Day12

data class Waypoint(val at: Point2D = Point2D(-1, 10)) {
fun move(dir: Direction, amount: Int): Waypoint =
Waypoint(at + (dir.offset * amount))

fun turnLeft(amount: Int): Waypoint =
(0 until amount).fold(this) { carry, _ ->
Waypoint(carry.at.rotateLeft())
}

fun turnRight(amount: Int): Waypoint =
(0 until amount).fold(this) { carry, _ ->
Waypoint(carry.at.rotateRight())
}
}
``````

We only have to have three functions for the `Waypoint` - how to `move` and how to turn. The `move` calculates a new location by multiplying the directional offset, and the rotations happen similar to how we implemented them earlier, depending on our `Point2D` functions.

Now we can solve part 2:

``````// In Day12

fun solvePart2(): Int {
var waypoint = Waypoint()
var ship = Ship()
input.forEach { instruction ->
val command = instruction.first()
val amount = instruction.substring(1).toInt()
when (command) {
'N' -> waypoint = waypoint.move(Direction.North, amount)
'S' -> waypoint = waypoint.move(Direction.South, amount)
'E' -> waypoint = waypoint.move(Direction.East, amount)
'W' -> waypoint = waypoint.move(Direction.West, amount)
'F' -> ship = ship.copy(at = ship.at + (waypoint.at * amount))
'L' -> waypoint = waypoint.turnLeft(amount / 90)
'R' -> waypoint = waypoint.turnRight(amount / 90)
else -> throw IllegalArgumentException("Unknown instruction: \$instruction")
}
}
return Point2D.ORIGIN distanceTo ship.at
}
``````

This looks a lot like the solution to part 1, except we aren’t doing it as a single expression, so I won’t go over it in great details. Because some instructions have us move the `Ship` and others have us move the `Waypoint`, I felt it was cleaner to make them both `vars` and set new values when we moved them, rather than trying to coordinate this all in a single expression. This version works similarly to part 1, but most of the instructions involved the `waypoint` instead of the `ship`.

Running this solves part 2.

See you tomorrow!