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