1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
input = ARGF.readlines(chomp: true)
  .flat_map.with_index {|row, y|
    row.chars.map.with_index {|elem, x| [[y,x], elem] }
  }.to_h

count_energized = ->(start, dir) {
  current = [[start, dir]]
  seen = Set.new

  until current.empty?
    coord, delta = current.shift
    seen << [coord, delta]

    next_delta = case [input[coord], delta]
                 in [nil, _]
                   [] # the beam has escaped the contraption
                 in [?., _] | [?|, [_,0]] | [?-, [0,_]] # keep going
                   [delta]
                 in [?|, [0,_]] # split up and down
                   [[-1,0], [1,0]]
                 in [?-, [_,0]] # split left and right
                   [[0,-1], [0,1]]
                 in [?/, _]
                   [delta.map { -_1 }.reverse]
                 in [?\\, _]
                   [delta.reverse]
                 else
                   fail
                 end

    current.concat(
      next_delta
        .map {|d| [coord.zip(d).map { _1 + _2 }, d] }
        .reject { seen.include?(_1) }
    )
  end

  seen.map(&:first).select { input.has_key?(_1) }.uniq.size
}

max_y = input.keys.map(&:first).max
max_x = input.keys.map(&:last).max

candidates = [
  (0..max_x).map { [[0,_1], [1,0]] },      # down
  (0..max_x).map { [[max_y,_1], [-1,0]] }, # up
  (0..max_y).map { [[_1,0], [0,1]] },      # right
  (0..max_y).map { [[_1,max_x], [0,-1]] }, # left
].inject(:+)

p candidates.map { count_energized[*_1] }.max