Advent of Code 2020 - Day 12

By Eric Burden | December 13, 2020

In the spirit of the holidays (and programming), I’ll be posting my solutions to the Advent of Code 2020 puzzles here, at least one day after they’re posted (no spoilers!). I’ll be implementing the solutions in R because, well, that’s what I like! What I won’t be doing is posting any of the actual answers, just the reasoning behind them.

Also, as a general convention, whenever the puzzle has downloadable input, I’m saving it in a file named input.txt.

Day 12 - Rain Risk

Find the problem description HERE.

Part One - Do a Barrel Roll

For part one, we’re directing our ferry to take evasive maneuvers by reading in a set of text instructions and ‘moving’ the ship as instructed. The solution is the total linear distance we’ve moved the ship from the starting point (the Manhattan distance).

test_input <- c("F10", "N3", "F7", "R90", "F11")
real_input <- readLines('input.txt')

# Helper function, given a list of strings in the format 'CN' where 'C' is an
# uppercase character (one of 'N', 'S', 'E', 'W', 'L', 'R', 'F') and 'N' is 
# a numeric character or characters. Returns a list where each element is a
# named list of the format list(list(dir = 'C', mag = 'N'), ...)
parse_input <- function(input) {
  navs <- strsplit(input, '(?<=[A-Z])(?=\\d)', perl = T)
  lapply(navs, function(x) { list(dir = x[1], mag = as.numeric(x[2])) })
}

# Helper function, given a `dir` of 'L' or 'R', a magnitude `mag`, and the
# ship's current position in the structure we have defined. Returns the 
# ship's new heading as 'N', 'E', 'S', or 'W' based on the indicated
# rotation of the ship
new_heading <- function(dir, mag, ship_pos) {
  dirs <- c('N', 'E', 'S', 'W')
  rotation <- if (dir == 'L') { -1 } else { 1 }  # Direction of rotation
  di <- rotation * ((mag/90) %% 4)               # Times to move 90 degrees
  
  # Changes the heading, wrapping around the ends of the `dirs` vector as needed
  heading <- which(dirs == ship_pos[['H']]) + di
  if (heading < 1) { heading <- heading + 4 }
  if (heading > 4) { heading <- heading - 4 }
  dirs[[heading]]
}

# Main function, moves the ship. Given a `dir` and `mag` from a navigation 
# instruction and the ship's current position as `ship_pos`, returns the ship's
# new position after applying the instruction
move_ship <- function(dir, mag, ship_pos) {
  # If `dir` is one of 'N', 'S', 'E', or 'W', change the ship's position that
  # direction
  if (dir == 'N') { ship_pos$Y <- ship_pos$Y + mag}
  if (dir == 'S') { ship_pos$Y <- ship_pos$Y - mag}
  if (dir == 'E') { ship_pos$X <- ship_pos$X + mag}
  if (dir == 'W') { ship_pos$X <- ship_pos$X - mag}
  
  # If the `dir` is 'L' or 'R', rotate the ship
  if (dir %in% c('L', 'R')) { ship_pos[['H']] <- new_heading(dir, mag, ship_pos) }
  
  # Move the ship 'forward' in it's current direction
  if (dir == 'F') { ship_pos <- move_ship(ship_pos$H, mag, ship_pos) }
  
  ship_pos
}

# A handy structure to store the ship's position
ship_pos <- list(X = 0, Y = 0, H = 'E')  
navigation <- parse_input(real_input)  # Parse input
for (nav in navigation) { ship_pos <- move_ship(nav$dir, nav$mag, ship_pos) }
answer1 <- abs(ship_pos$X) + abs(ship_pos$Y)

As far as puzzles go, this one is pretty straightforward. Parse the directions, follow the directions. The most complex bit is rotating directions, but since the rotational degrees are always in increments of ‘90’, it’s not too bad. Keeping track of the ship’s location in terms of ‘X’ and ‘Y’ really helps with calculating the final result.

Part Two - Read the Directions

So, it turns out that, much like an overconfident father on Christmas morning, we decided to ignore the instructions, forge ahead, realize we did it wrong, then slink back to the instructions humbled and just a bit wiser.

# Helper function, given a number of times to move the ship `n`, the ship's
# position `ship`, and a waypoint's position `waypoint`, 'move' the ship to 
# the waypoint `n` times.
ship_to_waypoint <- function(n, ship, waypoint) {
  for (i in seq(n)) {
    ship$X <- ship$X + waypoint$X
    ship$Y <- ship$Y + waypoint$Y
  }
  ship
}

# Helper function, given a direction to rotate `dir`, an amount to rotate 
# `mag`, and a waypoint's position `waypoint`, rotate the waypoint's position
# around the ship.
rotate <- function(dir, mag, waypoint) {
  rotation_dir <- if (dir == 'L') { -1 } else { 1 }
  rotation_mag <- mag %% 360  # Wraps around if mag > 360
  
  # If `mag` is 0 or a multiple of 360, just return the waypoint
  if (rotation_mag == 0) { return(waypoint) }
  
  # Otherwise, modify the waypoint's X and Y coordinates to simulate rotation
  if (rotation_mag == 90) { 
    new_X <- waypoint$Y * rotation_dir
    new_Y <- waypoint$X * -rotation_dir
  }
  if (rotation_mag == 180) { 
    new_X <- -waypoint$X
    new_Y <- -waypoint$Y
  }
  if (rotation_mag == 270) { 
    new_X <- waypoint$Y * -rotation_dir
    new_Y <- waypoint$X * rotation_dir
  }
  
  # Set the waypoint's X and Y coordinates and return the waypoint
  waypoint$X <- new_X
  waypoint$Y <- new_Y
  waypoint
}

# Modified main function, moves the ship. Given a `dir` and `mag` from a 
# navigation instruction and the set of objects we're tracking `system`, 
# returns an updated `system` that reflects movements in the ship and 
# waypoint
move <- function(dir, mag, system) {
  within(system, {
    if (dir == 'N') { waypoint$Y <- waypoint$Y + mag}
    if (dir == 'S') { waypoint$Y <- waypoint$Y - mag}
    if (dir == 'E') { waypoint$X <- waypoint$X + mag}
    if (dir == 'W') { waypoint$X <- waypoint$X - mag}
    if (dir == 'F') { ship <- ship_to_waypoint(mag, ship, waypoint)}
    if (dir %in% c('L', 'R')) { waypoint <- rotate(dir, mag, waypoint)}
  })
}

# Keep track of the waypoint and ship locations in X and Y coordinates as
# a group, `system`
system <- list(waypoint = list(X = 10, Y = 1), ship = list(X = 0, Y = 0))
navigation <- parse_input(real_input)
for (nav in navigation) { system <- move(nav$dir, nav$mag, system) }
answer2 <- abs(system$ship$X) + abs(system$ship$Y)

Now we’re keeping track of a waypoint’s position in addition to the position of the ship, and we’ve modified the rotate() and move() functions to accommodate the changes, as well as a ship_to_waypoint() function that increment’s the ship’s ‘X’ and ‘Y’ coordinates by the difference between the ship’s position and the waypoint’s position.

Wrap-Up

This was a relatively relaxing day. A straightforward strategy of creating a function for each individual operation then composing them together helped with creating a pretty readable solution. Probably the biggest breakthrough was realizing that ‘rotating’ the waypoint just meant transposing the ‘X’ and ‘Y’ coordinates.

If you found a different solution (or spotted a mistake in one of mine), please drop me a line!

comments powered by Disqus