diff --git a/samples/day22.txt b/samples/day22.txt new file mode 100644 index 0000000..8bc2e1e --- /dev/null +++ b/samples/day22.txt @@ -0,0 +1,14 @@ + ...# + .#.. + #... + .... +...#.......# +........#... +..#....#.... +..........#. + ...#.... + .....#.. + .#...... + ......#. + +10R5L5R10L4R5L5 diff --git a/src/bin/day22.rs b/src/bin/day22.rs new file mode 100644 index 0000000..74b043e --- /dev/null +++ b/src/bin/day22.rs @@ -0,0 +1,215 @@ +use std::{collections::HashMap, hash::Hash}; + +use aoc_2022::*; + +type Input = MonkeyMap; +type Output = isize; + +enum Instruction { + Move(usize), + TurnLeft, + TurnRight, +} + +#[derive(Debug, PartialEq, Eq, Hash)] +enum Orientation { + Horizontal(usize), + Vertical(usize), +} + +struct MonkeyMap { + map: Vec>, + boundaries: HashMap, + instructions: Vec, +} + +fn step_with_wrap(position: usize, diff: isize, lower: usize, upper: usize) -> usize { + let range_size = (upper - lower) as isize; + (lower as isize + (position as isize + diff - lower as isize + range_size) % range_size) + as usize +} + +struct State<'a> { + input: &'a Input, + y: usize, + x: usize, + direction: (isize, isize), +} + +impl<'a> State<'a> { + fn new(input: &'a Input) -> State<'a> { + Self { + input, + y: 0, + x: input.boundaries[&Orientation::Horizontal(0)].0, + direction: (1, 0), + } + } + + fn next(&self) -> (usize, usize) { + let h_bound = self.input.boundaries[&Orientation::Horizontal(self.y)]; + let v_bound = self.input.boundaries[&Orientation::Vertical(self.x)]; + + let (dx, dy) = self.direction; + + ( + step_with_wrap(self.x, dx, h_bound.0, h_bound.1), + step_with_wrap(self.y, dy, v_bound.0, v_bound.1), + ) + } + + fn is_blocked(&self) -> bool { + let (x, y) = self.next(); + self.input.map[y][x] == '#' + } + + fn step(&mut self, steps: usize) { + for _ in 0..steps { + if self.is_blocked() { + return; + } + (self.x, self.y) = self.next(); + } + } + + fn turn_left(&mut self) { + let (x, y) = self.direction; + self.direction = (y, -x); + } + + fn turn_right(&mut self) { + let (x, y) = self.direction; + self.direction = (-y, x); + } +} + +lazy_static! { + static ref DIRECTIONS: HashMap<(isize, isize), usize> = + HashMap::from([((1, 0), 0), ((0, 1), 1), ((-1, 0), 2), ((0, -1), 3)]); +} + +struct Day22; +impl Solution for Day22 { + fn parse_input>(pathname: P) -> Input { + let input = file_to_string(pathname); + let (map, instructions) = input.split_once("\n\n").unwrap(); + + let mut map: Vec> = map.lines().map(|l| l.chars().collect()).collect(); + let max_row_length = map.iter().map(|l| l.len()).max().unwrap(); + map.iter_mut().for_each(|l| l.resize(max_row_length, ' ')); + + let mut boundaries = HashMap::new(); + for row in 0..map.len() { + let mut first_non_empty = map[row].iter().enumerate().skip_while(|&(_, &c)| c == ' '); + let start = first_non_empty.next().unwrap().0; + + let mut last_non_empty = first_non_empty.skip_while(|&(_, &c)| c != ' '); + let end = last_non_empty.next().unwrap_or((map[row].len(), &'_')).0; + + boundaries.insert(Orientation::Horizontal(row), (start, end)); + } + + for col in 0..map[0].len() { + let mut first_non_empty = ColumnIterator::new(&map, col) + .enumerate() + .skip_while(|&(_, &c)| c == ' '); + let start = first_non_empty.next().unwrap().0; + + let mut last_non_empty = first_non_empty.skip_while(|&(_, &c)| c != ' '); + let end = last_non_empty.next().unwrap_or((map.len(), &'_')).0; + + boundaries.insert(Orientation::Vertical(col), (start, end)); + } + + let unparsed_instructions_str = instructions.trim_end(); + + let it = unparsed_instructions_str.chars(); + let mut skip = 0; + let mut instructions = Vec::new(); + while skip < unparsed_instructions_str.len() { + let steps: String = it + .clone() + .skip(skip) + .take_while(|c| c.is_digit(10)) + .collect(); + if !steps.is_empty() { + // found a move instruction + instructions.push(Instruction::Move(steps.parse::().unwrap())); + skip += steps.len(); + continue; + } + + match it.clone().skip(skip).next().unwrap() { + 'L' => instructions.push(Instruction::TurnLeft), + 'R' => instructions.push(Instruction::TurnRight), + x => panic!("Invalid turn: {}", x), + } + skip += 1; + } + + MonkeyMap { + map, + boundaries, + instructions, + } + } + + fn part_1(input: &Input) -> Output { + let final_state = input + .instructions + .iter() + .fold(State::new(input), |mut state, y| { + match &y { + Instruction::Move(steps) => state.step(*steps), + Instruction::TurnLeft => state.turn_left(), + Instruction::TurnRight => state.turn_right(), + } + + state + }); + + (1000 * (final_state.y + 1) + 4 * (final_state.x + 1) + DIRECTIONS[&final_state.direction]) + .try_into() + .unwrap() + } + + fn part_2(_input: &Input) -> Output { + todo!() + } +} + +fn main() -> Result<()> { + // Day22::run("sample") + Day22::main() +} + +test_sample!(day_22, Day22, 6032, 5031); + +#[cfg(test)] +mod day_22_extended { + #[test] + fn test_expression_positive() { + let (l, u) = (4, 9); + let d = 1; + + let mut positions = vec![6]; + for _ in 0..6 { + positions.push(l + (positions.last().unwrap() + d - l + (u - l)) % (u - l)); + } + + assert_eq!(positions, vec![6, 7, 8, 4, 5, 6, 7]); + } + + #[test] + fn test_expression_negative() { + let (l, u) = (4, 9); + let d = -1; + + let mut positions = vec![6]; + for _ in 0..6 { + positions.push(l + (positions.last().unwrap() + d - l + (u - l)) % (u - l)); + } + + assert_eq!(positions, vec![6, 5, 4, 8, 7, 6, 5]); + } +}