Advent of Code, Day 12

Day 12 of Advent of Code is here! We’re half way through! Check out Day 1 for what this is all about. See all of my solutions here. If you’re interested, check out what my colleagues at Slalom are doing with Advent of Code

Do your best!

I highly encourage you to get through the day’s challenge on your own. Certainly refer to this and other’s examples to help you get through. I would love to hear how you did! Did you find a better approach than I did? Do tell!

Running in NodeJS

I’ve moved from dev console to using NodeJS for my challenges. This will provide us with more capabilities to identify issues in our code much faster. Learn more about the move here. As a result, my input and scripts can be found on github.

Day 12: Rain Risk

Part I

“The navigation instructions (your puzzle input) consists of a sequence of single-character actions paired with integer input values. After staring at them for a few minutes, you work out what they probably mean. The ship starts by facing east. Only the L and R actions change the direction the ship is facing. (That is, if the ship is facing east and the next instruction is N10, the ship would move north 10 units, but would still move east if the following action were F.)

Figure out where the navigation instructions lead. What is the Manhattan distance between that location and the ship’s starting position?ref

Here’s a sampling of the instructions

Before we start, let’s get pull this data in

const fs = require('fs');
const instructions = fs.readFileSync('./input.txt', 'utf-8').trim().split('\n')

Nothing fancy today, just getting all rows into an array.

It may help to remember what a compass looks like. I do a fair amount of hiking and exploring, I’m looking at these all the time. However, I know many people may not know, especially since Google Maps does it all for us. Here’s a quick refresher:

Note the + and - in each direction, we’ll touch upon those shortly. I set up some variables to track the location of our boat:

const directions = ['E', 'S', 'W', 'N']
let dirIndex = 0
let x = 0
let y = 0

The boat lives at one location, a pair of coordinates designed by x and y. Could call these longitude and latitude, but x and y are simpler.

  • x runs along the west to east, and if heading east, we add to x, if we go west, we subtract from x
  • y runs along the south to north, if heading north, we add to y, if we head south, we subtract from y

The boat starts in the east direction, so that’ll be the initial direction.

Once we can got the concept in our head, this will be much easier. Here’s my code:

const moveShip = (navigate) => {
  const direction = directions[dirIndex]
  
  const coordinates = navigate.match(/([A-Z]{1})([0-9]*)/i)
  const instruction = coordinates[1]
  const unit = Number(coordinates[2])

  switch (instruction) {
    case 'F':
      const multiplier = direction === 'E' || direction === 'N' ? 1 : -1
      const isX = direction === 'E' || direction === 'W'
      const isY = direction === 'N' || direction === 'S'
      if(isX) {
        x = x + (unit * multiplier)
      }
      if (isY) {
        y = y + (unit * multiplier)
      }
      break;
    case 'N':
      y = y + unit
      break;
    case 'S':
      y = y - unit
      break;
    case 'E':
      x = x + unit
      break;
    case 'W':
      x = x - unit
      break;
    case 'R':
      const rotateRight = unit / 90
      dirIndex += rotateRight
      break;
    case 'L':
      const rotateLeft = unit / 90
      dirIndex -= rotateLeft
      break;
  }
  if(dirIndex >= directions.length) dirIndex = dirIndex - directions.length
  if(dirIndex < 0) dirIndex = directions.length - (dirIndex * -1)
  console.log(navigate, 'direction', dirIndex, directions[dirIndex], 'x', x, 'y', y)
}

This method moveShip moves our ship around:

  • We set up some variables
    • direction gets us the text value, N, E, S, W, of the direction the ship is currently heading
    • coordinates, parses the navigate to get the instruction and unit to apply
      • Yay regex is back!
      • ([A-Z]{1}) gets the first letter, which is in the instruction
      • ([0-9]*) gets the unit number after the letter
  • We then use a switch to do the instructions
    • F is to move forward, so depending on the direction, we have to add or subtract the unit from our x and y coordinates. If we’re going N or E, then we’re going positive (the + in the compass image above). Subsequently, S or W are going negative.
    • N and S add or subtract the unit from the y variable
    • E and W add or subtract the unit from the x variable
    • R and L rotate the ship, change the direction. The unit determines which direction and thankfully are in 90º increments. Going Right is clockwise, let’s go positive. Going Left is counterclockwise, going negative.
    • If we exceed our direction values, the directions array, whether negative or positive, we circle back on the array. So the if we’re at N now, and R90, that should be E, so we go from index 3 to index 0.
  • That console.log is really helpful to see the calculations happen and spot check the math.

Finally, we loop through all of the instructions from the file and call the moveShip function above:

instructions.forEach(nav => {
  moveShip(nav)
})

if(x < 0) x = x * -1
if(y < 0) y = y * -1

console.log(x, '+', y, 'sum is', x + y)

If we have any negative numbers, we want them positive so we can add them together and get a positive number. Remember a negative y isn’t actually negative, it’s just South.

Part II

“Before you can give the destination to the captain, you realize that the actual action meanings were printed on the back of the instructions the whole time. Almost all of the actions indicate how to move a waypoint which is relative to the ship’s position. The waypoint starts 10 units east and 1 unit north relative to the ship.What is the Manhattan distance between that location and the ship’s starting position?

This was hard to wrap my head around, but once I did it was easy. I started with the compass and had a mental model going well, but then we rotated the way point and it all went out. I started drawing out what is going on here and came up with the following:

Photo by Willian Justen de Vasconcellos on Unsplash, advanced calculation overlays be me ;)

Let me attempt to explain this a little.

The waypoint is 10 units East, and 1 unit North. The waypoints current location is east-west, x = 10 and north-south, y = 1. Illustrated above to the right side of the boat.

The boat starts at X = 0 and Y = 0, heading East, which is right, and a rotation of 0º. Moving forward 10, F10, units in this direction, I will add to the boat’s X coordinate 10×10 (the waypoint’s relative X position is 10, and I’m moving 10 units), and add to the boat’s Y coordinate 1×10 (the waypoint’s relative Y position is 1 and I’m moving 10 units). The boats new location is 100,10.

With Part 2, the E, S, W, N commands move the waypoint around, not the boat. N2 moves my waypoint up to a Y position of 3, now the waypoint is 10,3.

Rotating is where things get really sticky and where the above image helped a lot. Ignore everything about moving above for a moment. The R and L instructions now rotate the waypoint around the boat. So R90, and the waypoint spins 90º to the right, it’s now facing south. 180º now faces West and so on. That’s clear in the above image.

When we R90, the waypoints east-west, x and north-south, y are 3, -10. See the image above, I’m pointing down, so X is now negative, for south, and Y now points to east. This is really confusing and I didn’t attempt to swap numbers around on each rotation. Instead, I stored the rotation value alongside the waypoint coordinates. When the rotation is 90º, I know my east west line, x, is actually going north and south now, so moving in that direction I have to add to X to keep going south.

After R90, if we get a F20, we subtract 10×20 from our boat’s north-south number (10 being the waypoint’s east-west value, the X facing down) . Then add 1×20 to our boat’s east-west number (1 being the waypoint’s north-south value, the Y going right). If this hurts to think through, there’s a chance I’m explaining it wrong, do let me know if this is confusing.

Let’s check out the code, this works and I think explains it MUCH better

const fs = require('fs');
const instructions = fs.readFileSync('./input.txt', 'utf-8').trim().split('\n')

let eastWest= 0
let northSouth= 0
let waypoint = {
  xValue: 10,
  yValue: 1,
  rotation: 0
}
/* 
ROTATION
  0 - x heads east, y heads north
  90 - x heads south, y heads east
  180 - x heads west, y heads south
  270 - x heads north, y heads west
*/

const moveShip = (navigate) => {
  const coordinates = navigate.match(/([A-Z]{1})([0-9]*)/i)
  const instruction = coordinates[1]
  const unit = Number(coordinates[2])

  switch (instruction) {
    case 'F':
      switch(waypoint.rotation) {
        case 0:
          eastWest += waypoint.xValue * unit
          northSouth += waypoint.yValue * unit
          break;
        case 90:
          eastWest += waypoint.yValue * unit
          northSouth += waypoint.xValue * unit * -1
          break;
        case 180:
          eastWest += waypoint.xValue * unit * -1
          northSouth += waypoint.yValue * unit * -1
          break;
        case 270:
          eastWest += waypoint.yValue * unit * -1
          northSouth += waypoint.xValue * unit
          break;
      }
      break;
    case 'N':
      switch(waypoint.rotation) {
        case 0:
          waypoint.yValue += unit
          break;
        case 90:
          waypoint.xValue -= unit
          break;
        case 180:
          waypoint.yValue -= unit
          break;
        case 270:
          waypoint.xValue += unit
          break;
      }
      break;
    case 'S':
      switch(waypoint.rotation) { // same as N but *-1
        case 0:
          waypoint.yValue -= unit
          break;
        case 90:
          waypoint.xValue += unit
          break;
        case 180:
          waypoint.yValue += unit
          break;
        case 270:
          waypoint.xValue -= unit
          break;
      }
      break;
    case 'E':
      switch(waypoint.rotation) {
        case 0:
          waypoint.xValue += unit
          break;
        case 90:
          waypoint.yValue += unit
          break;
        case 180:
          waypoint.xValue -= unit
          break;
        case 270:
          waypoint.yValue -= unit
          break;
      }
      break;
    case 'W':
      switch(waypoint.rotation) { // -1 * East
        case 0:
          waypoint.xValue -= unit
          break;
        case 90:
          waypoint.yValue -= unit
          break;
        case 180:
          waypoint.xValue += unit
          break;
        case 270:
          waypoint.yValue += unit
          break;
      }
      break;
    case 'R':
      waypoint.rotation += unit
      if(waypoint.rotation > 270) waypoint.rotation -= 360
      break;
    case 'L':
      waypoint.rotation -= unit
      if(waypoint.rotation < 0) waypoint.rotation += 360
      break;
  }
  console.log(navigate, 'direction', waypoint.xValue, 'by', waypoint.yValue, 'rotation', waypoint.rotation, 'eastWest', eastWest, 'northSouth', northSouth)
}

instructions.forEach(nav => {
  moveShip(nav)
})

if(eastWest< 0) eastWest = eastWest* -1
if(northSouth< 0) northSouth = northSouth* -1

console.log(eastWest, '+', northSouth, 'sum is',eastWest + northSouth)

After getting the instructions, I created the eastWest and northSouth variables to manage where the boat it. I moved from X and Y because seeing the waypoint’s X and Y was confusing me too much. I made a single object to manage the waypoint. This has the x and y for the waypoint and what rotation it’s on.

Right in the moveShip method, the first instruction in the switch is the Forward command. I think this will help explain what I was trying to explain above. Depending on the rotation, we either add or subtract from the boat’s east-west and north-south positioning.

The next few cases are to move the waypoint. They all have to take into consideration the current rotation of the waypoint, to know what direction to move in.

The final two rotate it, which is easier this time than in Part 1.

I again logged it out, which helps a lot in testing and troubleshooting.

Finally, we have the same small clean up lines from Part 1, and the answer.

How did it go for you?

How did you find the answer on your own? How did you do it? Anything stump you? I’d love to hear how you did! Please comment below! I have also loaded up just the challenge and code from this and my other days on github, you can follow along there too: pretty Git pages or the code in the repo.

Series Navigation<< Advent of Code, Day 13Advent of Code, Day 11 >>

One thought on “Advent of Code, Day 12

Add yours

Leave a Reply

Up ↑

%d bloggers like this: