Advent of Code 2017, Day 13: Packet Scanners

#ruby #advent of code 2017

Part A

On Day 13 we are simulating packet scanners in an imaginary firewall. Our input looks like this:

0: 3
1: 2
4: 4
6: 4

First number is a layer number in the firewall and second number is range describing how many cells scanner in the given layer will scan. Scanner goes from the beginning to the end and then moves back. For example if the range is 3, scanner will check cells number 0, 1, 2, 1, 0, 1, 2 etc.

Each scanner is moving to the next cell in one picosecond. When our packet is caught we can calculate the severity of this incident by multiplying the layer number by its range.

Our goal is to calculate the severity of the whole trip.

Here is the solution:

data = File.readlines("13.txt", chomp: true)

layers = {}
size = 0

data.each do |line|
  depth, range = line.split(":").map(&:strip).map(&:to_i)

  layers[depth] = range
  size = depth if depth > size
end

def position(range, second)
  modulo = range * 2 - 2
  middle = modulo / 2

  reminder = second % modulo 

  if reminder > middle
    modulo - reminder
  else
    reminder
  end
end

severity = 0
(0..size).each do |second|
  range = layers[second]

  next unless range

  pos = position(range, second)

  if pos == 0
    severity += (second * range)
  end
end

puts severity

We have position method which calculates the scanner position with given range and at given second. Our packet will be always traveling in the first cell, so for each time packet moves to another layer we need to check what is the current position of the scanner for this layer. If it is 0 it means our packet was caught and we add up to our severity.

Part B

In the second part we need to calculate the delay required to pass through all layers undetected.

Here is the solution:

data = File.readlines("13.txt", chomp: true)

layers = {}
size = 0

data.each do |line|
  depth, range = line.split(":").map(&:strip).map(&:to_i)

  layers[depth] = range
  size = depth if depth > size
end

def position(range, second)
  modulo = range * 2 - 2
  middle = modulo / 2

  reminder = second % modulo 

  if reminder > middle
    modulo - reminder
  else
    reminder
  end
end

def caught?(layers, size, delay)
  (0..size).each do |depth|
    range = layers[depth]
    second = depth + delay

    next unless range

    if position(range, second) == 0
      return true
    end
  end

  false
end

delay = 0
while true
  if caught?(layers, size, delay)
    delay += 1
  else
    puts delay
    break
  end
end

Here we are reusing the solution for the first part, but we also have parametrized delay. It is basically brute-force approach. We start with 0 delay and check if we were caught. If yes, we try to delay by one more picosecond until we find the delay that let us pass undetected.