You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
advent-of-code/2022/ruby/day_24.rb

94 lines
2.0 KiB

1 year ago
require "set"
walls = Set.new
blizzards = Hash.new {|h,k| h[k] = Set.new }
clear_ground = Set.new
ARGF.read.lines(chomp: true).each.with_index do |row, y|
row.chars.each.with_index do |pos,x|
case pos
when ?#
walls.add([y,x])
when /[v^<>]/
blizzards[pos].add([y,x])
when ?.
clear_ground.add([y,x])
else
fail pos
end
end
end
x_range = Range.new(*walls.map(&:last).minmax)
y_range = Range.new(*walls.map(&:first).minmax)
debug = ->() do
puts
puts y_range.map {|y|
x_range.map {|x|
if walls.include?([y,x])
?#
elsif blizzards.any? {|_,pos| pos.include?([y,x])}
b = blizzards.select {|_,pos| pos.include?([y,x])}
(b.size == 1) ? b.keys[0] : b.size
else
?.
end
}.join
}.join("\n")
end
start = [0, clear_ground.find {|y,x| y == 0 }.last]
goal = [y_range.end, clear_ground.find {|y,x| y == y_range.end }.last]
dir_deltas = {
?^ => [-1, 0],
?v => [ 1, 0],
?< => [ 0, -1],
?> => [ 0, 1],
}
moves = [*dir_deltas.values, [0, 0]]
frontier = [start]
(1..).each do |time|
if (time % 1).zero?
puts time
# p frontier
# debug.()
end
blizzards = blizzards.to_h {|dir,positions|
delta = dir_deltas.fetch(dir)
edges, positions = positions
.map {|pos| pos.zip(delta).map { _1 + _2 }}
.partition { walls.include?(_1) }
positions.concat(edges.map {|(y,x)|
case dir
when ?^ then [y_range.end - 1, x]
when ?v then [y_range.begin + 1, x]
when ?< then [y, x_range.end - 1]
when ?> then [y, x_range.end + 1]
else fail dir
end
})
[dir, positions]
}
clear_ground = y_range.map {|y| x_range.map {|x| [y, x] }}.flatten(1)
.reject {|pos| walls.include?(pos) || blizzards.any? { _2.include?(pos) }}
options = frontier.map {|current|
moves.filter_map {|delta|
pos = current.zip(delta).map { _1 + _2 }
clear_ground.include?(pos) && pos
}
}.flatten(1)
frontier = options.uniq
if frontier.include?(goal)
puts time + 1
exit
end
end