require "set" require "minitest" require "minitest/pride" class Slice def self.parse(s) squares = {} s.scan(/^(x|y)=(\d+), (?:x|y)=(\d+)..(\d+)$/m).each do |xy, i, min, max| i, min, max = [i, min, max].map(&:to_i) (min..max).map {|j| xy == ?x ? [i, j] : [j, i] }.each do |pos| squares[pos] = ?# end end new(squares) end attr_reader :squares def initialize(squares) @squares = squares @squares[[500, 0]] = ?+ @active = Set[[500, 0]] end def simulate return enum_for(__method__) unless block_given? max_y = @squares.keys.map(&:last).max loop do @active.to_a.each do |pos| @active.delete(pos) x, y = pos case @squares.fetch(pos) when ?+ @squares[[x, y+1]] = ?| @active << [x, y+1] when ?| next if y+1 > max_y if @squares[[x, y+1]].nil? @squares[[x, y+1]] = ?| @active << [x, y+1] else if @squares[[x-1, y]].nil? @squares[[x-1, y]] = ?| @active << [x-1, y] end if @squares[[x+1, y]].nil? @squares[[x+1, y]] = ?| @active << [x+1, y] end left_x = x-1 left_x -= 1 until @squares[[left_x, y]] != ?| right_x = x+1 right_x += 1 until @squares[[right_x, y]] != ?| if @squares[[left_x, y]] == ?# && @squares[[right_x, y]] == ?# (left_x+1..right_x-1).each do |x| @squares[[x, y]] = ?~ @active << [x, y-1] if @squares[[x, y-1]] == ?| end end end end end return if @active.empty? yield self end end def water @squares.select {|_, s| %w[ | ~ ].include?(s) } end def to_s min_x, max_x = @squares.keys.map(&:first).minmax min_y, max_y = @squares.keys.map(&:last).minmax min_y = [0, min_y].min (min_y..max_y).map {|y| (min_x-1..max_x+1).map {|x| @squares.fetch([x, y]) { ?. } }.join }.join(?\n) end end class TestSlice < Minitest::Test def setup @slice = Slice.parse(<<~SLICE) x=495, y=2..7 y=7, x=495..501 x=501, y=3..7 x=498, y=2..4 x=506, y=1..2 x=498, y=10..13 x=504, y=10..13 y=13, x=498..504 SLICE end def test_parse_to_s assert_equal <<~SLICE.chomp, @slice.to_s ......+....... ............#. .#..#.......#. .#..#..#...... .#..#..#...... .#.....#...... .#.....#...... .#######...... .............. .............. ....#.....#... ....#.....#... ....#.....#... ....#######... SLICE end def test_simulate simulation = @slice.simulate 5.times { simulation.next } assert_equal <<~SLICE.chomp, @slice.to_s ......+....... ......|.....#. .#..#.|.....#. .#..#.|#...... .#..#.|#...... .#....|#...... .#.....#...... .#######...... .............. .............. ....#.....#... ....#.....#... ....#.....#... ....#######... SLICE 5.times { simulation.next } assert_equal <<~SLICE.chomp, @slice.to_s ......+....... ......|.....#. .#..#.|.....#. .#..#.|#...... .#..#.|#...... .#....|#...... .#~~~~~#...... .#######...... .............. .............. ....#.....#... ....#.....#... ....#.....#... ....#######... SLICE 4.times { simulation.next } assert_equal <<~SLICE.chomp, @slice.to_s ......+....... ......|.....#. .#..#.|.....#. .#..#.|#...... .#..#.|#...... .#~~~~~#...... .#~~~~~#...... .#######...... .............. .............. ....#.....#... ....#.....#... ....#.....#... ....#######... SLICE 2.times { simulation.next } assert_equal <<~SLICE.chomp, @slice.to_s ......+....... ......|.....#. .#..#.|.....#. .#..#~~#...... .#..#~~#...... .#~~~~~#...... .#~~~~~#...... .#######...... .............. .............. ....#.....#... ....#.....#... ....#.....#... ....#######... SLICE 21.times { simulation.next } assert_equal <<~SLICE.chomp, @slice.to_s ......+....... ......|.....#. .#..#||||...#. .#..#~~#|..... .#..#~~#|..... .#~~~~~#|..... .#~~~~~#|..... .#######|..... ........|..... ........|..... ....#~~~~~#... ....#~~~~~#... ....#~~~~~#... ....#######... SLICE 9.times { simulation.next } assert_equal <<~SLICE.chomp, @slice.to_s ......+....... ......|.....#. .#..#||||...#. .#..#~~#|..... .#..#~~#|..... .#~~~~~#|..... .#~~~~~#|..... .#######|..... ........|..... ...|||||||||.. ...|#~~~~~#|.. ...|#~~~~~#|.. ...|#~~~~~#|.. ...|#######|.. SLICE assert_equal 57, @slice.water.count end end def solve(input) slice = Slice.parse(input) min_y = slice.squares.select {|_,s| s == ?# }.map(&:first).map(&:last).min simulation = slice.simulate loop do simulation.next end # puts # min_x, max_x = slice.water.map(&:first).minmax # min_y, max_y = slice.water.map(&:last).minmax # min_y = [0, min_y].min # puts (min_y..max_y).map {|y| (min_x-1..max_x+1).map {|x| slice.squares.fetch([x, y]) { ?. } }.join }.join(?\n) # Part One # slice.water.count {|(_,y),_| y >= min_y } slice.water.count {|_,s| s == ?~ } end if __FILE__ == $0 require "minitest/autorun" and exit if ENV["TEST"] puts solve(ARGF.read) end