Le blog d'Archiloque

Advent of code 2019 in Ruby

For a second year I did some exercises from advent of code.

Like last year I’ve started several days after the official beginning to avoid the feel of being part of a competition or to have a fixed schedule.

Most of the exercises have the same qualities I appreciated in last year ones: not very long and don’t require external knowledge, and especially no knowledge of specific algorithms.

And same for the things I disliked:

  • The writing theme that add flavor but makes some problems harder to understand.

  • Exercises that feels like busywork when you have known about their problem domain, for example if you already read things about compilers, roguelike development and git, you already know how to solve most of the cases.

  • Exercises with edge cases that are not tested in the provided samples so it makes harder to check if things work as expected.

I’ve stopped at problem 2 of day 18 because I’ve lost my interest in it, and I prefer to stop before it feels too much like work.

If you have comments or question about these please contact me.

Day 01

01_1.rb
# @param [Integer] mass
def fuel(mass)
  (mass.to_f / 3.0).floor - 2
end

assert_equal(2, fuel(12))
assert_equal(2, fuel(14))
assert_equal(654, fuel(1969))
assert_equal(33583, fuel(100756))

result = 0
File.foreach('input.txt') do |line|
  result += fuel(line.strip.to_i)
end
p result
01_2.rb
# @param [Integer] mass
def fuel(mass)
  f = ((mass.to_f / 3.0).floor).to_i - 2
  if f > 0
    f + fuel(f)
  else
    0
  end
end

assert_equal(2, fuel(14))
assert_equal(966, fuel(1969))
assert_equal(50346, fuel(100756))

result = 0
File.foreach('input.txt') do |line|
  result += fuel(line.strip.to_i)
end
p result

Day 02

02_1.rb
def interpret_instruction(memory, instruction_pointer)
  opcode = memory[instruction_pointer]
  case opcode
  when 1 # add
    memory[memory[instruction_pointer + 3]] =
        (memory[memory[instruction_pointer + 1]] || 0) +
            (memory[memory[instruction_pointer + 2]] || 0)
    4
  when 2 # multiply
    memory[memory[instruction_pointer + 3]] =
        (memory[memory[instruction_pointer + 1]] || 0) *
            (memory[memory[instruction_pointer + 2]] || 0)
    4
  when 99
    nil
  else
    raise opcode.to_s
  end
end

def interpret_program(memory)
  instruction_pointer = 0
  while (delta = interpret_instruction(memory, instruction_pointer))
    instruction_pointer += delta
  end
  memory
end
02_2.rb
def interpret_instruction(memory, instruction_pointer)
  opcode = memory[instruction_pointer]
  case opcode
  when 1 # add
    memory[memory[instruction_pointer + 3]] =
        (memory[memory[instruction_pointer + 1]] || 0) +
            (memory[memory[instruction_pointer + 2]] || 0)
    4
  when 2 # multiply
    memory[memory[instruction_pointer + 3]] =
        (memory[memory[instruction_pointer + 1]] || 0) *
            (memory[memory[instruction_pointer + 2]] || 0)
    4
  when 99
    nil
  else
    raise opcode.to_s
  end
end

def interpret_program(memory)
  instruction_pointer = 0
  while (delta = interpret_instruction(memory, instruction_pointer))
    instruction_pointer += delta
  end
  memory
end

Day 03

03_1.rb
def draw_path(grid, path, wire)
  current_position = {line: 0, column: 0}
  path.split(',').each do |path_fragment|
    path_fragment_direction = path_fragment[0]
    case path_fragment_direction
    when 'R'
      direction = {line: 0, column: 1}
    when 'L'
      direction = {line: 0, column: -1}
    when 'U'
      direction = {line: 1, column: 0}
    when 'D'
      direction = {line: -1, column: 0}
    else
      raise path_fragment_direction
    end
    number_of_steps = path_fragment[1..-1].to_i
    number_of_steps.times do
      current_position[:line] += +direction[:line]
      current_position[:column] += +direction[:column]
      grid[current_position[:line]][current_position[:column]] << wire
    end
  end
end

def process_graph(pathes)
  grid = Hash.new { |h1, k1| h1[k1] = Hash.new { |h2, k2| h2[k2] = [] } }
  draw_path(grid, pathes[0], 'A')
  draw_path(grid, pathes[1], 'B')
  distance = 1
  while true
    distance.downto(0) do |line|
      column = distance - line
      if grid[line][column].uniq.length == 2
        return distance
      end
      if grid[line][-column].uniq.length == 2
        return distance
      end
      if grid[-line][column].uniq.length == 2
        return distance
      end
      if grid[-line][-column].uniq.length == 2
        return distance
      end
    end
    distance += 1
  end
end
03_2.rb
def draw_path(grid, path, wire_index)
  current_position = {line: 0, column: 0}
  distance = 0
  path.split(',').each do |path_fragment|
    path_fragment_direction = path_fragment[0]
    case path_fragment_direction
    when 'R'
      direction = {line: 0, column: 1}
    when 'L'
      direction = {line: 0, column: -1}
    when 'U'
      direction = {line: 1, column: 0}
    when 'D'
      direction = {line: -1, column: 0}
    else
      raise path_fragment_direction
    end
    number_of_steps = path_fragment[1..-1].to_i
    number_of_steps.times do
      distance += 1
      current_position[:line] += +direction[:line]
      current_position[:column] += +direction[:column]
      position = grid[current_position[:line]][current_position[:column]]
      if (!position[wire_index]) || (position[wire_index] > distance)
        position[wire_index] = distance
      end
    end
  end
end

def process_graph(pathes)
  grid = Hash.new { |h1, k1| h1[k1] = Hash.new { |h2, k2| h2[k2] = Array.new(2, nil) } }
  draw_path(grid, pathes[0], 0)
  draw_path(grid, pathes[1], 1)
  minimum_distance = -1
  grid.values.each do |v1|
    v1.values.each do |v2|
      if v2[0] && v2[1]
        distance = v2[0] + v2[1]
        if (minimum_distance == -1) || (distance < minimum_distance)
          minimum_distance = distance
        end
      end
    end
  end
  minimum_distance
end

Day 04

04_1.rb
NUMBER_OF_DIGITS = 6

# @param [Integer] current_number
# @param [Integer] digit_index
# @param [Integer] last_number
def next_digit(current_number, digit_index, last_number, &block)
  last_number.upto(9) do |digit|
    n = current_number + (digit * (10 ** (5 - digit_index)))
    if digit_index == (NUMBER_OF_DIGITS - 1)
      yield(n)
    else
      next_digit(n, digit_index + 1, digit, &block)
    end
  end
end

result = 0

next_digit(0, 0, 0) do |candidate|
  if(candidate >= 246515) && (candidate <= 739105) && (candidate.to_s.split(//).uniq.count < NUMBER_OF_DIGITS)
    p candidate
    result += 1
  end
end
p ''
p result
04_2.rb
NUMBER_OF_DIGITS = 6

# @param [Integer] current_number
# @param [Integer] digit_index
# @param [Integer] last_number
def next_digit(current_number, digit_index, last_number, &block)
  last_number.upto(9) do |digit|
    n = current_number + (digit * (10 ** (5 - digit_index)))
    if digit_index == (NUMBER_OF_DIGITS - 1)
      yield(n)
    else
      next_digit(n, digit_index + 1, digit, &block)
    end
  end
end

result = 0

next_digit(0, 0, 0) do |candidate|
  if (candidate >= 246515) && (candidate <= 739105)
    digit_counts = Hash.new(0)
    candidate.to_s.split(//).each do |n|
      digit_counts[n] += 1
    end
    if digit_counts.values.include? 2
      p candidate
      result += 1
    end
  end
end
p ''
p result

Day 05

05_1.rb
# @param [Array<Integer>] memory
# @param [Integer] instruction_pointer
# @param [String] instruction
# @param [Integer] parameter_index
# @return [Integer]
def value(memory, instruction_pointer, instruction, parameter_index)
  instruction_mode = instruction[-(3 + parameter_index)]
  p "  mode for #{parameter_index} is #{(instruction_mode == '0') ? 'position' : 'immediate'}"
  parameter_value = memory[instruction_pointer + parameter_index + 1]
  p "  parameter for #{parameter_index} is #{parameter_value}"
  case instruction_mode
  when '0' # position
    memory[parameter_value]
  when '1' # immediate
    parameter_value
  else
    raise instruction_mode
  end
end

# @param [Array<Integer>] memory
# @param [Integer] instruction_pointer
# @param [Array<Integer>] io
# @return [Integer|nil]
def interpret_instruction(memory, instruction_pointer, io)
  instruction = sprintf("%05d", memory[instruction_pointer])
  p ''
  p "instruction #{instruction}"
  opcode = instruction[-2..-1]
  case opcode
  when '01' # add
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    c = memory[instruction_pointer + 3]
    p "add #{a} & #{b} to #{c}"
    memory[c] = a + b
    4
  when '02' # multiply
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    c = memory[instruction_pointer + 3]
    p "multiply #{a} & #{b} to #{c}"
    memory[c] = a * b
    4
  when '03' # input
    a = memory[instruction_pointer + 1]
    p "input at #{a}"
    memory[a] = io.shift
    2
  when '04' # output
    a = value(memory, instruction_pointer, instruction, 0)
    p "output from #{a}"
    io << a
    2
  when '99'
    nil
  else
    raise opcode
  end
end

# @param [Array<Integer>] memory
# @param [Array<Integer>] io
def interpret_program(memory, io)
  instruction_pointer = 0
  while (delta = interpret_instruction(memory, instruction_pointer, io))
    instruction_pointer += delta
  end
  memory
end
05_2.rb
# @param [Array<Integer>] memory
# @param [Integer] instruction_pointer
# @param [String] instruction
# @param [Integer] parameter_index
# @return [Integer]
def value(memory, instruction_pointer, instruction, parameter_index)
  instruction_mode = instruction[-(3 + parameter_index)]
  parameter_value = memory[instruction_pointer + parameter_index + 1]
  case instruction_mode
  when '0' # position
    memory[parameter_value]
  when '1' # immediate
    parameter_value
  else
    raise instruction_mode
  end
end

def print_instruction(
    memory,
    instruction_pointer,
    instruction,
    opcode,
    number_of_params)
  STDOUT << "#{opcode} #{instruction} #{memory[instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
end

# @param [Array<Integer>] memory
# @param [Integer] instruction_pointer
# @param [Array<Integer>] io
# @return [Integer|nil]
def interpret_instruction(memory, instruction_pointer, io)
  instruction = sprintf("%05d", memory[instruction_pointer])
  opcode = instruction[-2..-1]
  case opcode
  when '01' # add
    print_instruction(memory, instruction_pointer, instruction, 'add', 3)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    c = memory[instruction_pointer + 3]
    STDOUT << "Store #{a} + #{b} at #{c}\n"
    memory[c] = a + b
    instruction_pointer + 4
  when '02' # multiply
    print_instruction(memory, instruction_pointer, instruction, 'multiply', 3)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    c = memory[instruction_pointer + 3]
    STDOUT << "Store #{a} * #{b} at #{c}\n"
    memory[c] = a * b
    instruction_pointer + 4
  when '03' # input
    print_instruction(memory, instruction_pointer, instruction, 'input', 1)
    a = memory[instruction_pointer + 1]
    STDOUT << "Input at #{a}\n"
    memory[a] = io.shift
    instruction_pointer + 2
  when '04' # output
    print_instruction(memory, instruction_pointer, instruction, 'output', 1)
    a = value(memory, instruction_pointer, instruction, 0)
    STDOUT << "Output #{a}\n"
    io << a
    instruction_pointer + 2
  when '05' # jump-if-true
    print_instruction(memory, instruction_pointer, instruction, 'jump-if-true', 2)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    if a != 0
      STDOUT << "Set instruction pointer to #{b} (#{a}, #{b})\n"
      b
    else
      STDOUT << "Do nothing\n"
      instruction_pointer + 3
    end
  when '06' # jump-if-false
    print_instruction(memory, instruction_pointer, instruction, 'jump-if-false', 2)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    if a == 0
      STDOUT << "Set instruction pointer to #{b} (#{a}, #{b})\n"
      b
    else
      STDOUT << "Do nothing\n"
      instruction_pointer + 3
    end
  when '07' # less-than
    print_instruction(memory, instruction_pointer, instruction, 'less-than', 3)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    c = memory[instruction_pointer + 3]
    value = (a < b) ? 1 : 0
    STDOUT << "Set #{value} at #{c} (#{a} < #{b})\n"
    memory[c] = value
    instruction_pointer + 4
  when '08' # equals
    print_instruction(memory, instruction_pointer, instruction, 'equals', 3)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    c = memory[instruction_pointer + 3]
    value = (a == b) ? 1 : 0
    STDOUT << "Set #{value} at #{c} (#{a} == #{b})\n"
    memory[c] = value
    instruction_pointer + 4
  when '99'
    print_instruction(memory, instruction_pointer, instruction, 'exit', 0)
    STDOUT << "Exit\n"
    nil
  else
    raise opcode
  end
end

# @param [Array<Integer>] memory
# @param [Array<Integer>] io
# @return [Array<Integer>]
def interpret_program(memory, io)
  STDOUT << "\n"
  instruction_pointer = 0
  while (instruction_pointer = interpret_instruction(memory, instruction_pointer, io))
  end
  io
end

Day 06

06_1.rb
def distance_to_com(parents, distance_to_center, element)
  if distance_to_center.key?(element)
    distance_to_center[element]
  else
    distance = distance_to_com(parents, distance_to_center, parents[element]) + 1
    distance_to_center[element] = distance
    distance
  end
end

def calculate(file)
  parents = {}
  File.foreach(file) do |line|
    orbit = line.strip.split(')')
    parents[orbit[1]] = orbit[0]
  end

  total = 0
  distance_to_center = {'COM' => 0}
  parents.keys.each do |element|
    total += distance_to_com(parents, distance_to_center, element)
  end
  total
end
06_2.rb
def path_to_com(parents, element)
  result = []
  current_element = parents[element]
  until current_element == 'COM'
    result << current_element
    current_element = parents[current_element]
  end
  result
end

def calculate(file)
  parents = {}
  File.foreach(file) do |line|
    orbit = line.strip.split(')')
    parents[orbit[1]] = orbit[0]
  end

  you_path = path_to_com(parents, 'YOU')
  san_path = path_to_com(parents, 'SAN')
  (you_path - san_path).length + (san_path - you_path).length
end

Day 07

07_1.rb
# @param [Array<Integer>] memory
# @param [Integer] instruction_pointer
# @param [String] instruction
# @param [Integer] parameter_index
# @return [Integer]
def value(memory, instruction_pointer, instruction, parameter_index)
  instruction_mode = instruction[-(3 + parameter_index)]
  parameter_value = memory[instruction_pointer + parameter_index + 1]
  case instruction_mode
  when '0' # position
    memory[parameter_value]
  when '1' # immediate
    parameter_value
  else
    raise instruction_mode
  end
end

def print_instruction(
    memory,
    instruction_pointer,
    instruction,
    opcode,
    number_of_params)
  STDOUT << "#{opcode} #{instruction} #{memory[instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
end

# @param [Array<Integer>] memory
# @param [Integer] instruction_pointer
# @param [Array<Integer>] io
# @return [Integer|nil]
def interpret_instruction(memory, instruction_pointer, io)
  instruction = sprintf("%05d", memory[instruction_pointer])
  opcode = instruction[-2..-1]
  case opcode
  when '01' # add
    print_instruction(memory, instruction_pointer, instruction, 'add', 3)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    c = memory[instruction_pointer + 3]
    STDOUT << "Store #{a} + #{b} at #{c}\n"
    memory[c] = a + b
    instruction_pointer + 4
  when '02' # multiply
    print_instruction(memory, instruction_pointer, instruction, 'multiply', 3)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    c = memory[instruction_pointer + 3]
    STDOUT << "Store #{a} * #{b} at #{c}\n"
    memory[c] = a * b
    instruction_pointer + 4
  when '03' # input
    print_instruction(memory, instruction_pointer, instruction, 'input', 1)
    a = memory[instruction_pointer + 1]
    STDOUT << "Input at #{a}\n"
    memory[a] = io.shift
    instruction_pointer + 2
  when '04' # output
    print_instruction(memory, instruction_pointer, instruction, 'output', 1)
    a = value(memory, instruction_pointer, instruction, 0)
    STDOUT << "Output #{a}\n"
    io << a
    instruction_pointer + 2
  when '05' # jump-if-true
    print_instruction(memory, instruction_pointer, instruction, 'jump-if-true', 2)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    if a != 0
      STDOUT << "Set instruction pointer to #{b} (#{a}, #{b})\n"
      b
    else
      STDOUT << "Do nothing\n"
      instruction_pointer + 3
    end
  when '06' # jump-if-false
    print_instruction(memory, instruction_pointer, instruction, 'jump-if-false', 2)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    if a == 0
      STDOUT << "Set instruction pointer to #{b} (#{a}, #{b})\n"
      b
    else
      STDOUT << "Do nothing\n"
      instruction_pointer + 3
    end
  when '07' # less-than
    print_instruction(memory, instruction_pointer, instruction, 'less-than', 3)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    c = memory[instruction_pointer + 3]
    value = (a < b) ? 1 : 0
    STDOUT << "Set #{value} at #{c} (#{a} < #{b})\n"
    memory[c] = value
    instruction_pointer + 4
  when '08' # equals
    print_instruction(memory, instruction_pointer, instruction, 'equals', 3)
    a = value(memory, instruction_pointer, instruction, 0)
    b = value(memory, instruction_pointer, instruction, 1)
    c = memory[instruction_pointer + 3]
    value = (a == b) ? 1 : 0
    STDOUT << "Set #{value} at #{c} (#{a} == #{b})\n"
    memory[c] = value
    instruction_pointer + 4
  when '99'
    print_instruction(memory, instruction_pointer, instruction, 'exit', 0)
    STDOUT << "Exit\n"
    nil
  else
    raise opcode
  end
end

# @param [Array<Integer>] memory
# @param [Array<Integer>] io
# @return [Array<Integer>]
def interpret_program(memory, io)
  STDOUT << "\n"
  instruction_pointer = 0
  while (instruction_pointer = interpret_instruction(memory, instruction_pointer, io))
  end
  io
end


# @param [Array<Integer>] memory
# @param [Integer] phase_setting
# @param [Integer] input_signal
def run_program_on_amplifier(memory, phase_setting, input_signal)
  io = [phase_setting, input_signal]
  interpret_program(memory.dup, io)
  io[0]
end

# @param [Array<Integer>] memory
# @param [Array<Integer>] phase_settings
def test_combination(memory, phase_settings)
  o1 = run_program_on_amplifier(memory, phase_settings[0], 0)
  o2 = run_program_on_amplifier(memory, phase_settings[1], o1)
  o3 = run_program_on_amplifier(memory, phase_settings[2], o2)
  o4 = run_program_on_amplifier(memory, phase_settings[3], o3)
  o5 = run_program_on_amplifier(memory, phase_settings[4], o4)
  o5
end

POSSIBLE_SETTINGS = [0, 1, 2, 3, 4]

# @param [Array<Integer>] memory
# @return [Hash]
def max_combination(memory)
  max_thruster = 0
  max_sequence = []
  POSSIBLE_SETTINGS.each do |s1|
    (POSSIBLE_SETTINGS - [s1]).each do |s2|
      (POSSIBLE_SETTINGS - [s1, s2]).each do |s3|
        (POSSIBLE_SETTINGS - [s1, s2, s3]).each do |s4|
          (POSSIBLE_SETTINGS - [s1, s2, s3, s4]).each do |s5|
            phase_settings = [s1, s2, s3, s4, s5]
            result = test_combination(memory, phase_settings)
            if result > max_thruster
              max_thruster = result
              max_sequence = phase_settings
            end
          end
        end
      end
    end
  end
  {max_thruster: max_thruster, max_sequence: max_sequence}
end
07_2.rb
LOG_INFO = false

class Amplifier

  STATUS_EXIT = :exit
  STATUS_NEED_INPUT = :need_input
  STATUS_CAN_CONTINUE = :can_continue

  attr_reader :status

  # @param [Integer] index
  # @param [Array<Integer>] memory
  # @param [Array<Integer>] io_in
  def initialize(index, memory, io_in)
    @index = index
    @memory = memory
    @io_in = io_in
    @instruction_pointer = 0
  end

  # @param [Array<Integer>] input
  # @return [Array<Integer>|nil]
  def resume(input)
    io_out = []
    @io_in.concat(input)
    if LOG_INFO
      STDOUT << "\nResume amplifier #{@index} with pointer at #{@instruction_pointer} with input #{@io_in}\n"
    end
    while true
      @status = interpret_instruction(io_out)
      case status
      when STATUS_NEED_INPUT
        if LOG_INFO
          STDOUT << "Input needed, output is #{io_out} and pointer at #{@instruction_pointer}\n"
        end
        return io_out
      when STATUS_EXIT
        if LOG_INFO
          STDOUT << "Amplifier stopped\n"
        end
        return io_out
      when STATUS_CAN_CONTINUE
      else
        raise result
      end
    end
  end

  private

  # @param [Array<Integer>] io_out
  # @return [Symbol] STATUS_EXIT, STATUS_NEED_INPUT or STATUS_CAN_CONTINUE
  def interpret_instruction(io_out)
    instruction = sprintf("%05d", @memory[@instruction_pointer])
    opcode = instruction[-2..-1]
    case opcode
    when '01' # add
      print_instruction(instruction, 'add', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = @memory[@instruction_pointer + 3]
      log_instruction("Store (#{a} + #{b}) at #{c}")
      @memory[c] = a + b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '02' # multiply
      print_instruction(instruction, 'multiply', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = @memory[@instruction_pointer + 3]
      log_instruction("Store (#{a} * #{b}) at #{c}")
      @memory[c] = a * b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '03' # input
      print_instruction(instruction, 'input', 1)
      if @io_in.empty?
        return STATUS_NEED_INPUT
      end
      a = @memory[@instruction_pointer + 1]
      log_instruction("Store input at #{a}")
      @memory[a] = @io_in.shift
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '04' # output
      print_instruction(instruction, 'output', 1)
      a = value(instruction, 0)
      log_instruction("Output #{a}")
      io_out << a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '05' # jump-if-true
      print_instruction(instruction, 'jump-if-true', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a != 0
        log_instruction("Set instruction pointer to #{b} (#{a} != 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} == 0)")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '06' # jump-if-false
      print_instruction(instruction, 'jump-if-false', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a == 0
        log_instruction("Set instruction pointer to #{b} (#{a} == 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} != #{0})")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '07' # less-than
      print_instruction(instruction, 'less-than', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = @memory[@instruction_pointer + 3]
      value = (a < b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} < #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '08' # equals
      print_instruction(instruction, 'equals', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = @memory[@instruction_pointer + 3]
      value = (a == b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} == #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '99'
      print_instruction(instruction, 'exit', 0)
      log_instruction("Exit")
      STATUS_EXIT
    else
      raise opcode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = @memory[@instruction_pointer + parameter_index + 1]
    case instruction_mode
    when '0' # position
      @memory[parameter_value]
    when '1' # immediate
      parameter_value
    else
      raise instruction_mode
    end
  end

  def print_instruction(
      instruction,
      opcode,
      number_of_params)
    if LOG_INFO
      STDOUT << "#{opcode} #{instruction} #{@memory[@instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
    end
  end

  def log_instruction(message)
    if LOG_INFO
      STDOUT << "#{message}\n"
    end
  end
end

# @param [Array<Integer>] memory
# @param [Array<Integer>] phase_settings
# @return [Integer]
def test_combination(memory, phase_settings)
  amplifiers = [
      Amplifier.new(0, memory.dup, [phase_settings[0]]),
      Amplifier.new(1, memory.dup, [phase_settings[1]]),
      Amplifier.new(2, memory.dup, [phase_settings[2]]),
      Amplifier.new(3, memory.dup, [phase_settings[3]]),
      Amplifier.new(4, memory.dup, [phase_settings[4]])
  ]

  io = [0]
  current_amplifier_index = 0
  last_thruster_signal = nil
  while amplifiers[current_amplifier_index].status != Amplifier::STATUS_EXIT
    io = amplifiers[current_amplifier_index].resume(io)
    if current_amplifier_index == 4
      last_thruster_signal = io.first
      current_amplifier_index = 0
      if LOG_INFO
        STDOUT << "\n"
      end
    else
      current_amplifier_index += 1
    end
  end
  last_thruster_signal
end

POSSIBLE_SETTINGS = [5, 6, 7, 8, 9]

# @param [Array<Integer>] memory
# @return [Hash]
def max_combination(memory)
  max_thruster = 0
  max_sequence = []
  POSSIBLE_SETTINGS.each do |s1|
    (POSSIBLE_SETTINGS - [s1]).each do |s2|
      (POSSIBLE_SETTINGS - [s1, s2]).each do |s3|
        (POSSIBLE_SETTINGS - [s1, s2, s3]).each do |s4|
          (POSSIBLE_SETTINGS - [s1, s2, s3, s4]).each do |s5|
            phase_settings = [s1, s2, s3, s4, s5]
            result = test_combination(memory, phase_settings)
            if LOG_INFO
              STDOUT << "\n"
            end
            if result > max_thruster
              max_thruster = result
              max_sequence = phase_settings
            end
          end
        end
      end
    end
  end
  {max_thruster: max_thruster, max_sequence: max_sequence}
end

Day 08

08_1.rb
INPUT = IO.read('input.txt')

LAYER_SIZE = 25 * 6
NUMBER_OF_LAYERS = INPUT.length / LAYER_SIZE

min_zeroes = LAYER_SIZE
min_zeroes_value = -1

0.upto(NUMBER_OF_LAYERS - 1) do |layer_index|
  layer = INPUT[(LAYER_SIZE * layer_index), LAYER_SIZE]
  zeroes = layer.count('0')
  if zeroes < min_zeroes
    min_zeroes = zeroes
    min_zeroes_value = layer.count('1') * layer.count('2')
  end
end
p min_zeroes_value
08_2.rb
INPUT = IO.read('input.txt')
LAYER_SIZE = 25 * 6
NUMBER_OF_LAYERS = INPUT.length / LAYER_SIZE

TRANSPARENT_COLOR = '2'
def calculate_pixel(position)
  current_layer_index = 0
  while current_layer_index < NUMBER_OF_LAYERS
    value = INPUT[(current_layer_index * LAYER_SIZE) + position]
    STDOUT << "pos #{position} index #{current_layer_index} value #{value}\n"
    if value != TRANSPARENT_COLOR
      return value
    end
    current_layer_index += 1
  end
  TRANSPARENT_COLOR
end
result = []
0.upto(LAYER_SIZE - 1) do |position|
  result << calculate_pixel(position)
end

0.upto(5) do |line_index|
  STDOUT << "#{result[(line_index * 25), 25].join('').gsub('0', ' ').gsub('1', '#')}\n"
end

Day 09

09_1.rb
LOG_INFO = true

class Amplifier

  STATUS_EXIT = :exit
  STATUS_NEED_INPUT = :need_input
  STATUS_CAN_CONTINUE = :can_continue

  attr_reader :status

  # @param [Integer] index
  # @param [Array<Integer>] memory
  # @param [Array<Integer>] io_in
  def initialize(index, memory, io_in)
    @index = index
    @memory = memory
    @io_in = io_in
    @instruction_pointer = 0
    @relative_base = 0
  end

  # @param [Array<Integer>] input
  # @return [Array<Integer>|nil]
  def resume(input)
    io_out = []
    @io_in.concat(input)
    if LOG_INFO
      STDOUT << "\nResume amplifier #{@index} with pointer at #{@instruction_pointer} with input #{@io_in}\n"
    end
    while true
      @status = interpret_instruction(io_out)
      case status
      when STATUS_NEED_INPUT
        if LOG_INFO
          STDOUT << "Input needed, output is #{io_out} and pointer at #{@instruction_pointer}\n"
        end
        return io_out
      when STATUS_EXIT
        if LOG_INFO
          STDOUT << "Amplifier stopped\n"
        end
        return io_out
      when STATUS_CAN_CONTINUE
      else
        raise result
      end
    end
  end

  private

  # @param [Integer] index
  # @return [Integer]
  def memory(index)
    @memory[index] || 0
  end

  # @param [Array<Integer>] io_out
  # @return [Symbol] STATUS_EXIT, STATUS_NEED_INPUT or STATUS_CAN_CONTINUE
  def interpret_instruction(io_out)
    instruction = sprintf("%05d", memory(@instruction_pointer))
    opcode = instruction[-2..-1]
    case opcode
    when '01' # add
      print_instruction(instruction, 'add', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} + #{b}) at #{c}")
      @memory[c] = a + b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '02' # multiply
      print_instruction(instruction, 'multiply', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} * #{b}) at #{c}")
      @memory[c] = a * b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '03' # input
      print_instruction(instruction, 'input', 1)
      if @io_in.empty?
        return STATUS_NEED_INPUT
      end
      a = s_value(instruction, 0)
      log_instruction("Store input at #{a}")
      @memory[a] = @io_in.shift
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '04' # output
      print_instruction(instruction, 'output', 1)
      a = value(instruction, 0)
      log_instruction("Output #{a}")
      io_out << a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '05' # jump-if-true
      print_instruction(instruction, 'jump-if-true', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a != 0
        log_instruction("Set instruction pointer to #{b} (#{a} != 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} == 0)")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '06' # jump-if-false
      print_instruction(instruction, 'jump-if-false', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a == 0
        log_instruction("Set instruction pointer to #{b} (#{a} == 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} != #{0})")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '07' # less-than
      print_instruction(instruction, 'less-than', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a < b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} < #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '08' # equals
      print_instruction(instruction, 'equals', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a == b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} == #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '09' # adjust-relative-base
      print_instruction(instruction, 'adjust-relative-base', 1)
      a = value(instruction, 0)
      log_instruction("Adjust relative base #{@relative_base} + #{a}")
      @relative_base += a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '99'
      print_instruction(instruction, 'exit', 0)
      log_instruction("Exit")
      STATUS_EXIT
    else
      raise opcode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def s_value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      parameter_value
    when '1' # position
      parameter_value
    when '2' # relative
      parameter_value + @relative_base
    else
      raise instruction_mode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      memory(parameter_value)
    when '1' # immediate
      parameter_value
    when '2' # relative
      memory(parameter_value + @relative_base)
    else
      raise instruction_mode
    end
  end

  def print_instruction(
      instruction,
      opcode,
      number_of_params)
    if LOG_INFO
      STDOUT << "#{opcode} #{instruction} #{@memory[@instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
    end
  end

  def log_instruction(message)
    if LOG_INFO
      STDOUT << "#{message}\n"
    end
  end
end

def run_amplifier(memory, io_in = [])
  Amplifier.new(
      0,
      memory,
      io_in)
      .resume([])
end
09_2.rb
LOG_INFO = false

class Amplifier

  STATUS_EXIT = :exit
  STATUS_NEED_INPUT = :need_input
  STATUS_CAN_CONTINUE = :can_continue

  attr_reader :status

  # @param [Integer] index
  # @param [Array<Integer>] memory
  # @param [Array<Integer>] io_in
  def initialize(index, memory, io_in)
    @index = index
    @memory = memory
    @io_in = io_in
    @instruction_pointer = 0
    @relative_base = 0
  end

  # @param [Array<Integer>] input
  # @return [Array<Integer>|nil]
  def resume(input)
    io_out = []
    @io_in.concat(input)
    if LOG_INFO
      STDOUT << "\nResume amplifier #{@index} with pointer at #{@instruction_pointer} with input #{@io_in}\n"
    end
    while true
      @status = interpret_instruction(io_out)
      case status
      when STATUS_NEED_INPUT
        if LOG_INFO
          STDOUT << "Input needed, output is #{io_out} and pointer at #{@instruction_pointer}\n"
        end
        return io_out
      when STATUS_EXIT
        if LOG_INFO
          STDOUT << "Amplifier stopped\n"
        end
        return io_out
      when STATUS_CAN_CONTINUE
      else
        raise result
      end
    end
  end

  private

  # @param [Integer] index
  # @return [Integer]
  def memory(index)
    @memory[index] || 0
  end

  # @param [Array<Integer>] io_out
  # @return [Symbol] STATUS_EXIT, STATUS_NEED_INPUT or STATUS_CAN_CONTINUE
  def interpret_instruction(io_out)
    instruction = sprintf("%05d", memory(@instruction_pointer))
    opcode = instruction[-2..-1]
    case opcode
    when '01' # add
      print_instruction(instruction, 'add', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} + #{b}) at #{c}")
      @memory[c] = a + b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '02' # multiply
      print_instruction(instruction, 'multiply', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} * #{b}) at #{c}")
      @memory[c] = a * b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '03' # input
      print_instruction(instruction, 'input', 1)
      if @io_in.empty?
        return STATUS_NEED_INPUT
      end
      a = s_value(instruction, 0)
      log_instruction("Store input at #{a}")
      @memory[a] = @io_in.shift
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '04' # output
      print_instruction(instruction, 'output', 1)
      a = value(instruction, 0)
      log_instruction("Output #{a}")
      io_out << a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '05' # jump-if-true
      print_instruction(instruction, 'jump-if-true', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a != 0
        log_instruction("Set instruction pointer to #{b} (#{a} != 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} == 0)")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '06' # jump-if-false
      print_instruction(instruction, 'jump-if-false', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a == 0
        log_instruction("Set instruction pointer to #{b} (#{a} == 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} != #{0})")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '07' # less-than
      print_instruction(instruction, 'less-than', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a < b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} < #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '08' # equals
      print_instruction(instruction, 'equals', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a == b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} == #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '09' # adjust-relative-base
      print_instruction(instruction, 'adjust-relative-base', 1)
      a = value(instruction, 0)
      log_instruction("Adjust relative base #{@relative_base} + #{a}")
      @relative_base += a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '99'
      print_instruction(instruction, 'exit', 0)
      log_instruction("Exit")
      STATUS_EXIT
    else
      raise opcode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def s_value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      parameter_value
    when '1' # position
      parameter_value
    when '2' # relative
      parameter_value + @relative_base
    else
      raise instruction_mode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      memory(parameter_value)
    when '1' # immediate
      parameter_value
    when '2' # relative
      memory(parameter_value + @relative_base)
    else
      raise instruction_mode
    end
  end

  def print_instruction(
      instruction,
      opcode,
      number_of_params)
    if LOG_INFO
      STDOUT << "#{opcode} #{instruction} #{@memory[@instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
    end
  end

  def log_instruction(message)
    if LOG_INFO
      STDOUT << "#{message}\n"
    end
  end
end

def run_amplifier(memory, io_in = [])
  Amplifier.new(
      0,
      memory,
      io_in)
      .resume([])
end

Day 10

10_1.rb
require 'set'

ASTEROID_CHAR = '#'
# @param [String] field
# @return [Hash]
def process(field)
  splitted_field = field.split("\n")
  lines_nb = splitted_field.length
  columns_nb = splitted_field[0].length
  asteroids = []
  0.upto(lines_nb - 1) do |line|
    0.upto(columns_nb - 1) do |column|
      if splitted_field[line][column] == ASTEROID_CHAR
        asteroids << {column: column, line: line}
      end
    end
  end
  p asteroids
  max_asteroid_position = nil
  max_asteroid_number = 0
  asteroids.each_with_index do |from_asteroid, from_index|
    STDOUT << "from #{from_asteroid}\n"
    asteroids_with_delta_positive = Set.new
    asteroids_with_delta_negative = Set.new
    above = false
    under = false
    asteroids.each_with_index do |to_asteroid, to_index|
      if from_index != to_index
        STDOUT << "  to #{to_asteroid} "
        delta_column = from_asteroid[:column] - to_asteroid[:column]
        delta_line = from_asteroid[:line] - to_asteroid[:line]
        if delta_column > 0
          STDOUT << "#{Rational(delta_line, delta_column)}\n"
          asteroids_with_delta_positive << Rational(delta_line, delta_column)
        elsif delta_column < 0
          STDOUT << "#{Rational(delta_line, delta_column)}\n"
          asteroids_with_delta_negative << Rational(delta_line, delta_column)
        elsif delta_line > 0
          STDOUT << "under\n"
          above = true
        else
          STDOUT << "above\n"
          under = true
        end
      end
    end
    visible_number = asteroids_with_delta_positive.length + asteroids_with_delta_negative.length + (above ? 1 : 0) + (under ? 1 : 0)
    STDOUT << "Can see #{visible_number} #{asteroids_with_delta_positive} #{asteroids_with_delta_negative} #{above} #{under}\n\n"
    if visible_number > max_asteroid_number
      max_asteroid_number = visible_number
      max_asteroid_position = from_asteroid
    end
  end
  max_asteroid_position.merge({visible: max_asteroid_number})
end
10_2.rb
require 'set'

ASTEROID_CHAR = '#'
# @param [String] field
# @return [Array]
def process(field, station_column, station_line)
  splitted_field = field.split("\n")
  lines_nb = splitted_field.length
  columns_nb = splitted_field[0].length
  number_of_asteroids = 0
  left_quadrant = Hash.new { |hash, key| hash[key] = [] }
  right_quadrant = Hash.new { |hash, key| hash[key] = [] }
  above = []
  under = []

  0.upto(lines_nb - 1) do |line|
    0.upto(columns_nb - 1) do |column|
      if splitted_field[line][column] == ASTEROID_CHAR
        if (line != station_line) || (column != station_column)
          number_of_asteroids += 1
          asteroid = {
              column: column,
              line: line,
              distance: ((line - station_line) ** 2) + ((column - station_column) ** 2)
          }

          delta_column = station_column - column
          delta_line = station_line - line
          if delta_column > 0
            #STDOUT << "#{Rational(delta_line, delta_column)}\n"
            left_quadrant[Rational(delta_line, delta_column)] << asteroid
          elsif delta_column < 0
            #STDOUT << "#{Rational(delta_line, delta_column)}\n"
            right_quadrant[Rational(delta_line, delta_column)] << asteroid
          elsif delta_line > 0
            #STDOUT << "under\n"
            above << asteroid
          else
            #STDOUT << "above\n"
            under << asteroid
          end
        end
      end
    end
  end
  (right_quadrant.values + left_quadrant.values + [above, under]).each do |asteriods|
    asteriods.sort_by!{|a| a[:distance]}
  end
  result = []
  quadrants = [
      {0 => above},
      right_quadrant,
      {0 => under},
      left_quadrant
  ]

  p above
  p right_quadrant
  p under
  p left_quadrant


  while result.length < number_of_asteroids
    quadrants.each do |quadrant|
      quadrant.keys.sort.each do |angle|
        asteroids = quadrant[angle]
        result << asteroids.shift
        if asteroids.empty?
          quadrant.delete(angle)
        end
      end
    end
  end
  result
end

Day 11

11_1.rb
LOG_INFO = false

class Amplifier

  STATUS_EXIT = :exit
  STATUS_NEED_INPUT = :need_input
  STATUS_CAN_CONTINUE = :can_continue

  attr_reader :status

  # @param [Array<Integer>] memory
  # @param [Array<Integer>] io_in
  def initialize(memory, io_in)
    @memory = memory
    @io_in = io_in
    @instruction_pointer = 0
    @relative_base = 0
    @status = :can_continue
  end

  # @param [Array<Integer>] input
  # @return [Array<Integer>|nil]
  def resume(input)
    io_out = []
    @io_in.concat(input)
    if LOG_INFO
      STDOUT << "\nResume amplifier with pointer at #{@instruction_pointer} with input #{@io_in}\n"
    end
    while true
      @status = interpret_instruction(io_out)
      case status
      when STATUS_NEED_INPUT
        if LOG_INFO
          STDOUT << "Input needed, output is #{io_out} and pointer at #{@instruction_pointer}\n"
        end
        return io_out
      when STATUS_EXIT
        if LOG_INFO
          STDOUT << "Amplifier stopped\n"
        end
        return io_out
      when STATUS_CAN_CONTINUE
      else
        raise result
      end
    end
  end

  private

  # @param [Integer] index
  # @return [Integer]
  def memory(index)
    @memory[index] || 0
  end

  # @param [Array<Integer>] io_out
  # @return [Symbol] STATUS_EXIT, STATUS_NEED_INPUT or STATUS_CAN_CONTINUE
  def interpret_instruction(io_out)
    instruction = sprintf("%05d", memory(@instruction_pointer))
    opcode = instruction[-2..-1]
    case opcode
    when '01' # add
      print_instruction(instruction, 'add', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} + #{b}) at #{c}")
      @memory[c] = a + b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '02' # multiply
      print_instruction(instruction, 'multiply', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} * #{b}) at #{c}")
      @memory[c] = a * b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '03' # input
      print_instruction(instruction, 'input', 1)
      if @io_in.empty?
        return STATUS_NEED_INPUT
      end
      a = s_value(instruction, 0)
      log_instruction("Store input at #{a}")
      @memory[a] = @io_in.shift
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '04' # output
      print_instruction(instruction, 'output', 1)
      a = value(instruction, 0)
      log_instruction("Output #{a}")
      io_out << a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '05' # jump-if-true
      print_instruction(instruction, 'jump-if-true', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a != 0
        log_instruction("Set instruction pointer to #{b} (#{a} != 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} == 0)")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '06' # jump-if-false
      print_instruction(instruction, 'jump-if-false', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a == 0
        log_instruction("Set instruction pointer to #{b} (#{a} == 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} != #{0})")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '07' # less-than
      print_instruction(instruction, 'less-than', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a < b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} < #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '08' # equals
      print_instruction(instruction, 'equals', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a == b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} == #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '09' # adjust-relative-base
      print_instruction(instruction, 'adjust-relative-base', 1)
      a = value(instruction, 0)
      log_instruction("Adjust relative base #{@relative_base} + #{a}")
      @relative_base += a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '99'
      print_instruction(instruction, 'exit', 0)
      log_instruction("Exit")
      STATUS_EXIT
    else
      raise opcode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def s_value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      parameter_value
    when '1' # position
      parameter_value
    when '2' # relative
      parameter_value + @relative_base
    else
      raise instruction_mode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      memory(parameter_value)
    when '1' # immediate
      parameter_value
    when '2' # relative
      memory(parameter_value + @relative_base)
    else
      raise instruction_mode
    end
  end

  def print_instruction(
      instruction,
      opcode,
      number_of_params)
    if LOG_INFO
      STDOUT << "#{opcode} #{instruction} #{@memory[@instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
    end
  end

  def log_instruction(message)
    if LOG_INFO
      STDOUT << "#{message}\n"
    end
  end
end

COLOR_BLACK = 0
COLOR_WHITE = 1

current_line = 0
current_column = 0
current_direction = :up
hull = Hash.new { |hash, key| hash[key] = {} }

memory = IO.read('input.txt').split(',').map(&:to_i)

amplifier = Amplifier.new(
    memory,
    [])
until amplifier.status == Amplifier::STATUS_EXIT
  out = amplifier.resume([hull[current_line][current_column] || COLOR_BLACK])
  amplifier.status == Amplifier::STATUS_NEED_INPUT
  hull[current_line][current_column] = out[0]
  case out[1]
  when 0
    current_direction = {
        up: :left,
        left: :down,
        down: :right,
        right: :up
    }[current_direction]
  when 1
    current_direction = {
        up: :right,
        right: :down,
        down: :left,
        left: :up
    }[current_direction]
  else
  raise out[1].to_s
  end
  case current_direction
  when :up
    current_line -= 1
  when :down
    current_line += 1
  when :right
    current_column += 1
  when :left
    current_column -= 1
  else
    raise current_direction
  end
end
p hull.values.collect{|v| v.length}.sum
11_2.rb
LOG_INFO = false

class Amplifier

  STATUS_EXIT = :exit
  STATUS_NEED_INPUT = :need_input
  STATUS_CAN_CONTINUE = :can_continue

  attr_reader :status

  # @param [Array<Integer>] memory
  # @param [Array<Integer>] io_in
  def initialize(memory, io_in)
    @memory = memory
    @io_in = io_in
    @instruction_pointer = 0
    @relative_base = 0
    @status = :can_continue
  end

  # @param [Array<Integer>] input
  # @return [Array<Integer>|nil]
  def resume(input)
    io_out = []
    @io_in.concat(input)
    if LOG_INFO
      STDOUT << "\nResume amplifier with pointer at #{@instruction_pointer} with input #{@io_in}\n"
    end
    while true
      @status = interpret_instruction(io_out)
      case status
      when STATUS_NEED_INPUT
        if LOG_INFO
          STDOUT << "Input needed, output is #{io_out} and pointer at #{@instruction_pointer}\n"
        end
        return io_out
      when STATUS_EXIT
        if LOG_INFO
          STDOUT << "Amplifier stopped\n"
        end
        return io_out
      when STATUS_CAN_CONTINUE
      else
        raise result
      end
    end
  end

  private

  # @param [Integer] index
  # @return [Integer]
  def memory(index)
    @memory[index] || 0
  end

  # @param [Array<Integer>] io_out
  # @return [Symbol] STATUS_EXIT, STATUS_NEED_INPUT or STATUS_CAN_CONTINUE
  def interpret_instruction(io_out)
    instruction = sprintf("%05d", memory(@instruction_pointer))
    opcode = instruction[-2..-1]
    case opcode
    when '01' # add
      print_instruction(instruction, 'add', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} + #{b}) at #{c}")
      @memory[c] = a + b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '02' # multiply
      print_instruction(instruction, 'multiply', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} * #{b}) at #{c}")
      @memory[c] = a * b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '03' # input
      print_instruction(instruction, 'input', 1)
      if @io_in.empty?
        return STATUS_NEED_INPUT
      end
      a = s_value(instruction, 0)
      log_instruction("Store input at #{a}")
      @memory[a] = @io_in.shift
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '04' # output
      print_instruction(instruction, 'output', 1)
      a = value(instruction, 0)
      log_instruction("Output #{a}")
      io_out << a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '05' # jump-if-true
      print_instruction(instruction, 'jump-if-true', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a != 0
        log_instruction("Set instruction pointer to #{b} (#{a} != 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} == 0)")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '06' # jump-if-false
      print_instruction(instruction, 'jump-if-false', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a == 0
        log_instruction("Set instruction pointer to #{b} (#{a} == 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} != #{0})")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '07' # less-than
      print_instruction(instruction, 'less-than', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a < b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} < #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '08' # equals
      print_instruction(instruction, 'equals', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a == b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} == #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '09' # adjust-relative-base
      print_instruction(instruction, 'adjust-relative-base', 1)
      a = value(instruction, 0)
      log_instruction("Adjust relative base #{@relative_base} + #{a}")
      @relative_base += a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '99'
      print_instruction(instruction, 'exit', 0)
      log_instruction("Exit")
      STATUS_EXIT
    else
      raise opcode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def s_value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      parameter_value
    when '1' # position
      parameter_value
    when '2' # relative
      parameter_value + @relative_base
    else
      raise instruction_mode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      memory(parameter_value)
    when '1' # immediate
      parameter_value
    when '2' # relative
      memory(parameter_value + @relative_base)
    else
      raise instruction_mode
    end
  end

  def print_instruction(
      instruction,
      opcode,
      number_of_params)
    if LOG_INFO
      STDOUT << "#{opcode} #{instruction} #{@memory[@instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
    end
  end

  def log_instruction(message)
    if LOG_INFO
      STDOUT << "#{message}\n"
    end
  end
end

COLOR_BLACK = 0
COLOR_WHITE = 1

current_line = 0
current_column = 0
current_direction = :up
hull = Hash.new { |hash, key| hash[key] = {} }
hull[0][0] = COLOR_WHITE

memory = IO.read('input.txt').split(',').map(&:to_i)

amplifier = Amplifier.new(
    memory,
    [])
until amplifier.status == Amplifier::STATUS_EXIT
  out = amplifier.resume([hull[current_line][current_column] || COLOR_BLACK])
  amplifier.status == Amplifier::STATUS_NEED_INPUT
  hull[current_line][current_column] = out[0]
  case out[1]
  when 0
    current_direction = {
        up: :left,
        left: :down,
        down: :right,
        right: :up
    }[current_direction]
  when 1
    current_direction = {
        up: :right,
        right: :down,
        down: :left,
        left: :up
    }[current_direction]
  else
  raise out[1].to_s
  end
  case current_direction
  when :up
    current_line -= 1
  when :down
    current_line += 1
  when :right
    current_column += 1
  when :left
    current_column -= 1
  else
    raise current_direction
  end
end

min_line = hull.keys.min
max_line = hull.keys.max
min_column = hull.values.collect{|v| v.keys.min}.min
max_column = hull.values.collect{|v| v.keys.max}.max

min_line.upto(max_line) do |line|
  min_column.upto(max_column) do |column|
    STDOUT << (hull[line][column] || COLOR_BLACK)
  end
  STDOUT << "\n"
end

Day 12

12_1.rb
MOON_REGEX = /\A<x=(?<x>[-\d]+), y=(?<y>[-\d]+), z=(?<z>[-\d]+)>\z/

def process(content, steps)
  moons = content.split("\n").collect do |l|
    m = MOON_REGEX.match(l)
    {
        pos: [m['x'].to_i, m['y'].to_i, m['z'].to_i],
        vel: [0, 0, 0]
    }
  end
  steps.times do
    p moons
    moons.each_with_index do |from_moon, from_i|
      moons.each_with_index do |to_moon, to_i|
        if from_i != to_i
          0.upto(2) do |coordinate_index|
            if from_moon[:pos][coordinate_index] != to_moon[:pos][coordinate_index]
              delta = (to_moon[:pos][coordinate_index] > from_moon[:pos][coordinate_index]) ? 1 : -1
              from_moon[:vel][coordinate_index] += delta
            end
          end
        end
      end
    end
    moons.each do |moon|
      0.upto(2) do |coordinate_index|
        moon[:pos][coordinate_index] += moon[:vel][coordinate_index]
      end
    end
  end
  p moons
  moons.collect do |moon|
    moon[:pos].collect(&:abs).sum * moon[:vel].collect(&:abs).sum
  end.sum
end
12_2.rb
MOON_REGEX = /\A<x=(?<x>[-\d]+), y=(?<y>[-\d]+), z=(?<z>[-\d]+)>\z/

def process(content)
  moons = content.split("\n").collect do |l|
    m = MOON_REGEX.match(l)
    [
        m['x'].to_i, 0,
        m['y'].to_i, 0,
        m['z'].to_i, 0
    ]
  end
  steps = [
      {},
      {},
      {}
  ]
  steps[0][moons.collect { |m| [m[0], m[1]] }.join('|')] = true
  steps[1][moons.collect { |m| [m[2], m[3]] }.join('|')] = true
  steps[2][moons.collect { |m| [m[4], m[5]] }.join('|')] = true
  cycles = [nil, nil, nil]
  while cycles.include?(nil) do
    moons = moons.collect { |m| m.dup }
    moons.each_with_index do |from_moon, from_i|
      moons.each_with_index do |to_moon, to_i|
        if from_i != to_i
          0.upto(2) do |coordinate_index|
            if from_moon[2 * coordinate_index] != to_moon[2 * coordinate_index]
              delta = (to_moon[2 * coordinate_index] > from_moon[2 * coordinate_index]) ? 1 : -1
              from_moon[(2 * coordinate_index) + 1] += delta
            end
          end
        end
      end
    end
    moons.each do |moon|
      0.upto(2) do |coordinate_index|
        moon[2 * coordinate_index] += moon[(2 * coordinate_index) + 1]
      end
    end
    0.upto(2) do |coordinate_index|
      unless cycles[coordinate_index]
        snapshot = moons.collect { |m| [m[2 * coordinate_index], m[(2 * coordinate_index) + 1]] }.join('|')
        if steps[coordinate_index].key?(snapshot)
          p "Cycle detected for #{coordinate_index}"
          cycles[coordinate_index] = steps[coordinate_index].length
        else
          steps[coordinate_index][snapshot] = true
        end
      end
    end
    # p cycles
  end
  cycles[0].lcm(cycles[1]).lcm(cycles[2])
end

Day 13

13_1.rb
LOG_INFO = false

class Amplifier

  STATUS_EXIT = :exit
  STATUS_NEED_INPUT = :need_input
  STATUS_CAN_CONTINUE = :can_continue

  attr_reader :status

  # @param [Array<Integer>] memory
  # @param [Array<Integer>] io_in
  def initialize(memory, io_in)
    @memory = memory
    @io_in = io_in
    @instruction_pointer = 0
    @relative_base = 0
    @status = :can_continue
  end

  # @param [Array<Integer>] input
  # @return [Array<Integer>|nil]
  def resume(input)
    io_out = []
    @io_in.concat(input)
    if LOG_INFO
      STDOUT << "\nResume amplifier with pointer at #{@instruction_pointer} with input #{@io_in}\n"
    end
    while true
      @status = interpret_instruction(io_out)
      case status
      when STATUS_NEED_INPUT
        if LOG_INFO
          STDOUT << "Input needed, output is #{io_out} and pointer at #{@instruction_pointer}\n"
        end
        return io_out
      when STATUS_EXIT
        if LOG_INFO
          STDOUT << "Amplifier stopped\n"
        end
        return io_out
      when STATUS_CAN_CONTINUE
      else
        raise result
      end
    end
  end

  private

  # @param [Integer] index
  # @return [Integer]
  def memory(index)
    @memory[index] || 0
  end

  # @param [Array<Integer>] io_out
  # @return [Symbol] STATUS_EXIT, STATUS_NEED_INPUT or STATUS_CAN_CONTINUE
  def interpret_instruction(io_out)
    instruction = sprintf("%05d", memory(@instruction_pointer))
    opcode = instruction[-2..-1]
    case opcode
    when '01' # add
      print_instruction(instruction, 'add', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} + #{b}) at #{c}")
      @memory[c] = a + b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '02' # multiply
      print_instruction(instruction, 'multiply', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} * #{b}) at #{c}")
      @memory[c] = a * b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '03' # input
      print_instruction(instruction, 'input', 1)
      if @io_in.empty?
        return STATUS_NEED_INPUT
      end
      a = s_value(instruction, 0)
      log_instruction("Store input at #{a}")
      @memory[a] = @io_in.shift
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '04' # output
      print_instruction(instruction, 'output', 1)
      a = value(instruction, 0)
      log_instruction("Output #{a}")
      io_out << a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '05' # jump-if-true
      print_instruction(instruction, 'jump-if-true', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a != 0
        log_instruction("Set instruction pointer to #{b} (#{a} != 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} == 0)")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '06' # jump-if-false
      print_instruction(instruction, 'jump-if-false', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a == 0
        log_instruction("Set instruction pointer to #{b} (#{a} == 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} != #{0})")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '07' # less-than
      print_instruction(instruction, 'less-than', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a < b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} < #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '08' # equals
      print_instruction(instruction, 'equals', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a == b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} == #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '09' # adjust-relative-base
      print_instruction(instruction, 'adjust-relative-base', 1)
      a = value(instruction, 0)
      log_instruction("Adjust relative base #{@relative_base} + #{a}")
      @relative_base += a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '99'
      print_instruction(instruction, 'exit', 0)
      log_instruction("Exit")
      STATUS_EXIT
    else
      raise opcode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def s_value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      parameter_value
    when '1' # position
      parameter_value
    when '2' # relative
      parameter_value + @relative_base
    else
      raise instruction_mode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      memory(parameter_value)
    when '1' # immediate
      parameter_value
    when '2' # relative
      memory(parameter_value + @relative_base)
    else
      raise instruction_mode
    end
  end

  def print_instruction(
      instruction,
      opcode,
      number_of_params)
    if LOG_INFO
      STDOUT << "#{opcode} #{instruction} #{@memory[@instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
    end
  end

  def log_instruction(message)
    if LOG_INFO
      STDOUT << "#{message}\n"
    end
  end
end

COLOR_BLACK = 0
COLOR_WHITE = 1

current_line = 0
current_column = 0
current_direction = :up
hull = Hash.new { |hash, key| hash[key] = {} }
hull[0][0] = COLOR_WHITE

memory = IO.read('input.txt').split(',').map(&:to_i)

amplifier = Amplifier.new(
    memory,
    [])
until amplifier.status == Amplifier::STATUS_EXIT
  out = amplifier.resume([hull[current_line][current_column] || COLOR_BLACK])
  amplifier.status == Amplifier::STATUS_NEED_INPUT
  hull[current_line][current_column] = out[0]
  case out[1]
  when 0
    current_direction = {
        up: :left,
        left: :down,
        down: :right,
        right: :up
    }[current_direction]
  when 1
    current_direction = {
        up: :right,
        right: :down,
        down: :left,
        left: :up
    }[current_direction]
  else
    raise out[1].to_s
  end
  case current_direction
  when :up
    current_line -= 1
  when :down
    current_line += 1
  when :right
    current_column += 1
  when :left
    current_column -= 1
  else
    raise current_direction
  end
end

screen = []
memory = IO.read('input.txt').split(',').map(&:to_i)

amplifier = Amplifier.new(
    memory,
    [])
output = amplifier.resume([])
0.upto((output.length / 3) - 1) do |i|
  x = output[3 * i]
  y = output[(3 * i) + 1]
  id = output[(3 * i) + 2]
  unless screen[y]
    screen[y] = []
  end
  screen[y][x] = id
end

STDOUT << (screen.collect{|l| l.join('')}.join("\n") + "\n")

p screen.collect{|l| l.count(2)}.sum
13_2.rb
LOG_INFO = false

class Amplifier

  STATUS_EXIT = :exit
  STATUS_NEED_INPUT = :need_input
  STATUS_CAN_CONTINUE = :can_continue

  attr_reader :status

  # @param [Array<Integer>] memory
  # @param [Array<Integer>] io_in
  def initialize(memory, io_in)
    @memory = memory
    @io_in = io_in
    @instruction_pointer = 0
    @relative_base = 0
    @status = :can_continue
  end

  # @param [Array<Integer>] input
  # @return [Array<Integer>|nil]
  def resume(input)
    io_out = []
    @io_in.concat(input)
    if LOG_INFO
      STDOUT << "\nResume amplifier with pointer at #{@instruction_pointer} with input #{@io_in}\n"
    end
    while true
      @status = interpret_instruction(io_out)
      case status
      when STATUS_NEED_INPUT
        if LOG_INFO
          STDOUT << "Input needed, output is #{io_out} and pointer at #{@instruction_pointer}\n"
        end
        return io_out
      when STATUS_EXIT
        if LOG_INFO
          STDOUT << "Amplifier stopped\n"
        end
        return io_out
      when STATUS_CAN_CONTINUE
      else
        raise result
      end
    end
  end

  private

  # @param [Integer] index
  # @return [Integer]
  def memory(index)
    @memory[index] || 0
  end

  # @param [Array<Integer>] io_out
  # @return [Symbol] STATUS_EXIT, STATUS_NEED_INPUT or STATUS_CAN_CONTINUE
  def interpret_instruction(io_out)
    instruction = sprintf("%05d", memory(@instruction_pointer))
    opcode = instruction[-2..-1]
    case opcode
    when '01' # add
      print_instruction(instruction, 'add', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} + #{b}) at #{c}")
      @memory[c] = a + b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '02' # multiply
      print_instruction(instruction, 'multiply', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} * #{b}) at #{c}")
      @memory[c] = a * b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '03' # input
      print_instruction(instruction, 'input', 1)
      if @io_in.empty?
        return STATUS_NEED_INPUT
      end
      a = s_value(instruction, 0)
      log_instruction("Store input at #{a}")
      @memory[a] = @io_in.shift
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '04' # output
      print_instruction(instruction, 'output', 1)
      a = value(instruction, 0)
      log_instruction("Output #{a}")
      io_out << a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '05' # jump-if-true
      print_instruction(instruction, 'jump-if-true', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a != 0
        log_instruction("Set instruction pointer to #{b} (#{a} != 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} == 0)")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '06' # jump-if-false
      print_instruction(instruction, 'jump-if-false', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a == 0
        log_instruction("Set instruction pointer to #{b} (#{a} == 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} != #{0})")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '07' # less-than
      print_instruction(instruction, 'less-than', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a < b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} < #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '08' # equals
      print_instruction(instruction, 'equals', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a == b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} == #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '09' # adjust-relative-base
      print_instruction(instruction, 'adjust-relative-base', 1)
      a = value(instruction, 0)
      log_instruction("Adjust relative base #{@relative_base} + #{a}")
      @relative_base += a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '99'
      print_instruction(instruction, 'exit', 0)
      log_instruction("Exit")
      STATUS_EXIT
    else
      raise opcode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def s_value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      parameter_value
    when '1' # position
      parameter_value
    when '2' # relative
      parameter_value + @relative_base
    else
      raise instruction_mode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      memory(parameter_value)
    when '1' # immediate
      parameter_value
    when '2' # relative
      memory(parameter_value + @relative_base)
    else
      raise instruction_mode
    end
  end

  def print_instruction(
      instruction,
      opcode,
      number_of_params)
    if LOG_INFO
      STDOUT << "#{opcode} #{instruction} #{@memory[@instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
    end
  end

  def log_instruction(message)
    if LOG_INFO
      STDOUT << "#{message}\n"
    end
  end
end

COLOR_BLACK = 0
COLOR_WHITE = 1

current_line = 0
current_column = 0
current_direction = :up
hull = Hash.new { |hash, key| hash[key] = {} }
hull[0][0] = COLOR_WHITE

memory = IO.read('input.txt').split(',').map(&:to_i)

amplifier = Amplifier.new(
    memory,
    [])
until amplifier.status == Amplifier::STATUS_EXIT
  out = amplifier.resume([hull[current_line][current_column] || COLOR_BLACK])
  amplifier.status == Amplifier::STATUS_NEED_INPUT
  hull[current_line][current_column] = out[0]
  case out[1]
  when 0
    current_direction = {
        up: :left,
        left: :down,
        down: :right,
        right: :up
    }[current_direction]
  when 1
    current_direction = {
        up: :right,
        right: :down,
        down: :left,
        left: :up
    }[current_direction]
  else
    raise out[1].to_s
  end
  case current_direction
  when :up
    current_line -= 1
  when :down
    current_line += 1
  when :right
    current_column += 1
  when :left
    current_column -= 1
  else
    raise current_direction
  end
end

def read_char
  begin
    system("stty raw -echo")
    str = STDIN.getc
  ensure
    system("stty -raw echo")
  end
  str
end

def print_info(screen, score)
  0.upto((output.length / 3) - 1) do |i|
    x = output[3 * i]
    y = output[(3 * i) + 1]
    id = output[(3 * i) + 2]
    if (x == -1) && (y == 0)
      score = id
    else
      unless screen[y]
        screen[y] = []
      end
      screen[y][x] = id
    end
  end

  STDOUT << "#{screen.collect { |l| l.join('') }.join("\n")}\n#{score}\n"
end

memory = IO.read('input.txt').split(',').map(&:to_i)
memory[0] = 2
amplifier = Amplifier.new(
    memory,
    [])

screen = []
current_score = -1
max_score = -1

next_direction = 0
while true
  output = amplifier.resume([next_direction])
  0.upto((output.length / 3) - 1) do |i|
    x = output[3 * i]
    y = output[(3 * i) + 1]
    id = output[(3 * i) + 2]
    if (x == -1) && (y == 0)
      current_score = id
      if current_score > max_score
        max_score = current_score
      end
    else
      unless screen[y]
        screen[y] = []
      end
      screen[y][x] = id
    end
  end
  STDOUT << "\e[H\e[2J"
  STDOUT << "#{screen.collect { |l| l.join('').gsub('0', ' ') }.join("\n")}\n\n#{current_score}\n#{max_score}"
  ball_column = screen.find { |l| l.include?(4) }.index(4)
  paddle_column = screen.find { |l| l.include?(3) }.index(3)
  if ball_column < paddle_column
    next_direction = -1
  elsif paddle_column < ball_column
    next_direction = 1
  else
    next_direction = 0
  end
  sleep(0.01)
end

Day 14

14_1.rb
ELEMENT_REGEX = /\A\s*(?<quantity>\d+) (?<element>[A-Z]+)\s*\z/

def parse_element(s)
  m = ELEMENT_REGEX.match(s)
  {
      element: m['element'],
      quantity: m['quantity'].to_i
  }
end

FUEL = 'FUEL'
ORE = 'ORE'

def find_rank(element, recipes)
  recipe = recipes[element]
  recipe[:rank] = (recipe[:requirements].keys.collect do |required_element|
    if required_element == ORE
      0
    else
      find_rank(required_element, recipes)
    end
  end.max + 1)
end

# @param [String] input_recipes
# @return [Integer]
def process(input_recipes)
  p ''
  recipes = {}
  input_recipes.split("\n").each do |input_recipe|
    parts = input_recipe.split('=>')
    result = parse_element(parts[1])
    requirements_array = parts[0].split(',').collect { |e| parse_element(e) }
    requirements_hash = {}
    requirements_array.each do |r|
      requirements_hash[r[:element]] = r[:quantity]
    end

    recipes[result[:element]] = {
        quantity: result[:quantity],
        requirements: requirements_hash
    }
  end

  recipes.keys.each do |element|
    find_rank(element, recipes)
  end
  p recipes

  elements_requirements = recipes.delete(FUEL)[:requirements]
  ore_requirements = 0
  until elements_requirements.empty?
    p "Currently #{elements_requirements.length} requirements #{elements_requirements}"

    required_element = elements_requirements.keys.sort_by { |element| recipes[element][:rank] }.last
    required_quantity = elements_requirements.delete(required_element)

    p "Need #{required_quantity} of #{required_element}"
    recipe = recipes.delete(required_element)
    p "Recipe #{recipe}"
    recipe_quantity = (required_quantity.to_f / recipe[:quantity].to_f).ceil
    p "Recipes will be run #{recipe_quantity} times"

    recipe[:requirements].each_pair do |el, quant|
      required_quant = recipe_quantity * quant
      p "Require #{required_quant} of #{el}"
      if el == ORE
        ore_requirements += required_quant
      else
        elements_requirements[el] = (elements_requirements[el] || 0) + required_quant
      end
    end
  end
  ore_requirements
end
14_2.rb
ELEMENT_REGEX = /\A\s*(?<quantity>\d+) (?<element>[A-Z]+)\s*\z/

def parse_element(s)
  m = ELEMENT_REGEX.match(s)
  {
      element: m['element'],
      quantity: m['quantity'].to_i
  }
end

FUEL = 'FUEL'
ORE = 'ORE'

def find_rank(element, recipes)
  recipe = recipes[element]
  recipe[:rank] = (recipe[:requirements].keys.collect do |required_element|
    if required_element == ORE
      0
    else
      find_rank(required_element, recipes)
    end
  end.max + 1)
end

# @param [String] input_recipes
def prepare_recipes(input_recipes)
  recipes = {}
  input_recipes.split("\n").each do |input_recipe|
    parts = input_recipe.split('=>')
    result = parse_element(parts[1])
    requirements_array = parts[0].split(',').collect { |e| parse_element(e) }
    requirements_hash = {}
    requirements_array.each do |r|
      requirements_hash[r[:element]] = r[:quantity]
    end

    recipes[result[:element]] = {
        quantity: result[:quantity],
        requirements: requirements_hash
    }
  end

  recipes.keys.each do |element|
    find_rank(element, recipes)
  end
  recipes
end

# @return [Integer]
def process(recipes, number_of_fuel)
  elements_requirements = {}
  recipes[FUEL][:requirements].each_pair do |k, v|
    elements_requirements[k] = v * number_of_fuel
  end
  ore_requirements = 0
  until elements_requirements.empty?
    required_element = elements_requirements.keys.sort_by { |element| recipes[element][:rank] }.last
    required_quantity = elements_requirements.delete(required_element)

    recipe = recipes[required_element]
    recipe_quantity = (required_quantity.to_f / recipe[:quantity].to_f).ceil

    recipe[:requirements].each_pair do |el, quant|
      required_quant = recipe_quantity * quant
      if el == ORE
        ore_requirements += required_quant
      else
        elements_requirements[el] = (elements_requirements[el] || 0) + required_quant
      end
    end
  end
  ore_requirements
end

TOTAL_ORE = 1000000000000

def process2(input_recipes)
  recipes = prepare_recipes(input_recipes)
  current_max = 1
  current_min = current_max
  while process(recipes, current_max) <= TOTAL_ORE
    current_min = current_max
    current_max = current_max * 2
  end

  p '!'
  p current_max

  while current_max != (current_min + 1)
    new_value = (current_min + current_max) / 2
    p new_value
    ore = process(recipes, new_value)
    if ore < TOTAL_ORE
      current_min = new_value
      p "Under"
    else
      current_max = new_value
      p "Above"
    end
  end
  current_min
end

Day 15

15_1.rb
LOG_INFO = false

class Amplifier

  STATUS_EXIT = :exit
  STATUS_NEED_INPUT = :need_input
  STATUS_CAN_CONTINUE = :can_continue

  attr_reader :status

  # @param [Array<Integer>] memory
  # @param [Array<Integer>] io_in
  def initialize(memory, io_in)
    @memory = memory
    @io_in = io_in
    @instruction_pointer = 0
    @relative_base = 0
    @status = :can_continue
  end

  # @param [Array<Integer>] input
  # @return [Array<Integer>|nil]
  def resume(input)
    io_out = []
    @io_in.concat(input)
    if LOG_INFO
      STDOUT << "\nResume amplifier with pointer at #{@instruction_pointer} with input #{@io_in}\n"
    end
    while true
      @status = interpret_instruction(io_out)
      case status
      when STATUS_NEED_INPUT
        if LOG_INFO
          STDOUT << "Input needed, output is #{io_out} and pointer at #{@instruction_pointer}\n"
        end
        return io_out
      when STATUS_EXIT
        if LOG_INFO
          STDOUT << "Amplifier stopped\n"
        end
        return io_out
      when STATUS_CAN_CONTINUE
      else
        raise result
      end
    end
  end

  private

  # @param [Integer] index
  # @return [Integer]
  def memory(index)
    @memory[index] || 0
  end

  # @param [Array<Integer>] io_out
  # @return [Symbol] STATUS_EXIT, STATUS_NEED_INPUT or STATUS_CAN_CONTINUE
  def interpret_instruction(io_out)
    instruction = sprintf("%05d", memory(@instruction_pointer))
    opcode = instruction[-2..-1]
    case opcode
    when '01' # add
      print_instruction(instruction, 'add', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} + #{b}) at #{c}")
      @memory[c] = a + b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '02' # multiply
      print_instruction(instruction, 'multiply', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} * #{b}) at #{c}")
      @memory[c] = a * b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '03' # input
      print_instruction(instruction, 'input', 1)
      if @io_in.empty?
        return STATUS_NEED_INPUT
      end
      a = s_value(instruction, 0)
      log_instruction("Store input at #{a}")
      @memory[a] = @io_in.shift
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '04' # output
      print_instruction(instruction, 'output', 1)
      a = value(instruction, 0)
      log_instruction("Output #{a}")
      io_out << a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '05' # jump-if-true
      print_instruction(instruction, 'jump-if-true', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a != 0
        log_instruction("Set instruction pointer to #{b} (#{a} != 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} == 0)")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '06' # jump-if-false
      print_instruction(instruction, 'jump-if-false', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a == 0
        log_instruction("Set instruction pointer to #{b} (#{a} == 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} != #{0})")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '07' # less-than
      print_instruction(instruction, 'less-than', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a < b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} < #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '08' # equals
      print_instruction(instruction, 'equals', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a == b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} == #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '09' # adjust-relative-base
      print_instruction(instruction, 'adjust-relative-base', 1)
      a = value(instruction, 0)
      log_instruction("Adjust relative base #{@relative_base} + #{a}")
      @relative_base += a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '99'
      print_instruction(instruction, 'exit', 0)
      log_instruction("Exit")
      STATUS_EXIT
    else
      raise opcode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def s_value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      parameter_value
    when '1' # position
      parameter_value
    when '2' # relative
      parameter_value + @relative_base
    else
      raise instruction_mode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      memory(parameter_value)
    when '1' # immediate
      parameter_value
    when '2' # relative
      memory(parameter_value + @relative_base)
    else
      raise instruction_mode
    end
  end

  def print_instruction(
      instruction,
      opcode,
      number_of_params)
    if LOG_INFO
      STDOUT << "#{opcode} #{instruction} #{@memory[@instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
    end
  end

  def log_instruction(message)
    if LOG_INFO
      STDOUT << "#{message}\n"
    end
  end
end

MOVEMENT_NORTH = 1
MOVEMENT_SOUTH = 2
MOVEMENT_EAST = 3
MOVEMENT_WEST = 4

RESULT_WALL = 0
RESULT_MOVED = 1
RESULT_FOUND = 2

memory = IO.read('input.txt').split(',').map(&:to_i)
places_to_try = []
map = Hash.new { |hash, key| hash[key] = {} }

places_to_try << {
    path: [MOVEMENT_NORTH],
    target_line: -1,
    target_column: 0
}
places_to_try << {
    path: [MOVEMENT_SOUTH],
    target_line: 1,
    target_column: 0
}
places_to_try << {
    path: [MOVEMENT_EAST],
    target_line: 0,
    target_column: 1
}
places_to_try << {
    path: [MOVEMENT_WEST],
    target_line: 0,
    target_column: -1
}

def draw_map(map)
  min_line = map.keys.min
  max_line = map.keys.max
  min_column = map.values.collect{|v| v.keys.min}.compact.min
  max_column = map.values.collect{|v| v.keys.max}.compact.max

  min_line.upto(max_line) do |line|
    min_column.upto(max_column) do |column|
      STDOUT << (map[line][column] || ' ')
    end
    STDOUT << "\n"
  end
end

until places_to_try.empty? do
  place_to_try = places_to_try.shift
  p place_to_try
  unless map[place_to_try[:target_line]].key?(place_to_try[:target_column])
    amplifier = Amplifier.new(memory.dup, place_to_try[:path].dup)
    output = amplifier.resume([])
    p output
    status = output.last
    map[place_to_try[:target_line]][place_to_try[:target_column]] = status
    draw_map(map)
    case status
    when RESULT_FOUND
      p "#{place_to_try[:path].length} #{place_to_try[:path]}"
      exit
    when RESULT_MOVED
      [
          {direction: MOVEMENT_NORTH, delta_line: -1, delta_column: 0},
          {direction: MOVEMENT_SOUTH, delta_line: 1, delta_column: 0},
          {direction: MOVEMENT_EAST, delta_line: 0, delta_column: 1},
          {direction: MOVEMENT_WEST, delta_line: 0, delta_column: -1}
      ].each do |possible_move|
        target_line = place_to_try[:target_line] + possible_move[:delta_line]
        target_column = place_to_try[:target_column] + possible_move[:delta_column]
        unless map[target_line].key?(target_column)
          p "Will try (#{target_line}, #{target_column})"
          places_to_try << {
              path:  place_to_try[:path] + [possible_move[:direction]],
              target_line: target_line,
              target_column: target_column
          }
        end
      end
    end
  end
end
15_2.rb
LOG_INFO = false

class Amplifier

  STATUS_EXIT = :exit
  STATUS_NEED_INPUT = :need_input
  STATUS_CAN_CONTINUE = :can_continue

  attr_reader :status

  # @param [Array<Integer>] memory
  # @param [Array<Integer>] io_in
  def initialize(memory, io_in, instruction_pointer = 0, relative_base = 0, status = :can_continue)
    @memory = memory
    @io_in = io_in
    @instruction_pointer = instruction_pointer
    @relative_base = relative_base
    @status = status
  end

  def dup
    Amplifier.new(
        @memory.dup,
        @io_in.dup,
        @instruction_pointer,
        @relative_base,
        @status
    )
  end

  # @param [Array<Integer>] input
  # @return [Array<Integer>|nil]
  def resume(input)
    io_out = []
    @io_in.concat(input)
    if LOG_INFO
      STDOUT << "\nResume amplifier with pointer at #{@instruction_pointer} with input #{@io_in}\n"
    end
    while true
      @status = interpret_instruction(io_out)
      case status
      when STATUS_NEED_INPUT
        if LOG_INFO
          STDOUT << "Input needed, output is #{io_out} and pointer at #{@instruction_pointer}\n"
        end
        return io_out
      when STATUS_EXIT
        if LOG_INFO
          STDOUT << "Amplifier stopped\n"
        end
        return io_out
      when STATUS_CAN_CONTINUE
      else
        raise result
      end
    end
  end

  private

  # @param [Integer] index
  # @return [Integer]
  def memory(index)
    @memory[index] || 0
  end

  # @param [Array<Integer>] io_out
  # @return [Symbol] STATUS_EXIT, STATUS_NEED_INPUT or STATUS_CAN_CONTINUE
  def interpret_instruction(io_out)
    instruction = sprintf("%05d", memory(@instruction_pointer))
    opcode = instruction[-2..-1]
    case opcode
    when '01' # add
      print_instruction(instruction, 'add', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} + #{b}) at #{c}")
      @memory[c] = a + b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '02' # multiply
      print_instruction(instruction, 'multiply', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} * #{b}) at #{c}")
      @memory[c] = a * b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '03' # input
      print_instruction(instruction, 'input', 1)
      if @io_in.empty?
        return STATUS_NEED_INPUT
      end
      a = s_value(instruction, 0)
      log_instruction("Store input at #{a}")
      @memory[a] = @io_in.shift
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '04' # output
      print_instruction(instruction, 'output', 1)
      a = value(instruction, 0)
      log_instruction("Output #{a}")
      io_out << a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '05' # jump-if-true
      print_instruction(instruction, 'jump-if-true', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a != 0
        log_instruction("Set instruction pointer to #{b} (#{a} != 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} == 0)")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '06' # jump-if-false
      print_instruction(instruction, 'jump-if-false', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a == 0
        log_instruction("Set instruction pointer to #{b} (#{a} == 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} != #{0})")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '07' # less-than
      print_instruction(instruction, 'less-than', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a < b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} < #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '08' # equals
      print_instruction(instruction, 'equals', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a == b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} == #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '09' # adjust-relative-base
      print_instruction(instruction, 'adjust-relative-base', 1)
      a = value(instruction, 0)
      log_instruction("Adjust relative base #{@relative_base} + #{a}")
      @relative_base += a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '99'
      print_instruction(instruction, 'exit', 0)
      log_instruction("Exit")
      STATUS_EXIT
    else
      raise opcode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def s_value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      parameter_value
    when '1' # position
      parameter_value
    when '2' # relative
      parameter_value + @relative_base
    else
      raise instruction_mode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      memory(parameter_value)
    when '1' # immediate
      parameter_value
    when '2' # relative
      memory(parameter_value + @relative_base)
    else
      raise instruction_mode
    end
  end

  def print_instruction(
      instruction,
      opcode,
      number_of_params)
    if LOG_INFO
      STDOUT << "#{opcode} #{instruction} #{@memory[@instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
    end
  end

  def log_instruction(message)
    if LOG_INFO
      STDOUT << "#{message}\n"
    end
  end
end

MOVEMENT_NORTH = 1
MOVEMENT_SOUTH = 2
MOVEMENT_EAST = 3
MOVEMENT_WEST = 4

RESULT_WALL = 0
RESULT_MOVED = 1
RESULT_FOUND = 2

memory = IO.read('input.txt').split(',').map(&:to_i)
places_to_try = []
map = Hash.new { |hash, key| hash[key] = {} }


initial_amplifier = Amplifier.new(memory.dup, [])
places_to_try << {
    path: [MOVEMENT_NORTH],
    target_line: -1,
    target_column: 0,
    amplifier: initial_amplifier
}
places_to_try << {
    path: [MOVEMENT_SOUTH],
    target_line: 1,
    target_column: 0,
    amplifier: initial_amplifier
}
places_to_try << {
    path: [MOVEMENT_EAST],
    target_line: 0,
    target_column: 1,
    amplifier: initial_amplifier
}
places_to_try << {
    path: [MOVEMENT_WEST],
    target_line: 0,
    target_column: -1,
    amplifier: initial_amplifier
}

def draw_map(map)
  min_line = map.keys.min
  max_line = map.keys.max
  min_column = map.values.collect { |v| v.keys.min }.compact.min
  max_column = map.values.collect { |v| v.keys.max }.compact.max

  min_line.upto(max_line) do |line|
    min_column.upto(max_column) do |column|
      if map[line].key?(column)
        v = map[line][column]
        if v < 0
          STDOUT << 'O'
        else
          STDOUT << ['#', '.', '2'][v]
        end
      else
        STDOUT << '#'
      end
    end
    STDOUT << "\n"
  end
end

max_path = 0

oxygen_system_line = nil
oxygen_system_column = nil

until places_to_try.empty? do
  place_to_try = places_to_try.shift
  unless map[place_to_try[:target_line]].key?(place_to_try[:target_column])
    amplifier = place_to_try[:amplifier].dup
    output = amplifier.resume([place_to_try[:path].last])
    status = output.last
    map[place_to_try[:target_line]][place_to_try[:target_column]] = status
    case status
    when RESULT_WALL
    else
      if (status == RESULT_FOUND)
        oxygen_system_line = place_to_try[:target_line]
        oxygen_system_column = place_to_try[:target_column]
      end
      if place_to_try[:path].length > max_path
        max_path = place_to_try[:path].length
      end
      [
          {direction: MOVEMENT_NORTH, delta_line: -1, delta_column: 0},
          {direction: MOVEMENT_SOUTH, delta_line: 1, delta_column: 0},
          {direction: MOVEMENT_EAST, delta_line: 0, delta_column: 1},
          {direction: MOVEMENT_WEST, delta_line: 0, delta_column: -1}
      ].each do |possible_move|
        target_line = place_to_try[:target_line] + possible_move[:delta_line]
        target_column = place_to_try[:target_column] + possible_move[:delta_column]
        unless map[target_line].key?(target_column)
          places_to_try << {
              path: place_to_try[:path] + [possible_move[:direction]],
              target_line: target_line,
              target_column: target_column,
              amplifier: amplifier
          }
        end
      end
    end
  end
end

draw_map(map)

map[oxygen_system_line][oxygen_system_column] = -1
draw_map(map)
number_of_minutes = 0
while true
  number_of_minutes += 1
  at_least_one_location_filled = false
  map.each_pair do |target_line, line_locations|
    line_locations.each do |target_column, location_value|
      if location_value == RESULT_MOVED
        if ((map[target_line - 1][target_column] == - number_of_minutes) ||
            (map[target_line + 1][target_column] == - number_of_minutes) ||
            (map[target_line][target_column - 1] == - number_of_minutes) ||
            (map[target_line][target_column + 1] == - number_of_minutes)
        )
          map[target_line][target_column] = - number_of_minutes - 1
          at_least_one_location_filled = true
        end
      end
    end
  end
  p ""
  p number_of_minutes
  draw_map(map)
  unless at_least_one_location_filled
    p number_of_minutes - 1
    exit
  end
end

Day 16

16_1.rb
BASE_PATTERN = [0, 1, 0, -1]

def calculate_factor(current_index, target_index)
  # For the element at index n, the pattern length is 4 * (target_index + 1)
  pattern_size = 4 * (current_index + 1)
  # Detect if we are on the first pattern instance (because it's shorter by one)
  if target_index < pattern_size
    # We're on the first pattern instance
    index_on_pattern = (target_index + 1) % pattern_size
  else
    # We're not on the first pattern instance
    rebased_index = target_index - (pattern_size - 1)
    index_on_pattern = rebased_index % pattern_size
  end
  BASE_PATTERN[index_on_pattern / (current_index + 1)]
end

# @param [Integer] number_of_phases
# @param [Array<Integer>] input
# @return [Array<Integer>]
def calculate(input, number_of_phases)
  number_of_phases.times do
    output = Array.new(input.length, nil)
    0.upto(input.length - 1) do |current_index|
      current_output_value = 0
      0.upto(input.length - 1) do |target_index|
        factor = calculate_factor(current_index, target_index)
        current_input_value = input[target_index]
        current_output_value += factor * current_input_value
      end
      output[current_index] = current_output_value.abs % 10
    end
    input = output
  end
  input
end
16_2.rb
BASE_PATTERN = [0, 1, 0, -1]

def calculate_factor(current_index, target_index)
  # For the element at index n, the pattern length is 4 * (target_index + 1)
  pattern_size = 4 * (current_index + 1)
  # Detect if we are on the first pattern instance (because it's shorter by one)
  if target_index < pattern_size
    # We're on the first pattern instance
    index_on_pattern = (target_index + 1) % pattern_size
  else
    # We're not on the first pattern instance
    rebased_index = target_index - (pattern_size - 1)
    index_on_pattern = rebased_index % pattern_size
  end
  BASE_PATTERN[index_on_pattern / (current_index + 1)]
end

# @param [String] s_input
# @return [String]
def calculate(s_input)
  delta = s_input[0, 7].to_i
  input = (s_input.split('').map(&:to_i) * 10000)[delta..-1]
  100.times do
    value = 0
    (input.length - 1).downto(0) do |index|
      value += input[index]
      value = value.abs % 10
      input[index] = value
    end
  end
  input[0, 8].join('')
end

Day 17

17_1.rb
LOG_INFO = false

class Amplifier

  STATUS_EXIT = :exit
  STATUS_NEED_INPUT = :need_input
  STATUS_CAN_CONTINUE = :can_continue

  attr_reader :status

  # @param [Array<Integer>] memory
  # @param [Array<Integer>] io_in
  def initialize(memory, io_in, instruction_pointer = 0, relative_base = 0, status = :can_continue)
    @memory = memory
    @io_in = io_in
    @instruction_pointer = instruction_pointer
    @relative_base = relative_base
    @status = status
  end

  def dup
    Amplifier.new(
        @memory.dup,
        @io_in.dup,
        @instruction_pointer,
        @relative_base,
        @status
    )
  end

  # @param [Array<Integer>] input
  # @return [Array<Integer>|nil]
  def resume(input)
    io_out = []
    @io_in.concat(input)
    if LOG_INFO
      STDOUT << "\nResume amplifier with pointer at #{@instruction_pointer} with input #{@io_in}\n"
    end
    while true
      @status = interpret_instruction(io_out)
      case status
      when STATUS_NEED_INPUT
        if LOG_INFO
          STDOUT << "Input needed, output is #{io_out} and pointer at #{@instruction_pointer}\n"
        end
        return io_out
      when STATUS_EXIT
        if LOG_INFO
          STDOUT << "Amplifier stopped\n"
        end
        return io_out
      when STATUS_CAN_CONTINUE
      else
        raise result
      end
    end
  end

  private

  # @param [Integer] index
  # @return [Integer]
  def memory(index)
    @memory[index] || 0
  end

  # @param [Array<Integer>] io_out
  # @return [Symbol] STATUS_EXIT, STATUS_NEED_INPUT or STATUS_CAN_CONTINUE
  def interpret_instruction(io_out)
    instruction = sprintf("%05d", memory(@instruction_pointer))
    opcode = instruction[-2..-1]
    case opcode
    when '01' # add
      print_instruction(instruction, 'add', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} + #{b}) at #{c}")
      @memory[c] = a + b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '02' # multiply
      print_instruction(instruction, 'multiply', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} * #{b}) at #{c}")
      @memory[c] = a * b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '03' # input
      print_instruction(instruction, 'input', 1)
      if @io_in.empty?
        return STATUS_NEED_INPUT
      end
      a = s_value(instruction, 0)
      log_instruction("Store input at #{a}")
      @memory[a] = @io_in.shift
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '04' # output
      print_instruction(instruction, 'output', 1)
      a = value(instruction, 0)
      log_instruction("Output #{a}")
      io_out << a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '05' # jump-if-true
      print_instruction(instruction, 'jump-if-true', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a != 0
        log_instruction("Set instruction pointer to #{b} (#{a} != 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} == 0)")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '06' # jump-if-false
      print_instruction(instruction, 'jump-if-false', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a == 0
        log_instruction("Set instruction pointer to #{b} (#{a} == 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} != #{0})")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '07' # less-than
      print_instruction(instruction, 'less-than', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a < b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} < #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '08' # equals
      print_instruction(instruction, 'equals', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a == b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} == #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '09' # adjust-relative-base
      print_instruction(instruction, 'adjust-relative-base', 1)
      a = value(instruction, 0)
      log_instruction("Adjust relative base #{@relative_base} + #{a}")
      @relative_base += a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '99'
      print_instruction(instruction, 'exit', 0)
      log_instruction("Exit")
      STATUS_EXIT
    else
      raise opcode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def s_value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      parameter_value
    when '1' # position
      parameter_value
    when '2' # relative
      parameter_value + @relative_base
    else
      raise instruction_mode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      memory(parameter_value)
    when '1' # immediate
      parameter_value
    when '2' # relative
      memory(parameter_value + @relative_base)
    else
      raise instruction_mode
    end
  end

  def print_instruction(
      instruction,
      opcode,
      number_of_params)
    if LOG_INFO
      STDOUT << "#{opcode} #{instruction} #{@memory[@instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
    end
  end

  def log_instruction(message)
    if LOG_INFO
      STDOUT << "#{message}\n"
    end
  end
end

amplifier = Amplifier.new(IO.read('input.txt').split(',').map(&:to_i), [])
output = amplifier.resume([])
map = output.map(&:chr).join("").split("\n")
map_width = map.first.length
map_height = map.length

total = 0
1.upto(map_width - 2) do |column|
  1.upto(map_height - 2) do |line|
    if (map[line][column] == '#') &&
        (map[line - 1][column] == '#') &&
        (map[line + 1][column] == '#') &&
        (map[line][column - 1] == '#') &&
        (map[line][column + 1] == '#')
      p "#{line}, #{column} is an intersection"
      total += (line * column)
    end
  end
end
p total
17_2.rb
LOG_INFO = false

class Amplifier

  STATUS_EXIT = :exit
  STATUS_NEED_INPUT = :need_input
  STATUS_CAN_CONTINUE = :can_continue

  attr_reader :status

  # @param [Array<Integer>] memory
  # @param [Array<Integer>] io_in
  def initialize(memory, io_in, instruction_pointer = 0, relative_base = 0, status = :can_continue)
    @memory = memory
    @io_in = io_in
    @instruction_pointer = instruction_pointer
    @relative_base = relative_base
    @status = status
  end

  def dup
    Amplifier.new(
        @memory.dup,
        @io_in.dup,
        @instruction_pointer,
        @relative_base,
        @status
    )
  end

  # @param [Array<Integer>] input
  # @return [Array<Integer>|nil]
  def resume(input)
    io_out = []
    @io_in.concat(input)
    if LOG_INFO
      STDOUT << "\nResume amplifier with pointer at #{@instruction_pointer} with input #{@io_in}\n"
    end
    while true
      @status = interpret_instruction(io_out)
      case status
      when STATUS_NEED_INPUT
        if LOG_INFO
          STDOUT << "Input needed, output is #{io_out} and pointer at #{@instruction_pointer}\n"
        end
        return io_out
      when STATUS_EXIT
        if LOG_INFO
          STDOUT << "Amplifier stopped\n"
        end
        return io_out
      when STATUS_CAN_CONTINUE
      else
        raise result
      end
    end
  end

  private

  # @param [Integer] index
  # @return [Integer]
  def memory(index)
    @memory[index] || 0
  end

  # @param [Array<Integer>] io_out
  # @return [Symbol] STATUS_EXIT, STATUS_NEED_INPUT or STATUS_CAN_CONTINUE
  def interpret_instruction(io_out)
    instruction = sprintf("%05d", memory(@instruction_pointer))
    opcode = instruction[-2..-1]
    case opcode
    when '01' # add
      print_instruction(instruction, 'add', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} + #{b}) at #{c}")
      @memory[c] = a + b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '02' # multiply
      print_instruction(instruction, 'multiply', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      log_instruction("Store (#{a} * #{b}) at #{c}")
      @memory[c] = a * b
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '03' # input
      print_instruction(instruction, 'input', 1)
      if @io_in.empty?
        return STATUS_NEED_INPUT
      end
      a = s_value(instruction, 0)
      log_instruction("Store input at #{a}")
      @memory[a] = @io_in.shift
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '04' # output
      print_instruction(instruction, 'output', 1)
      a = value(instruction, 0)
      log_instruction("Output #{a}")
      io_out << a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '05' # jump-if-true
      print_instruction(instruction, 'jump-if-true', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a != 0
        log_instruction("Set instruction pointer to #{b} (#{a} != 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} == 0)")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '06' # jump-if-false
      print_instruction(instruction, 'jump-if-false', 2)
      a = value(instruction, 0)
      b = value(instruction, 1)
      if a == 0
        log_instruction("Set instruction pointer to #{b} (#{a} == 0)")
        @instruction_pointer = b
      else
        log_instruction("Do nothing (#{a} != #{0})")
        @instruction_pointer += 3
      end
      STATUS_CAN_CONTINUE
    when '07' # less-than
      print_instruction(instruction, 'less-than', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a < b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} < #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '08' # equals
      print_instruction(instruction, 'equals', 3)
      a = value(instruction, 0)
      b = value(instruction, 1)
      c = s_value(instruction, 2)
      value = (a == b) ? 1 : 0
      log_instruction("Set #{value} at #{c} (#{a} == #{b})")
      @memory[c] = value
      @instruction_pointer += 4
      STATUS_CAN_CONTINUE
    when '09' # adjust-relative-base
      print_instruction(instruction, 'adjust-relative-base', 1)
      a = value(instruction, 0)
      log_instruction("Adjust relative base #{@relative_base} + #{a}")
      @relative_base += a
      @instruction_pointer += 2
      STATUS_CAN_CONTINUE
    when '99'
      print_instruction(instruction, 'exit', 0)
      log_instruction("Exit")
      STATUS_EXIT
    else
      raise opcode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def s_value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      parameter_value
    when '1' # position
      parameter_value
    when '2' # relative
      parameter_value + @relative_base
    else
      raise instruction_mode
    end
  end

  # @param [String] instruction
  # @param [Integer] parameter_index
  # @return [Integer]
  def value(instruction, parameter_index)
    instruction_mode = instruction[-(3 + parameter_index)]
    parameter_value = memory(@instruction_pointer + parameter_index + 1)
    case instruction_mode
    when '0' # position
      memory(parameter_value)
    when '1' # immediate
      parameter_value
    when '2' # relative
      memory(parameter_value + @relative_base)
    else
      raise instruction_mode
    end
  end

  def print_instruction(
      instruction,
      opcode,
      number_of_params)
    if LOG_INFO
      STDOUT << "#{opcode} #{instruction} #{@memory[@instruction_pointer + 1, number_of_params].join(', ')}".ljust(40)
    end
  end

  def log_instruction(message)
    if LOG_INFO
      STDOUT << "#{message}\n"
    end
  end
end

amplifier = Amplifier.new(IO.read('input.txt').split(',').map(&:to_i), [])
output = amplifier.resume([])
map = output.map(&:chr).join("").split("\n")
map_width = map.first.length
map_height = map.length

number_of_intersections = 0
1.upto(map_width - 2) do |column|
  1.upto(map_height - 2) do |line|
    if (map[line][column] == '#') &&
        (map[line - 1][column] == '#') &&
        (map[line + 1][column] == '#') &&
        (map[line][column - 1] == '#') &&
        (map[line][column + 1] == '#')
      p "#{line}, #{column} is an intersection"
      # map[line][column] = 'x'
      number_of_intersections += 0
    end
  end
end

robot_line = map.index { |l| l.include?('^') }
robot_column = map[robot_line].index('^')

number_of_standard_scaffolds = map.collect { |l| l.count('#') }.sum - number_of_intersections

DIRECTION_NORTH = 'N'
DIRECTION_SOUTH = 'S'
DIRECTION_EAST = 'E'
DIRECTION_WEST = 'W'

DELTA_LINE = {
    DIRECTION_NORTH => -1,
    DIRECTION_SOUTH => 1,
    DIRECTION_EAST => 0,
    DIRECTION_WEST => 0,
}
DELTA_COLUMN = {
    DIRECTION_NORTH => 0,
    DIRECTION_SOUTH => 0,
    DIRECTION_EAST => 1,
    DIRECTION_WEST => -1,
}

DELTA_LINE_TURNING_RIGHT = {
    DIRECTION_NORTH => 0,
    DIRECTION_SOUTH => 0,
    DIRECTION_EAST => 1,
    DIRECTION_WEST => -1,
}
DELTA_COLUMN_TURNING_RIGHT = {
    DIRECTION_NORTH => 1,
    DIRECTION_SOUTH => -1,
    DIRECTION_EAST => 0,
    DIRECTION_WEST => 0,
}
DELTA_LINE_TURNING_LEFT = {
    DIRECTION_NORTH => 0,
    DIRECTION_SOUTH => 0,
    DIRECTION_EAST => -1,
    DIRECTION_WEST => 1,
}
DELTA_COLUMN_TURNING_LEFT = {
    DIRECTION_NORTH => -1,
    DIRECTION_SOUTH => 1,
    DIRECTION_EAST => 0,
    DIRECTION_WEST => 0,
}

DIRECTION_TURNING_LEFT = {
    DIRECTION_NORTH => DIRECTION_WEST,
    DIRECTION_SOUTH => DIRECTION_EAST,
    DIRECTION_EAST => DIRECTION_NORTH,
    DIRECTION_WEST => DIRECTION_SOUTH,
}
DIRECTION_TURNING_RIGHT = {
    DIRECTION_NORTH => DIRECTION_EAST,
    DIRECTION_SOUTH => DIRECTION_WEST,
    DIRECTION_EAST => DIRECTION_SOUTH,
    DIRECTION_WEST => DIRECTION_NORTH,
}

def can_go_on?(line, column, map_height, map_width, map)
  (line >= 0) && (column >= 0) && (line < map_height) && (column < map_width) && (map[line][column] == '#')
end

def find_path(map, robot_line, robot_column, map_height, map_with)
  current_direction = DIRECTION_NORTH
  current_path = []
  current_line = robot_line
  current_column = robot_column

  while true
    p "(#{current_line}, #{current_column}) going #{current_direction}"
    target_line = current_line + DELTA_LINE[current_direction]
    target_column = current_column + DELTA_COLUMN[current_direction]
    if can_go_on?(target_line, target_column, map_height, map_with, map)
      current_path[-1] += 1
      current_line = target_line
      current_column = target_column
    else
      target_line = current_line + DELTA_LINE_TURNING_LEFT[current_direction]
      target_column = current_column + DELTA_COLUMN_TURNING_LEFT[current_direction]
      if can_go_on?(target_line, target_column, map_height, map_with, map)
        p "Turning left"
        current_path << 'L'
        current_path << 1
        current_line = target_line
        current_column = target_column
        current_direction = DIRECTION_TURNING_LEFT[current_direction]
      else
        target_line = current_line + DELTA_LINE_TURNING_RIGHT[current_direction]
        target_column = current_column + DELTA_COLUMN_TURNING_RIGHT[current_direction]
        if can_go_on?(target_line, target_column, map_height, map_with, map)
          p "Turning right"
          current_path << 'R'
          current_path << 1
          current_line = target_line
          current_column = target_column
          current_direction = DIRECTION_TURNING_RIGHT[current_direction]
        else
          p "Stuck at (#{current_line}, #{current_column}) !"
          return current_path
        end
      end
    end
  end
end

find_path(map, robot_line, robot_column, map_height, map_width)

def format_code(code)
  result = []
  code.each_with_index do |c, index|
    if index != 0
      result << ','.ord
    end
    result.concat(c.to_s.chars.map(&:ord))
  end
  result + [10]
end

# I did the calculation by hand as it's not too difficult
main_routine = format_code(['A', 'B', 'A', 'B', 'C', 'C', 'B', 'A', 'B', 'C'])
function_a = format_code(['L', 12, 'L', 10, 'R', 8, 'L', 12])
function_b = format_code(['R', 8, 'R', 10, 'R', 12])
function_c = format_code(['L', 10, 'R', 12, 'R', 8])

memory = IO.read('input.txt').split(',').map(&:to_i)
memory[0] = 2
program = main_routine + function_a + function_b + function_c + ['n'.ord, 10]
amplifier = Amplifier.new(memory, program)
p amplifier.resume([]).last

Day 18

18_1.rb
require 'set'

# @param [String] map
def process(map)
  number_of_keys = map.count('a-z')
  all_keys_mask = 0
  1.upto(number_of_keys) do |key_number|
    all_keys_mask += (1 << (key_number - 1))
  end
  map = map.split("\n")
  map_width = map.first.length
  map_height = map.length
  # All positions already visited
  # each contain a Set with the list of key combinations
  visited_positions = Hash.new { |hash, key| hash[key] = Set.new }
  entrance_line = map.index { |l| l.include?('@') }
  entrance_column = map[entrance_line].index('@')
  map[entrance_line][entrance_column] = '.'
  positions_to_explore = []
  positions_to_explore << {
      line: entrance_line,
      column: entrance_column,
      keys: 0,
      number_of_steps: 0
  }
  until positions_to_explore.empty?
    position_to_explore = positions_to_explore.shift
    [{delta_line: -1, delta_column: 0},
     {delta_line: 1, delta_column: 0},
     {delta_line: 0, delta_column: 1},
     {delta_line: 0, delta_column: -1}].each do |direction|
      target_line = position_to_explore[:line] + direction[:delta_line]
      target_column = position_to_explore[:column] + direction[:delta_column]
      target_keys = position_to_explore[:keys]
      target_map_item = map[target_line][target_column]
      # p "Trying (#{target_line}, #{target_column}) with content [#{target_map_item}] and keys [#{target_keys}]"
      if target_map_item == '#'
        # p "Wall"
        next
      end

      target_map_item_ascii = target_map_item.ord
      if (target_map_item_ascii >= 97) && (target_map_item_ascii <= 122)
        # p "Key"
        key_index = target_map_item_ascii - 97
        if (target_keys & (1 << key_index)) == 0
          # p "Fetch the key"
          target_keys += (1 << key_index)
          if target_keys == all_keys_mask
            # p "Found all the keys !"
            return position_to_explore[:number_of_steps] + 1
          end
        end
      elsif (target_map_item_ascii >= 65) && (target_map_item_ascii <= 90)
        require_key_index = target_map_item_ascii - 65
        unless (target_keys & (1 << require_key_index)) != 0
          # p "Miss the key"
          next
        end
      end

      unless visited_positions[(target_line * map_width) + target_column].add?(target_keys)
        next
      end

      positions_to_explore << {
          line: target_line,
          column: target_column,
          keys: target_keys,
          number_of_steps: position_to_explore[:number_of_steps] + 1
      }
    end
  end
end