require "forwardable" DIR_DELTAS = { N: [-1, 0], W: [ 0, -1], E: [ 0, 1], S: [ 1, 0], } TILE_NEIGHBOR_DELTAS = { ?| => "NS", ?- => "EW", ?L => "NE", ?J => "NW", ?7 => "SW", ?F => "SE", ?S => "NEWS", }.transform_values {|dirs| dirs.chars.map { DIR_DELTAS.fetch(_1.to_sym) }} Tiles = Data.define(:tiles) do extend Forwardable def_delegator :tiles, :key def_delegator :tiles, :fetch def_delegator :tiles, :size def neighbors(coord) TILE_NEIGHBOR_DELTAS.fetch(self.fetch(coord)) .map {|delta| coord.zip(delta).map { _1 + _2 }} .select { tiles.has_key?(_1) } end def to_s coords = tiles.keys y_range, x_range = *extents y_range.map {|y| x_range.map {|x| tiles.fetch([y,x], ?.) }.join }.join("\n") end def extents y_range = Range.new(*tiles.keys.map(&:first).minmax) x_range = Range.new(*tiles.keys.map(&:last).minmax) [y_range, x_range] end end tiles = ARGF.readlines(chomp: true).each.with_index.inject({}) {|tiles, (row, y)| tiles.merge( row.chars.each.with_index .reject {|tile,_| tile == ?. } .to_h {|tile,x| [[y,x], tile] } ) } tiles = Tiles.new(tiles) start = tiles.key(?S) seed = tiles.neighbors(start) .find {|candidate| tiles.neighbors(candidate).any? { tiles.fetch(_1) == ?S } } loop_ = [seed] until loop_.last == start cur = loop_.last loop_ << tiles.neighbors(cur) .reject { _1 == loop_[-2] } .first end # fix the start tile start_dirs = [seed, loop_[-2]].map {|coord| start.zip(coord).map { _2 - _1 }} tile = TILE_NEIGHBOR_DELTAS.find { _2.sort == start_dirs.sort }[0] tiles.tiles[start] = tile loop_ = Tiles.new(loop_.to_h { [_1, tiles.fetch(_1)] }) # puts loop_ # part one p loop_.size / 2 # part two y_range, x_range = *loop_.extents p y_range.sum {|y| x_range.chunk_while {|x_a, x_b| a = loop_.fetch([y, x_a], nil) b = loop_.fetch([y, x_b], nil) (a.nil? && b.nil?) || (a && TILE_NEIGHBOR_DELTAS.fetch(a).include?([0,1])) }.select {|chunk| # keep empty tiles end_tiles = chunk.values_at(0, -1).map {|x| loop_.fetch([y,x], nil) } next true if end_tiles.all?(&:nil?) # only keep pipes that cross the horizontal axis deltas = end_tiles.flat_map {|tile| TILE_NEIGHBOR_DELTAS.fetch(tile) } [[-1,0], [1,0]].all? {|dy| deltas.include?(dy) } }.sum {|chunk| tiles = chunk.map {|x| loop_.fetch([y,x], nil) } (tiles[0]...tiles[0]) ? tiles.count(nil) : 0 # lol } }