Skip to main content

advent_of_code/year2021/
day17.rs

1use 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}