advent_of_code/year2021/
day17.rs1use crate::input::Input;
2use std::ops::RangeInclusive;
3
4pub fn solve(input: &Input) -> Result<i32, String> {
5 let trench = Trench::parse(input.text).ok_or_else(|| "Unable to parse trench".to_string())?;
6
7 let mut max_y = 0;
8 let mut count = 0;
9 let max_x_to_try = *trench.x_range.end();
10 let min_y_to_try = *trench.y_range.start();
11 for initial_y_velocity in min_y_to_try..=1000 {
12 for initial_x_velocity in 1..=max_x_to_try {
13 if let Some(value) =
14 probes_ends_in_trench(initial_x_velocity, initial_y_velocity, &trench)
15 {
16 max_y = value;
17 count += 1;
18 }
19 }
20 }
21 Ok(input.part_values(max_y, count))
22}
23
24struct Trench {
25 x_range: RangeInclusive<i16>,
26 y_range: RangeInclusive<i16>,
27}
28
29impl Trench {
30 fn parse(text: &str) -> Option<Self> {
31 let target_area = if text.len() < 18 {
32 return None;
33 } else {
34 &text[15..]
35 };
36 let (x_range, y_range) = if let Some((x_range, y_range)) = target_area.split_once(", y=") {
37 (Self::parse_range(x_range)?, Self::parse_range(y_range)?)
38 } else {
39 return None;
40 };
41 Some(Self { x_range, y_range })
42 }
43
44 fn parse_range(range: &str) -> Option<RangeInclusive<i16>> {
45 if let Some((start, end)) = range.split_once("..") {
46 let a = start.parse::<i16>().ok()?;
47 let b = end.parse::<i16>().ok()?;
48 Some(std::cmp::min(a, b)..=std::cmp::max(a, b))
49 } else {
50 None
51 }
52 }
53}
54
55fn probes_ends_in_trench(
56 horizontal_starting_velocity: i16,
57 vertical_starting_velocity: i16,
58 trench: &Trench,
59) -> Option<i32> {
60 let mut x = 0;
61 let mut y = 0_i32;
62 let mut dx = horizontal_starting_velocity;
63 let mut dy = i32::from(vertical_starting_velocity);
64 let mut max_y = 0;
65 loop {
66 x += dx;
67 y += dy;
68 dx -= dx.signum();
69 dy -= 1;
70 max_y = std::cmp::max(y, max_y);
71
72 if y > i32::from(i16::MAX) {
73 continue;
74 }
75
76 if !trench.x_range.contains(&x) {
77 let stopped_by_drag = dx == 0;
78 let passed_trench = x > *trench.x_range.end();
79 if stopped_by_drag || passed_trench {
80 return None;
81 } else {
82 continue;
83 }
84 }
85
86 if !trench.y_range.contains(&(y as i16)) {
87 let below_trench = (y as i16) < *trench.y_range.end();
88 let falling_down = dy < 0;
89 if below_trench && falling_down {
90 return None;
91 } else {
92 continue;
93 }
94 }
95
96 return Some(max_y);
97 }
98}
99
100#[test]
101pub fn tests() {
102 let example = "target area: x=20..30, y=-10..-5";
103 test_part_one!(example => 45);
104 test_part_two!(example => 112);
105
106 let real_input = include_str!("day17_input.txt");
107 test_part_one!(real_input => 7381);
108 test_part_two!(real_input => 3019);
109}