use std::ops::RangeBounds; use itertools::Itertools; #[derive(PartialEq, Eq, Debug)] pub struct TargetArea(pub(crate) (isize, isize), pub(crate) (isize, isize)); #[derive(PartialEq, Eq)] pub enum State { EnRoute, InTargetArea, Overshot, } impl TargetArea { pub fn get_position_state(&self, position: (isize, isize)) -> State { let x_bounds_en_route = self.get_bounds(true, true); let y_bounds_en_route = self.get_bounds(false, true); let x_bounds_target = self.get_bounds(true, false); let y_bounds_target = self.get_bounds(false, false); match ( x_bounds_en_route.contains(&position.0), y_bounds_en_route.contains(&position.1), x_bounds_target.contains(&position.0), y_bounds_target.contains(&position.1), ) { (_, _, true, true) => State::InTargetArea, (true, true, _, _) => State::EnRoute, _ => State::Overshot, } } pub fn get_bounds(&self, x: bool, en_route: bool) -> impl RangeBounds { let mut values = if x { vec![self.0 .0, self.0 .1] } else { vec![self.1 .0, self.1 .1] }; if en_route && x { values.push(0); } else if en_route && !x { values.push(isize::MAX); } *values.iter().min().unwrap()..=*values.iter().max().unwrap() } pub fn get_lowest_possible_x_velocity(&self) -> Option { let bounds = self.get_bounds(true, false); for i in 1..=Ord::max(self.0 .0, self.0 .1) { if XLoc(0, i).any(|loc| bounds.contains(&loc)) { return Some(i); } } None } pub fn get_highest_possible_y_velocity(&self) -> Option { let bounds = self.get_bounds(false, false); let y_min = Ord::min(self.1 .0, self.1 .1); (y_min..500) .rev() .find(|i| YLoc(0, *i, y_min).any(|loc| bounds.contains(&loc))) } pub fn get_all_possible_velocities(&self) -> Vec<(isize, isize)> { let x_bounds = self.get_bounds(true, false); let x_velocities = (1..=Ord::max(self.0 .0, self.0 .1)) .filter(|i| XLoc(0, *i).any(|loc| x_bounds.contains(&loc))); let y_bounds = self.get_bounds(false, false); let y_min = Ord::min(self.1 .0, self.1 .1); let y_velocities = (y_min..500) .rev() .filter(|i| YLoc(0, *i, y_min).any(|loc| y_bounds.contains(&loc))); let mut ret_val = Vec::new(); x_velocities .cartesian_product(y_velocities) .filter(|velocity| Probe::new(*velocity).will_reach_target_area(self)) .for_each(|velocity| ret_val.push(velocity)); ret_val } } pub struct XLoc(isize, isize); impl Iterator for XLoc { type Item = isize; fn next(&mut self) -> Option { let velocity = self.1; self.0 += velocity; let item = self.0; match velocity { n if n > 0 => self.1 -= 1, n if n < 0 => self.1 += 1, _ => return None, }; Some(item) } } pub struct YLoc(isize, isize, isize); impl Iterator for YLoc { type Item = isize; fn next(&mut self) -> Option { let velocity = self.1; self.0 += velocity; self.1 -= 1; let item = self.0; if item < self.2 { None } else { Some(item) } } } #[derive(Clone, Debug)] pub struct Probe { pub position: (isize, isize), pub velocity: (isize, isize), } impl Probe { pub fn new(velocity: (isize, isize)) -> Self { Self { position: (0, 0), velocity, } } pub fn step(&mut self) { self.position.0 += self.velocity.0; self.position.1 += self.velocity.1; self.velocity.0 = match self.velocity.0 { n if n > 0 => self.velocity.0 - 1, n if n < 0 => self.velocity.0 + 1, _ => 0, }; self.velocity.1 -= 1; } pub fn will_reach_target_area(&self, target_area: &TargetArea) -> bool { let mut probe = self.clone(); while target_area.get_position_state(probe.position) == State::EnRoute { probe.step(); } matches!( target_area.get_position_state(probe.position), State::InTargetArea ) } } #[cfg(test)] mod test { use crate::day_17::model::Probe; use super::TargetArea; fn example() -> TargetArea { TargetArea((20, 30), (-10, -5)) } #[test] fn examples() { for velocity in [(7, 2), (6, 3), (9, 0), (6, 9)] { assert!(Probe::new(velocity).will_reach_target_area(&example())); } for velocity in [(17, -4)] { assert!(!Probe::new(velocity).will_reach_target_area(&example())); } for y_vel in 10..1000 { assert!(!Probe::new((6, y_vel)).will_reach_target_area(&example())); } } #[test] fn lowest_possible_x_velocity() { assert_eq!(example().get_lowest_possible_x_velocity(), Some(6)); } #[test] fn highest_possible_y_velocity() { assert_eq!(example().get_highest_possible_y_velocity(), Some(9)); } }