diff --git a/2019/day10/part1.rb b/2019/day10/part1.rb new file mode 100644 index 0000000..4a1f3ca --- /dev/null +++ b/2019/day10/part1.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +input = File.read(File.join(__dir__, 'input')) + +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 = {} + asteroids.each do |asteroid| + next if asteroid == self + + theta = direction_to(asteroid).normalize + # Since we only care about count, we don't care *which* one we can see at + # in a given direction. If we do care about it, sort by distance and + # take the closer one. + 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 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 +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 + +belt = AsteroidBelt.new input + +best = belt.asteroids.max_by do |asteroid| + asteroid.neighbors(belt.asteroids).count +end +pp best +pp best.neighbors(belt.asteroids).count