-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday17.rb
109 lines (93 loc) · 3.26 KB
/
day17.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# frozen_string_literal: true
# https://adventofcode.com/2021/day/17
class Probe
attr_accessor :start, :position, :initial_direction, :current_direction, :target, :trajectory, :hit, :max_y_reached
def initialize(starting_point, direction, target)
@start = starting_point.clone
@position = starting_point.clone
@max_y_reached = starting_point[1]
@initial_direction = direction.clone
@current_direction = direction.clone
@target = target
@hit = false
plot_trajectory
end
def plot_trajectory
@trajectory = [@start]
while keep_going?
@position[0] = @position[0] + @current_direction[0]
@position[1] = @position[1] + @current_direction[1]
@trajectory << @position.clone
@max_y_reached = @position[1] if @position[1] > @max_y_reached
@current_direction[0] = [0, @current_direction[0] - 1].max
@current_direction[1] = @current_direction[1] - 1
end
end
def keep_going?
return false if @position[0] > @target[1] || @position[1] < @target[2]
if in_target?
@hit = true
return false
end
true
end
def in_target?
@position[0] >= @target[0] && @position[0] <= @target[1] && @position[1] >= @target[2] && @position[1] <= @target[3]
end
def visualize_trajectory
y_start = [0, @max_y_reached].max
grid = Array.new(1 + y_start - @target[2]) { |_| Array.new(@target[1] + 1, '.') }
(@target[0]..target[1]).each do |x|
(@target[2]..@target[3]).each do |y|
grid[(y - y_start).magnitude][x] = 'T'
end
end
(0..@target[1]).each do |x|
(@target[2]..y_start).each do |y|
grid[(y - y_start).magnitude][x] = '*' if @trajectory.include? [x, y]
end
end
grid.each { |row| puts row.join }
grid.map(&:join)
end
end
class ProbeLauncher
attr_accessor :target, :probes
def initialize(target_description)
@target = parse_target_description(target_description)
calculate_probes
end
def calculate_probes
@probes = []
bounds = sensible_direction_bounds
(bounds[0]..bounds[1]).each do |x_mag|
(bounds[2]..bounds[3]).each do |y_mag|
@probes << Probe.new([0, 0], [x_mag, y_mag], @target)
end
end
end
def sensible_direction_bounds
(1..@target[1]).each do |x|
return [x, @target[1], @target[2], @target[2].magnitude] if (0..x).sum >= @target[0]
end
[1, @target[1], @target[2], @target[2].magnitude] # Defensive, should never get here.
end
# E.g. 'target area: x=20..30, y=-10..-5' becomes [20, 30, -10, -5]
def parse_target_description(description)
/x=([-\d.]*).*y=([-\d.]*)/.match(description).captures.map { |e| e.split('../inputs/2021/..').map(&:to_i) }.flatten(1)
end
def max_y_reached
@probes.select(&:hit).max { |a, b| a.max_y_reached <=> b.max_y_reached }.max_y_reached
end
def on_target_probe_count
@probes.select(&:hit).length
end
end
def launch(description)
launcher = ProbeLauncher.new(description)
max_y = launcher.max_y_reached
puts "The maximum reachable y is #{max_y} given the target '#{description}'"
count = launcher.on_target_probe_count
puts "The number of potentially successful probes is #{count} given the target '#{description}'"
end
launch('../inputs/2021/target area: x=143..177, y=-106..-71') if __FILE__ == $PROGRAM_NAME