class Playground attr_reader :boxes, :circuits, :last_pair def initialize(boxes) @boxes = boxes boxes.each { |box| box.pg = self } @circuits = boxes.to_h{ |box| [box.index, Circuit.new(box)] } end def self.for(input) new input.split("\n").each_with_index.map(&JunctionBox.method(:for)) end def distances boxes.map { |b1| boxes.map { |b2| b1.distance_squared(b2) } } end def distance_pairs distances.each_with_index.map do |dist_row, x| dist_row.each_with_index.map do |dist, y| [[x, y].min, [x,y].max, dist] end end.flatten(1).uniq.sort_by { |dp| dp[2] } end def connect_circuits!(connections=1000) distance_pairs.first(connections).each do |a, b, _| connect_circuit!(a, b) end end def connect_all_circuits! distance_pairs.each do |a, b, _| connect_circuit!(a, b) break if all_connected? end end def all_connected? circuits.values.uniq.length == 1 end def connect_circuit!(a, b) return if circuits[a].boxes.include?(boxes[b]) @last_pair = boxes[a], boxes[b] circuits[a].boxes.push(*Array.new(circuits[b].boxes)) circuits[b].boxes.each { |box| circuits[box.index] = circuits[a] } end class JunctionBox attr_reader :index, :x, :y, :z attr_accessor :pg def initialize(index, x, y, z) @index = index @x = x @y = y @z = z end def self.for(line, index) new index, *line.split(",").map(&:to_i) end def distance_squared(other) return 9999999999999 if self == other (x - other.x)**2 + (y - other.y)**2 + (z - other.z)**2 end def distances @distances ||= pg.boxes.map { |box| distance_squared(box) } end def to_s "#{index} (#{x}, #{y}, #{z})" end end class Circuit attr_reader :boxes def initialize(box) @boxes = [box] end def to_s boxes.map(&:index) end def length boxes.length end end end