DEV Community

Discussion on: Advent of Code 2020 Solution Megathread - Day 12: Rain Risk

Collapse
 
sleeplessbyte profile image
Derk-Jan Karrenbeld

There are (at least) two ways to "model" the solution: polar coordinates or vector/gonio math. Here is a Ruby OOP solution using the latter:

require 'benchmark'

class Ship

  attr_reader :position

  def initialize
    self.facing = 90
    self.position = Position.new(0, 0)
    self.waypoint = Waypoint.new
  end

  def sail!(instruction)

    case instruction.action
      when 'N'
        position.translate!(0, -instruction.value)
      when 'S'
        position.translate!(0,  instruction.value)
      when 'E'
        position.translate!( instruction.value, 0)
      when 'W'
        position.translate!(-instruction.value, 0)
      when 'L'
        self.facing = (self.facing - instruction.value) % 360
      when 'R'
        self.facing = (self.facing + instruction.value) % 360
      when 'F'
        direction = %w[N E S W][facing / 90]
        sail! instruction.with_action(direction)
        # input only contains 90 180 270 so no need for
        # SOS Castoa (gonio).

      end
  end

  def sail_with_waypoint!(instruction)
    if %w[N S E W L R].include?(instruction.action)
      waypoint.sail! instruction
      return
    end

    dx, dy = waypoint.position.difference(Position.new(0, 0))
    self.position.translate!(dx * instruction.value, dy * instruction.value)
  end

  private

  attr_accessor :facing, :waypoint
  attr_writer :position
end

class Waypoint

  attr_reader :position

  def initialize
    self.position = Position.new(10, -1)
  end

  def sail!(instruction)
    case instruction.action
    when 'N'
      position.translate!(0, -instruction.value)
    when 'S'
      position.translate!(0,  instruction.value)
    when 'E'
      position.translate!( instruction.value, 0)
    when 'W'
      position.translate!(-instruction.value, 0)
    when 'L'
      theta = -(instruction.value / 180.to_f * Math::PI)

      self.position = Position.new(
        (position.x * Math.cos(theta) - position.y * Math.sin(theta)).round,
        (position.x * Math.sin(theta) + position.y * Math.cos(theta)).round
      )
    when 'R'
      theta = instruction.value / 180.to_f * Math::PI

      self.position = Position.new(
        (position.x * Math.cos(theta) - position.y * Math.sin(theta)).round,
        (position.x * Math.sin(theta) + position.y * Math.cos(theta)).round
      )
    end
  end

  private

  attr_accessor :facing
  attr_writer :position
end

class Instruction
  attr_reader :action, :value

  def initialize(action, value)
    self.action = action
    self.value = value.to_i
  end

  def with_action(new_action)
    Instruction.new(new_action, value)
  end

  private

  attr_writer :action, :value
end

class Position
  attr_reader :x, :y

  def initialize(x, y)
    self.x = x
    self.y = y
  end

  def translate!(x, y)
    self.x += x
    self.y += y

    self
  end

  def distance(other)
    difference(other).map(&:abs).sum
  end

  def difference(other)
    [x - other.x, y - other.y]
  end

  private

  attr_writer :x, :y
end

module NavigationInstructions
  def self.from_lines(lines)
    lines.map do |line|
      line = line.chomp
      Instruction.new(line[0], line[1..])
    end
  end
end

def track(ship)
  original = ship.position.dup
  yield
  original.distance(ship.position)
end

source = 'input.txt'

instructions = NavigationInstructions.from_lines(File.readlines(source))
verbose = false

Benchmark.bmbm do |x|
  x.report(:part_1) do
    ship = Ship.new

    distance = track(ship) do
      instructions.each do |instruction|
        ship.sail! instruction
        puts "[ahoy] #{instruction.action} by #{instruction.value}: #{ship.position.x}, #{ship.position.y}" if verbose
      end
    end

    puts distance
  end

  x.report(:part_2) do
    ship = Ship.new

    distance = track(ship) do
      instructions.each do |instruction|
        ship.sail_with_waypoint! instruction
        puts "[ahoy] #{instruction.action} by #{instruction.value}: #{ship.position.x}, #{ship.position.y}" if verbose
      end
    end
    puts distance
  end
end
Enter fullscreen mode Exit fullscreen mode