Advent of Code 2020: Day 12
Day 12 is about moving a ship across a coordinate plane using directions and a way point that moves and rotates around the ship. There’s a bit of geometry, and I made a really dumb mistake that took me a long time to figure out.
Challenge
The puzzle can be found here. The puzzle input will look like:
F10
N3
F7
R90
F11
Each line has one of seven actions: move (N)orth, (S)outh, (E)ast, (W)est or (F)orward, or turn (L)eft or (Right) in degrees, and then an integer number of spaces to move or degrees to turn.
For part one, the ship starts facing east (90 degrees), and moving does not change the direction of the ship. I need to find the manhattan distance between the ship’s starting and ending positions.
In part two, the directional move commands are actually for a wayfinding beacon that starts at 1 north and 10 east. The directional commands move the beacon, and the turn commands rotate the beacon around the ship. Only the forward command moves the ship.
Solution
Part 1
I’m sure there are prettier ways to do it, but I just loop over the instructions, moving the ship or changing the direction each time. A quick glance shows that all the turn instructions are in increments of 90 degrees, but the math isn’t too complicated to handle any values
#!/usr/bin/env python3
import math
import sys
with open(sys.argv[1], "r") as f:
insts = f.readlines()
direction = 90
x, y = 0, 0
for i in insts:
d, p = i[0], int(i[1:])
if d == "N":
y += p
elif d == "S":
y -= p
elif d == "E":
x += p
elif d == "W":
x -= p
elif d == "L":
direction = (direction - p) % 360
elif d == "R":
direction = (direction + p) % 360
elif d == "F":
y += round(math.cos(math.radians(direction)) * p)
x += round(math.sin(math.radians(direction)) * p)
print(f"Part 1: {abs(x)+abs(y)}")
This gets the correct result.
Part 2
I think there’s a way to conceptualize part two such that I can turn part one into a solve function and get either back. But for sake of speed and clarity, I just copied all of part one and started changing bits. I’ll add a position for the waypoint, and handle rotations using geometric calculations with sin
and cos
. To turn right, I’ll just set the angle to 360 minus the angle and then turn left that amount.
sx, sy = 0, 0
wx, wy = 10, 1
for i in insts:
d, p = i[0], int(i[1:])
if d == "N":
wy += p
elif d == "S":
wy -= p
elif d == "E":
wx += p
elif d == "W":
wx -= p
elif d == "L":
wx_orig = wx
wx = round((math.cos(math.radians(p)) * wx) - (math.sin(math.radians(p)) * wy))
wy = round(
(math.sin(math.radians(p)) * wx_orig) + (math.cos(math.radians(p)) * wy)
)
elif d == "R":
p = 360 - p
wx_orig = wx
wx = round((math.cos(math.radians(p)) * wx) - (math.sin(math.radians(p)) * wy))
wy = round(
(math.sin(math.radians(p)) * wx_orig) + (math.cos(math.radians(p)) * wy)
)
elif d == "F":
sy += wy * p
sx += wx * p
print(f"Part 2: {abs(sx) + abs(sy)}")
What got me stuck for a long while was that I was forgetting to keep the original wx
so when it changed in the first calculation, the calculation of wy
was wrong. I added a line to print the status at the end of each loop: print(f'{i.strip():5s} {sx} {sy} {wx} {wy}')
and watched how it was breaking, and eventually figured that out.
Final Code
Script runs instantly:
$ time python3 day12.py 12-puzzle.txt
Part 1: 759
Part 2: 45763
real 0m0.021s
user 0m0.014s
sys 0m0.007s
#!/usr/bin/env python3
import math
import sys
with open(sys.argv[1], "r") as f:
insts = f.readlines()
direction = 90
x, y = 0, 0
for i in insts:
d, p = i[0], int(i[1:])
if d == "N":
y += p
elif d == "S":
y -= p
elif d == "E":
x += p
elif d == "W":
x -= p
elif d == "L":
direction = (direction - p) % 360
elif d == "R":
direction = (direction + p) % 360
elif d == "F":
y += round(math.cos(math.radians(direction)) * p)
x += round(math.sin(math.radians(direction)) * p)
print(f"Part 1: {abs(x)+abs(y)}")
sx, sy = 0, 0
wx, wy = 10, 1
for i in insts:
d, p = i[0], int(i[1:])
if d == "N":
wy += p
elif d == "S":
wy -= p
elif d == "E":
wx += p
elif d == "W":
wx -= p
elif d == "L":
wx_orig = wx
wx = round((math.cos(math.radians(p)) * wx) - (math.sin(math.radians(p)) * wy))
wy = round(
(math.sin(math.radians(p)) * wx_orig) + (math.cos(math.radians(p)) * wy)
)
elif d == "R":
p = 360 - p
wx_orig = wx
wx = round((math.cos(math.radians(p)) * wx) - (math.sin(math.radians(p)) * wy))
wy = round(
(math.sin(math.radians(p)) * wx_orig) + (math.cos(math.radians(p)) * wy)
)
elif d == "F":
sy += wy * p
sx += wx * p
# print(f'{i.strip():5s} {sx} {sy} {wx} {wy}')
print(f"Part 2: {abs(sx) + abs(sy)}")