191 lines
5.2 KiB
Rust
191 lines
5.2 KiB
Rust
|
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<isize> {
|
||
|
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<isize> {
|
||
|
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<isize> {
|
||
|
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<Self::Item> {
|
||
|
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<Self::Item> {
|
||
|
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));
|
||
|
}
|
||
|
}
|