diff --git a/aoc2021/input/day21.txt b/aoc2021/input/day21.txt new file mode 100644 index 0000000..94d668d --- /dev/null +++ b/aoc2021/input/day21.txt @@ -0,0 +1,2 @@ +Player 1 starting position: 4 +Player 2 starting position: 2 diff --git a/aoc2021/input/day21_provided.txt b/aoc2021/input/day21_provided.txt new file mode 100644 index 0000000..3f69194 --- /dev/null +++ b/aoc2021/input/day21_provided.txt @@ -0,0 +1,2 @@ +Player 1 starting position: 4 +Player 2 starting position: 8 diff --git a/aoc2021/src/day21.rs b/aoc2021/src/day21.rs new file mode 100644 index 0000000..a926b65 --- /dev/null +++ b/aoc2021/src/day21.rs @@ -0,0 +1,109 @@ +use std::fmt::Write; +use std::iter; +use std::ops::RangeInclusive; + +use anyhow::{Context, Result}; + +const INPUT: &str = include_str!("../input/day21.txt"); + +pub fn run() -> Result { + let mut res = String::with_capacity(128); + + writeln!(res, "part 1: {}", part1(INPUT)?)?; + + Ok(res) +} + +fn part1(input: &str) -> Result { + let mut lines = input.lines(); + let mut player1_pos: PlayerPos = lines + .next() + .and_then(|line| line.trim().strip_prefix("Player 1 starting position: ")) + .and_then(|pos| pos.parse().ok()) + .map(PlayerPos::new) + .context("couldn't find player 1 pos")?; + let mut player2_pos: PlayerPos = lines + .next() + .and_then(|line| line.trim().strip_prefix("Player 2 starting position: ")) + .and_then(|pos| pos.parse().ok()) + .map(PlayerPos::new) + .context("couldn't find player 2 pos")?; + + let mut player1_score = 0; + let mut player2_score = 0; + let mut dice = DeterministicDice::new(); + + while player2_score < 1000 { + let mv = dice.next_3_sum(); + + player1_pos.advance_by(mv); + player1_score += player1_pos.pos(); + + std::mem::swap(&mut player1_pos, &mut player2_pos); + std::mem::swap(&mut player1_score, &mut player2_score); + } + + let loser_score = player1_score; + + Ok(loser_score * dice.rolls()) +} + +struct PlayerPos(usize); + +impl PlayerPos { + fn new(pos: usize) -> Self { + debug_assert!((1..=10).contains(&pos)); + + // represented from 0 to 9 for modulo ease of use + Self(pos - 1) + } + + fn advance_by(&mut self, mv: usize) { + self.0 = (self.0 + mv) % 10 + } + + fn pos(&self) -> usize { + self.0 + 1 + } +} + +struct DeterministicDice { + iter: iter::Cycle>, + rolls: usize, +} + +impl DeterministicDice { + fn new() -> Self { + Self { + iter: (1..=100).cycle(), + rolls: 0, + } + } + + fn next_3_sum(&mut self) -> usize { + self.rolls += 3; + + self.iter.next().unwrap() + self.iter.next().unwrap() + self.iter.next().unwrap() + } + + fn rolls(&self) -> usize { + self.rolls + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const PROVIDED: &str = include_str!("../input/day21_provided.txt"); + + #[test] + fn part1_provided() { + assert_eq!(part1(PROVIDED).unwrap(), 739785); + } + + #[test] + fn part1_real() { + assert_eq!(part1(INPUT).unwrap(), 908595); + } +} diff --git a/aoc2021/src/lib.rs b/aoc2021/src/lib.rs index cf569f2..7e99714 100644 --- a/aoc2021/src/lib.rs +++ b/aoc2021/src/lib.rs @@ -17,3 +17,5 @@ pub mod day14; pub mod day15; pub mod day16; pub mod day17; + +pub mod day21; diff --git a/aoc2021/src/main.rs b/aoc2021/src/main.rs index 7dc5fe7..6ba207f 100644 --- a/aoc2021/src/main.rs +++ b/aoc2021/src/main.rs @@ -20,6 +20,8 @@ use aoc2021::day15; use aoc2021::day16; use aoc2021::day17; +use aoc2021::day21; + fn main() -> Result<()> { let days: &[DayFunc] = &[ day01::run, @@ -39,6 +41,7 @@ fn main() -> Result<()> { day15::run, day16::run, day17::run, + day21::run, ]; aoc::run(days)