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
def succ(coord, delta)
  coord.zip(delta).map { _1 + _2 }
end

map = ARGF.readlines(chomp: true)
  .flat_map.with_index {|row, y|
    row.chars.map.with_index {|elem, x|
      [[y,x], elem.to_i]
    }
  }.to_h

max_y = map.keys.map(&:first).max
max_x = map.keys.map(&:last).max
dest = [max_y,max_x]

Node = Data.define(:pos, :dir, :continued)

frontier = []
visited = {}

# seed initial directions
[
  [1,0], # down
  [0,1], # right
].each do |dir|
  node = Node.new(dir, dir, 1)
  frontier << node
  visited[node] = map.fetch(dir)
end

loop do
  frontier.sort_by! { visited.fetch(_1) }
  current = frontier.shift

  nexts = []

  # turn left and right
  if current.continued > 3
    nexts.concat([current.dir.reverse, current.dir.reverse.map { -_1 }].map {|turn|
      Node.new(current.pos.zip(turn).map { _1 + _2 }, turn, 1)
    })
  end

  # keep going?
  if current.continued < 10
    nexts << Node.new(
      current.pos.zip(current.dir).map { _1 + _2 },
      current.dir,
      current.continued + 1,
    )
  end

  if found = nexts.find { _1.pos == dest && (4..10).cover?(_1.continued) }
    p visited.fetch(current) + map.fetch(dest)
    exit
  end

  nexts
    .select { map.has_key?(_1.pos) }
    .reject { visited.has_key?(_1) }
    .each do |node|
      frontier << node
      visited[node] = visited.fetch(current) + map.fetch(node.pos)
    end
end