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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
input = DATA.readlines(chomp: true)
  .flat_map.with_index {|line, j|
    line.chars.map.with_index {|x, i|
      [[j,i], x]
    }
  }.to_h

TURNS = {
  [-1,0] => [0,1],
  [0,1] => [1,0],
  [1,0] => [0,-1],
  [0,-1] => [-1,0],
}

Loop = Class.new(Exception)

def patrol(lab)
  return enum_for(__method__, lab) unless block_given?

  start = lab.find { _2 == ?^ }.first
  guard = [start, [-1,0]]
  seen = Set.new

  loop do
    pos, dir = guard
    yield pos

    peek = pos.zip(dir).map {|i,di| i + di }
    return unless lab.has_key?(peek)

    raise Loop if seen.include?([peek, dir])
    seen << [peek, dir]

    case lab.fetch(peek)
    when ?., ?^
      guard[0] = peek
    when ?#
      guard[1] = TURNS.fetch(dir)
    when nil
      return
    else
      fail
    end
  end
end

def loop?(lab)
  patrol(lab).each {}
  false
rescue Loop
  true
end

path = patrol(input).to_set
pp path.length

pp input
  .select { path.include?(_1) && _2 != ?^ }
  .select {|xy, _|
    # pp xy
    input[xy] = ?#
    res = loop?(input)
    input[xy] = ?.
    res
  }.size

__END__