input = ARGF.readlines(chomp: true) .flat_map.with_index {|row, y| row.chars.map.with_index {|elem, x| [[y,x], elem] } }.to_h current = [[[0,0], [0,1]]] seen = Set.new until current.empty? coord, delta = current.shift seen << [coord, delta] case [elem = input[coord], delta] in [nil, _] # the beam has escaped the contraption in [?., _] | [?|, [_,0]] | [?-, [0,_]] # keep going current << [coord.zip(delta).map { _1 + _2 }, delta] in [?|, [0,_]] # split up and down current << [coord.zip([-1,0]).map { _1 + _2 }, [-1,0]] current << [coord.zip([1,0]).map { _1 + _2 }, [1,0]] in [?-, [_,0]] # split left and right current << [coord.zip([0,-1]).map { _1 + _2 }, [0,-1]] current << [coord.zip([0,1]).map { _1 + _2 }, [0,1]] in [?/, [0,1]] # going right, goes up current << [coord.zip([-1,0]).map { _1 + _2 }, [-1,0]] in [?/, [0,-1]] # going left, goes down current << [coord.zip([1,0]).map { _1 + _2 }, [1,0]] in [?/, [1,0]] # going down, goes left current << [coord.zip([0,-1]).map { _1 + _2 }, [0,-1]] in [?/, [-1,0]] # going up, goes right current << [coord.zip([0,1]).map { _1 + _2 }, [0,1]] in [?\\, [0,1]] # going right, goes down current << [coord.zip([1,0]).map { _1 + _2 }, [1,0]] in [?\\, [0,-1]] # going left, goes up current << [coord.zip([-1,0]).map { _1 + _2 }, [-1,0]] in [?\\, [1,0]] # going down, goes right current << [coord.zip([0,1]).map { _1 + _2 }, [0,1]] in [?\\, [-1,0]] # going up, goes left current << [coord.zip([0,-1]).map { _1 + _2 }, [0,-1]] else fail "unexpected element: #{elem.inspect}" end current = current.reject { seen.include?(_1) } end p seen.map(&:first).select { input.has_key?(_1) }.uniq.size