diff --git a/2022/src/day17.rs b/2022/src/day17.rs index 9ce0f0e..7b54a44 100644 --- a/2022/src/day17.rs +++ b/2022/src/day17.rs @@ -45,23 +45,13 @@ fn collides(shape: &[&[bool]], cavern: &IndexSet, x: usize, y: usize) -> bool { false } -pub fn part1(input: &[u8]) -> Result { - // Poor man's trim() - let input = if input[input.len() - 1] == b'\n' { - &input[..input.len() - 1] - } else { - input - }; - - let mut cavern = IndexSet::default(); - let mut max_height = 0; - - let mut gusts = input - .iter() - .cycle() - .map(|&b| if b == b'<' { -1 } else { 1 }); - - for &shape in SHAPES.iter().cycle().take(2022) { +fn simulate( + mut cavern: IndexSet, + shapes: impl Iterator, + mut gusts: impl Iterator, + mut max_height: usize, +) -> usize { + for &shape in shapes { let mut x = 2usize; let mut y = max_height + shape.len() + 2; @@ -95,11 +85,140 @@ pub fn part1(input: &[u8]) -> Result { } } - Ok(max_height.to_string()) + max_height } -pub fn part2(_input: &[u8]) -> Result { - anyhow::bail!("not implemented") +pub fn part1(input: &[u8]) -> Result { + // Poor man's trim() + let input = if input[input.len() - 1] == b'\n' { + &input[..input.len() - 1] + } else { + input + }; + + let gusts = input + .iter() + .cycle() + .map(|&b| if b == b'<' { -1 } else { 1 }); + + Ok(simulate( + IndexSet::default(), + SHAPES.iter().cycle().take(2022), + gusts, + 0, + ) + .to_string()) +} + +#[derive(Clone, Copy, Debug)] +struct HeightMap([usize; 7]); + +impl HeightMap { + fn max(&self) -> &usize { + self.0.iter().max().unwrap() + } +} + +impl PartialEq for HeightMap { + fn eq(&self, other: &Self) -> bool { + let self_min = *self.0.iter().min().unwrap(); + let other_min = *other.0.iter().min().unwrap(); + + self.0 + .iter() + .zip(&other.0) + .all(|(&a, &b)| a - self_min == b - other_min) + } +} + +pub fn part2(input: &[u8]) -> Result { + // Poor man's trim() + let input = if input[input.len() - 1] == b'\n' { + &input[..input.len() - 1] + } else { + input + }; + + let mut cavern = IndexSet::default(); + + let mut height_map = HeightMap([0; 7]); + + let mut shapes = SHAPES.iter().enumerate().cycle(); + let mut gusts = input + .iter() + .map(|&b| if b == b'<' { -1 } else { 1 }) + .enumerate() + .cycle(); + + let mut tortoise = (0, 0, height_map); + let mut tortoise_time = 0; + let mut last_gust = 0; + + const TARGET: usize = 1000000000000; + + for (it, (shape_id, &shape)) in shapes.by_ref().enumerate() { + let mut x = 2usize; + let mut y = height_map.max() + shape.len() + 2; + + // Acquire gust of wind + for (gust_id, offset) in gusts.by_ref() { + last_gust = gust_id; + if let Some(nx) = x.checked_add_signed(offset) { + if !collides(shape, &cavern, nx, y) { + x = nx; + } + } else { + // Hit the left wall + } + + // Move down if possible + if y >= shape.len() && !collides(shape, &cavern, x, y - 1) { + y -= 1; + } else { + break; + } + } + + // If we get here we've successfully stopped falling + for (row, line) in shape.iter().enumerate() { + for (col, &on) in line.iter().enumerate() { + if on { + height_map.0[x + col] = height_map.0[x + col].max(y - row + 1); + cavern.insert((y - row) * WIDTH + x + col); + } + } + } + + // See if we found a circle + let hare_time = it + 1; + let hare = (shape_id, last_gust, height_map); + + if hare == tortoise { + let cycle_len = hare_time - tortoise_time; + let remaining = TARGET - hare_time; + let to_skip = remaining / cycle_len; + let remainder = remaining % cycle_len; + + let current_max = *height_map.max(); + + // All of them rose by the same amount so we just need to compare the first one + let delta = height_map.0[0] - tortoise.2 .0[0]; + + let result = simulate( + cavern, + shapes.map(|(_, shape)| shape).take(remainder), + gusts.map(|(_, offset)| offset), + current_max, + ) + delta * to_skip; + + return Ok(result.to_string()); + } else if it.count_ones() == 1 { + tortoise = hare; + tortoise_time = hare_time; + } + } + + Ok(height_map.max().to_string()) } #[cfg(test)] @@ -112,4 +231,9 @@ mod tests { fn sample_part1() { assert_eq!(part1(SAMPLE).unwrap(), "3068"); } + + #[test] + fn sample_part2() { + assert_eq!(part2(SAMPLE).unwrap(), "1514285714288"); + } }