# frozen_string_literal: true class AsteroidBelt def initialize(text) @grid = parse(text) end def asteroids return @asteroids unless @asteroids.nil? @asteroids = [] @grid.each_with_index do |line, y| line.chars.each_with_index do |cell, x| @asteroids << Asteroid.new(x, y) if cell == '#' end end @asteroids end private def parse(text) text.lines end end class Asteroid attr_reader :x, :y def initialize(x, y) @x = x @y = y end def neighbors(asteroids) by_direction = Hash.new { |hash, key| hash[key] = [] } asteroids.each do |asteroid| next if asteroid == self theta = direction_to(asteroid).normalize by_direction[theta] << asteroid end by_direction end def direction_to(other) dx = other.x - x dy = other.y - y Vector.new(dx, dy) end def distance_to(other) dx = other.x - x dy = other.y - y Math.sqrt(dx**2 + dy**2) end def inspect "" end def to_s "(#{x},#{y})" end end class Vector attr_reader :x, :y def initialize(x, y, normal = false) @x = x @y = y @normal = normal end def normalize return self if @normal div = gcd(@x.abs, @y.abs) Vector.new(Rational(@x / div), Rational(@y / div), true) end def ==(other) s = normalize o = other.normalize s.x == o.x && s.y == o.y end def eql?(other) self == other end def hash n = normalize [n.x, n.y].hash end def inspect "" end def to_s "(#{x},#{y})" end def from_vertical # (0,1) # 0 / # |t/ # |/ # (-1,0) 3pi/2 -----+----- +pi/2 (1,0) # | # | # +pi # (0,-1) -Math.atan2(x, y) end end def gcd(n, m) # Invariant: n >= m n, m = m, n if n < m n, m = m, n % m until m.zero? n end input = File.read(File.join(__dir__, 'input')) belt = AsteroidBelt.new input best = belt.asteroids.max_by do |asteroid| asteroid.neighbors(belt.asteroids).count end pp best neighbors = best.neighbors(belt.asteroids) directions = neighbors.keys.sort_by(&:from_vertical) neighbors.values.each do |asteroids| asteroids.sort_by! do |asteroid| best.distance_to(asteroid) end end destroyed = [] until neighbors.values.all?(&:empty?) directions.each do |dir| next if neighbors[dir].empty? destroyed << neighbors[dir].shift end end winner = destroyed[199] pp winner puts 100 * winner.x + winner.y