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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
input = DATA.read.gsub(
  /[#O\.@]/, %w[ # ## O [] . .. @ @. ].each_slice(2).to_h
).split("\n\n")

map = {}
input.shift.split("\n").each.with_index do |row, y|
  row.chars.each.with_index do |pos, x|
    map[[y,x]] = pos unless pos == ?.
  end
end
robot = map.find { _2 == ?@ }.first

bounds = [
  (0..map.keys.map(&:first).max),
  (0..map.keys.map(&:last).max),
]
map_s = ->() {
  bounds[0].map { |y|
    bounds[1].map { |x|
      map.fetch([y,x], ?.)
    }.join
  }.join("\n")
}

Nope = Class.new(Exception)

def push(map, pos, move)
  delta = DELTAS.fetch(move)
  pospos = pos.zip(delta).map { _1 + _2 }

  case map.fetch(pospos, nil)
  when ?#
    raise Nope
  when ?[, ?]
    case move
    when ?<, ?>
      push(map, pospos, move)
    when ?^, ?v
      a = pospos
      dx = {?[ => 1, ?] => -1}.fetch(map.fetch(pospos))
      b = a.zip([0, dx]).map { _1 + _2 }
      push_two(map, a, b, move).reverse.uniq.each do |pos, pospos|
        map[pospos] = map.fetch(pos)
        map.delete(pos)
      end
    else
      fail
    end
  when nil
  else
    fail
  end

  map[pospos] = map.fetch(pos)
  map.delete(pos)

  pospos
end

def push_two(map, a, b, move)
  delta = DELTAS.fetch(move)
  aa = a.zip(delta).map { _1 + _2 }
  bb = b.zip(delta).map { _1 + _2 }

  [[a, aa], [b, bb]].flat_map {|pos, pospos|
    case map.fetch(pospos, nil)
    when ?#
      raise Nope
    when ?[, ?]
      a_ = pospos
      dx = {?[ => 1, ?] => -1}.fetch(map.fetch(pospos))
      b_ = a_.zip([0, dx]).map { _1 + _2 }
      [[pos, pospos]] + push_two(map, a_, b_, move)
    when nil
      [[pos, pospos]]
    else
      fail
    end
  }
end

DELTAS = %w[ ^ v < > ].zip([[-1,0], [1,0], [0,-1], [0, 1]]).to_h
movements = input.shift.gsub("\n", "").chars

movements.each do |move|
  begin
    robot = push(map, robot, move)
  rescue Nope
  end
end

# pp map.select { _2 == ?O }.sum {|(y,x),_| 100 * y + x }
pp map.select { _2 == ?[ }.sum {|(y,x),_| 100 * y + x }

__END__