For Day 12, the problem revolves around navigating a ship using a set of navigational instructions. The instructions look like this:

F10
N3
F7
R90
F11
F2
F2
F2
R180
F10

The instructions move the ship in a cardinal direction N, S, E, W, Rotate the ship, or move Forward, using the current rotation. This problem is solved easily with a simple navigation routine:

def navigate(steps, visualize):
    states = []

    ship = Position(0, 0)
    heading = 0

    if visualize:
        states.append(ShipState(ship, heading, None))

    for action, value in steps:
        if action == "N":
            ship.y += value
        elif action == "S":
            ship.y -= value
        elif action == "E":
            ship.x += value
        elif action == "W":
            ship.x -= value
        elif action == "L":
            heading += value
            heading = heading % 360
        elif action == "R":
            heading -= value
            heading = heading % 360
        elif action == "F":
            if heading == 0:
                ship.x += value
            elif heading == 90:
                ship.y += value
            elif heading == 180:
                ship.x -= value
            elif heading == 270:
                ship.y -= value

        if visualize:
            states.append(ShipState(ship, heading, None))

    return ship, states

The “F” action block would be perfect for a switch statement, if only Python had them.

The answer to the puzzle is the Manhattan distance from the starting point, after all steps have been executed.

A small test execution:

Part 2 was essentially the same, but instead of rotating the ship, the target waypoint is moved, and the F directive move the ship to the waypoint. This can be done with an almost identical routine:

    waypoint = Position(10, 1)
    ship = Position(0, 0)
    for action, value in steps:
        if action == "N":
            waypoint.y += value
        elif action == "S":
            waypoint.y -= value
        elif action == "E":
            waypoint.x += value
        elif action == "W":
            waypoint.x -= value
        elif action == "L":
            for _ in range(value // 90):
                waypoint.x, waypoint.y = -waypoint.y, waypoint.x
        elif action == "R":
            for _ in range(value // 90):
                waypoint.x, waypoint.y = waypoint.y, -waypoint.x
        elif action == "F":
            ship.x += waypoint.x * value
            ship.y += waypoint.y * value