Advent of Code 2020: Day 12 (Ruby solution)

Today's puzzle are to simulate ship's routes. The navigation instructions (input file) consists of a sequence of single-character actions paired with integer input values

  • Action N means to move north by the given value.
  • Action S means to move south by the given value.
  • Action E means to move east by the given value.
  • Action W means to move west by the given value.
  • Action L means to turn left the given number of degrees.
  • Action R means to turn right the given number of degrees.
  • Action F means to move forward by the given value in the direction the ship is currently facing.

Here is my ship implementation form the first task.

require 'matrix'

WORLD_DIRECTIONS = {
    'N' => Vector[0, 1],
    'E' => Vector[1, 0],
    'S' => Vector[0, -1],
    'W' => Vector[-1, 0]
}.freeze

def number_of_crosses action, angle
  ((action == 'L' ? 1 : -1) * angle / 90) % 4
end

class Ship
  attr_reader :position

  def initialize
    @position = Vector[0, 0]
    @rotation = Vector[1, 0]
  end

  def navigate instructions
    instructions.each do |instruction|
      action = instruction[0]
      value = instruction.scan(/\d+/).first.to_i
      process_instruction action, value
    end
  end

  protected

  def process_instruction action, value
    if action.start_with? 'L', 'R'
      rotate action, value
    else
      move action, value
    end
  end

  def rotate action, angle
    number_of_crosses(action, angle).times { @rotation = @rotation.cross }
  end

  def move action, distance
    vector = WORLD_DIRECTIONS.merge('F' => @rotation)[action]
    @position += distance * vector
  end
end

In the second task, instruction are different:

  • Action N means to move the waypoint north by the given value.
  • Action S means to move the waypoint south by the given value.
  • Action E means to move the waypoint east by the given value.
  • Action W means to move the waypoint west by the given value.
  • Action L means to rotate the waypoint around the ship left (counter-clockwise) the given number of degrees.
  • Action R means to rotate the waypoint around the ship right (clockwise) the given number of degrees.
  • Action F means to move forward to the waypoint a number of times equal to the given value.

I provided a second ship implementation extending the previous one. Here is the rest of the code:

class ShipWithWaypoints < Ship
  def initialize
    @waypoint_position = Vector[10, 1]
    super
  end

  protected

  def process_instruction action, value
    if action.start_with? 'L', 'R'
      rotate_waypoint action, value
    elsif action.start_with? 'F'
      move value
    else
      move_waypoint action, value
    end
  end

  def rotate_waypoint action, angle
    number_of_crosses(action, angle).times { @waypoint_position = @waypoint_position.cross }
  end

  def move_waypoint action, distance
    vector = WORLD_DIRECTIONS[action]
    @waypoint_position += distance * vector
  end

  def move value
    @position += @waypoint_position * value
  end
end

file = File.read('inputs/day12.txt')
instructions = file.lines
[Ship, ShipWithWaypoints].each do |ship_type|
  ship = ship_type.new
  ship.navigate(instructions)
  puts(ship.position.sum { |v| v.abs })
end