2021: day17: part 1
This commit is contained in:
parent
0446f11a46
commit
026470220b
1
aoc2021/input/day17.txt
Normal file
1
aoc2021/input/day17.txt
Normal file
|
@ -0,0 +1 @@
|
|||
target area: x=277..318, y=-92..-53
|
1
aoc2021/input/day17_provided.txt
Normal file
1
aoc2021/input/day17_provided.txt
Normal file
|
@ -0,0 +1 @@
|
|||
target area: x=20..30, y=-10..-5
|
159
aoc2021/src/day17.rs
Normal file
159
aoc2021/src/day17.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::fmt::Write;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
const INPUT: &str = include_str!("../input/day17.txt");
|
||||
|
||||
pub fn run() -> Result<String> {
|
||||
let mut res = String::with_capacity(128);
|
||||
|
||||
writeln!(res, "part 1: {}", part1(INPUT)?)?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> Result<isize> {
|
||||
let area: TargetArea = input
|
||||
.parse()
|
||||
.context("couldn't parse input to target area")?;
|
||||
|
||||
let min_x_vel = if area.min_x() > 0 {
|
||||
(0..).find(|x| ((x * (x + 1)) / 2) >= area.min_x()).unwrap()
|
||||
} else if area.max_x() < 0 {
|
||||
-(0..)
|
||||
.find(|x| ((x * (x + 1)) / 2) >= area.max_x().abs())
|
||||
.unwrap()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let max_x_vel = if area.min_x() > 0 {
|
||||
area.max_x()
|
||||
} else if area.max_x() < 0 {
|
||||
area.min_x()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// Rust ranges can only be increasing, so swap values around if negative
|
||||
let (min_x_vel, max_x_vel) = (min_x_vel.min(max_x_vel), min_x_vel.max(max_x_vel));
|
||||
|
||||
// we could launch the prob downward, but in that case max Y reached would always be 0
|
||||
let min_y_vel = 0;
|
||||
let max_y_vel = 5000; // idk
|
||||
|
||||
(min_x_vel..=max_x_vel)
|
||||
.flat_map(|x_vel| (min_y_vel..=max_y_vel).map(move |y_vel| (x_vel, y_vel)))
|
||||
.filter_map(|(x_vel, y_vel)| throw(x_vel, y_vel, &area))
|
||||
.max()
|
||||
.context("couldn't find any trajectory")
|
||||
}
|
||||
|
||||
fn throw(mut xvel: isize, mut yvel: isize, area: &TargetArea) -> Option<isize> {
|
||||
let (mut pos_x, mut pos_y) = (0, 0);
|
||||
let mut highest_y = 0;
|
||||
|
||||
loop {
|
||||
if area.contains(pos_x, pos_y) {
|
||||
return Some(highest_y);
|
||||
}
|
||||
|
||||
// Three cases where we can stop here:
|
||||
// - probe is lower than area, and gravity pulls it even lower
|
||||
// - probe is on the left, and x velocity goes left
|
||||
// - probe is on the right, and x velocity goes right
|
||||
if (pos_y < area.min_y() && yvel <= 0)
|
||||
|| (xvel <= 0 && pos_x < area.min_x())
|
||||
|| (xvel >= 0 && pos_x > area.max_x())
|
||||
{
|
||||
// the probe will never reach the area, we can stop the simulation here
|
||||
return None;
|
||||
}
|
||||
|
||||
// - The probe's x position increases by its x velocity.
|
||||
pos_x += xvel;
|
||||
// - The probe's y position increases by its y velocity.
|
||||
pos_y += yvel;
|
||||
// - Due to drag, the probe's x velocity changes by 1 toward the value 0; that is, it
|
||||
// decreases by 1 if it is greater than 0, increases by 1 if it is less than 0, or does not
|
||||
// change if it is already 0.
|
||||
match xvel.cmp(&0) {
|
||||
Ordering::Less => xvel += 1,
|
||||
Ordering::Greater => xvel -= 1,
|
||||
_ => {}
|
||||
}
|
||||
// - Due to gravity, the probe's y velocity decreases by 1.
|
||||
yvel -= 1;
|
||||
|
||||
// update highest seen y
|
||||
highest_y = highest_y.max(pos_y);
|
||||
}
|
||||
}
|
||||
|
||||
struct TargetArea {
|
||||
x_range: RangeInclusive<isize>,
|
||||
y_range: RangeInclusive<isize>,
|
||||
}
|
||||
|
||||
impl TargetArea {
|
||||
fn contains(&self, x: isize, y: isize) -> bool {
|
||||
self.x_range.contains(&x) && self.y_range.contains(&y)
|
||||
}
|
||||
|
||||
fn min_y(&self) -> isize {
|
||||
*self.y_range.start()
|
||||
}
|
||||
|
||||
fn min_x(&self) -> isize {
|
||||
*self.x_range.start()
|
||||
}
|
||||
|
||||
fn max_x(&self) -> isize {
|
||||
*self.x_range.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for TargetArea {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let s = s
|
||||
.trim()
|
||||
.strip_prefix("target area: ")
|
||||
.context("missing target area while parsing")?;
|
||||
|
||||
let (x, y) = s.split_once(", ").context("couldn't split on comma")?;
|
||||
|
||||
let x = x.strip_prefix("x=").context("couldn't find `x=`")?;
|
||||
let (min_x, max_x) = x.split_once("..").context("couldn't split on `..`")?;
|
||||
let (min_x, max_x) = (min_x.parse()?, max_x.parse()?);
|
||||
|
||||
let y = y.strip_prefix("y=").context("couldn't find `y=`")?;
|
||||
let (min_y, max_y) = y.split_once("..").context("couldn't split on `..`")?;
|
||||
let (min_y, max_y) = (min_y.parse()?, max_y.parse()?);
|
||||
|
||||
Ok(Self {
|
||||
x_range: min_x..=max_x,
|
||||
y_range: min_y..=max_y,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const PROVIDED: &str = include_str!("../input/day17_provided.txt");
|
||||
|
||||
#[test]
|
||||
fn part1_provided() {
|
||||
assert_eq!(part1(PROVIDED).unwrap(), 45);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part1_real() {
|
||||
assert_eq!(part1(INPUT).unwrap(), 4186);
|
||||
}
|
||||
}
|
|
@ -16,3 +16,4 @@ pub mod day13;
|
|||
pub mod day14;
|
||||
pub mod day15;
|
||||
pub mod day16;
|
||||
pub mod day17;
|
||||
|
|
|
@ -18,6 +18,7 @@ use aoc2021::day13;
|
|||
use aoc2021::day14;
|
||||
use aoc2021::day15;
|
||||
use aoc2021::day16;
|
||||
use aoc2021::day17;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let days: &[DayFunc] = &[
|
||||
|
@ -37,6 +38,7 @@ fn main() -> Result<()> {
|
|||
day14::run,
|
||||
day15::run,
|
||||
day16::run,
|
||||
day17::run,
|
||||
];
|
||||
|
||||
aoc::run(days)
|
||||
|
|
Loading…
Reference in a new issue