diff --git a/src/bin/day22.rs b/src/bin/day22.rs index 5854926..ec03bca 100644 --- a/src/bin/day22.rs +++ b/src/bin/day22.rs @@ -5,11 +5,28 @@ use aoc_2022::*; type Input = MonkeyMap; type Output = isize; -type Position = Vector2D; +type Position = Vector2D; type Direction = Vector2D; +lazy_static! { + static ref DIRECTIONS: HashMap = HashMap::from([ + (Direction::new(1, 0), 0), + (Direction::new(0, 1), 1), + (Direction::new(-1, 0), 2), + (Direction::new(0, -1), 3) + ]); +} + +fn turn_left(d: Direction) -> Direction { + Direction::new(d.y(), -d.x()) +} + +fn turn_right(d: Direction) -> Direction { + Direction::new(-d.y(), d.x()) +} + enum Instruction { - Move(usize), + Move(isize), TurnLeft, TurnRight, } @@ -19,143 +36,238 @@ enum Orientation { Horizontal(usize), Vertical(usize), } +impl Orientation { + fn horizontal(width: usize) -> Self { + Self::Horizontal(width) + } + fn vertical(height: usize) -> Self { + Self::Vertical(height) + } +} struct MonkeyMap { map: Vec>, - boundaries: HashMap>, + boundaries: HashMap>, instructions: Vec, } -fn wrapping_step(position: usize, diff: isize, range: &Range) -> usize { +fn wrapping_step(position: isize, diff: isize, range: &Range) -> isize { let (lower, upper) = (range.start, range.end); - let range_size = (upper - lower) as isize; + let range_size = upper - lower; - (lower as isize + (position as isize + diff - lower as isize + range_size) % range_size) - as usize + lower + (position + diff - lower + range_size) % range_size } -trait Wrap { - fn next(&self, state: &State<'_>) -> (Position, Direction); +trait Wrap: Clone { + type State; + + // simulation + fn is_blocked(&self) -> bool; + fn step(&mut self, steps: isize); + fn turn_left(&mut self); + fn turn_right(&mut self); + + // movement + fn next(&self) -> (Self::State, Direction); + + // final answer + fn answer(&self) -> Output; } -struct Wrap2D; -impl Wrap for Wrap2D { - fn next(&self, state: &State<'_>) -> (Position, Direction) { - let h_bound = &state.input.boundaries[&Orientation::Horizontal(state.position.y())]; - let v_bound = &state.input.boundaries[&Orientation::Vertical(state.position.x())]; - - ( - Position::new( - wrapping_step(state.position.x(), state.direction.x(), h_bound), - wrapping_step(state.position.y(), state.direction.y(), v_bound), - ), - state.direction, - ) - } -} - -struct Face { - horizontal_bound: Range, - vertical_bound: Range, -} - -struct Wrap3D { - faces: Vec, -} -impl Wrap3D { - fn face(&self, position: Position) -> &Face { - let (x, y) = (position.x(), position.y()); - - for f in &self.faces {} - todo!() - } -} -impl Wrap for Wrap3D { - fn next(&self, state: &State<'_>) -> (Position, Direction) { - let face = self.face(state.position); - - ( - Position::new( - wrapping_step( - state.position.x(), - state.direction.x(), - &face.horizontal_bound, - ), - wrapping_step( - state.position.y(), - state.direction.y(), - &face.vertical_bound, - ), - ), - state.direction, - ) - } -} - -struct State<'a> { +#[derive(Clone, Copy)] +struct Wrap2D<'a> { input: &'a Input, position: Position, direction: Direction, } - -impl<'a> State<'a> { - fn new(input: &'a Input) -> State<'a> { +impl<'a> Wrap2D<'a> { + fn new(input: &'a Input) -> Wrap2D<'a> { Self { input, position: Position::new(input.boundaries[&Orientation::Horizontal(0)].start, 0), direction: Direction::new(1, 0), } } +} +impl Wrap for Wrap2D<'_> { + type State = Position; - fn is_blocked(&self, wrapper: &impl Wrap) -> bool { - let (next_position, _) = wrapper.next(self); + // simulation + fn is_blocked(&self) -> bool { + let (next_position, _) = self.next(); self.input.map[next_position] == '#' } - fn step(&mut self, wrapper: &impl Wrap, steps: usize) { + fn step(&mut self, steps: isize) { for _ in 0..steps { - if self.is_blocked(wrapper) { + if self.is_blocked() { return; } - (self.position, self.direction) = wrapper.next(self); + (self.position, self.direction) = self.next(); } } fn turn_left(&mut self) { - let previous = self.direction; - self.direction = Direction::new(previous.y(), -previous.x()); + self.direction = turn_left(self.direction); } fn turn_right(&mut self) { - let previous = self.direction; - self.direction = Direction::new(-previous.y(), previous.x()); + self.direction = turn_right(self.direction); + } + + // movement + fn next(&self) -> (Self::State, Direction) { + let h_bound = &self.input.boundaries[&Orientation::Horizontal(self.position.y() as usize)]; + let v_bound = &self.input.boundaries[&Orientation::Vertical(self.position.x() as usize)]; + + ( + Position::new( + wrapping_step(self.position.x(), self.direction.x(), h_bound), + wrapping_step(self.position.y(), self.direction.y(), v_bound), + ), + self.direction, + ) + } + + // final answer + fn answer(&self) -> Output { + 1000 * (self.position.y() + 1) + + 4 * (self.position.x() + 1) + + DIRECTIONS[&self.direction] as isize } } -lazy_static! { - static ref DIRECTIONS: HashMap<(isize, isize), usize> = - HashMap::from([((1, 0), 0), ((0, 1), 1), ((-1, 0), 2), ((0, -1), 3)]); +#[derive(Clone)] +struct Face { + horizontal_bound: Range, + vertical_bound: Range, + continuation: Vec<(usize, usize)>, +} +impl Face { + fn normalized(&self, p: Position) -> Position { + Position::new( + p.x() + self.horizontal_bound.start, + p.y() + self.vertical_bound.start, + ) + } } -fn solve(wrapper: &impl Wrap, input: &Input) -> Output { - let final_state = input +#[derive(Debug, Clone)] +struct State3D { + face: usize, + position: Position, +} +impl State3D { + fn global_position(&self, faces: &[Face]) -> Position { + faces[self.face].normalized(self.position) + } +} + +#[derive(Clone)] +struct Wrap3D<'a> { + input: &'a Input, + state: State3D, + direction: Direction, + faces: Vec, +} +impl<'a> Wrap3D<'a> { + fn new(configuration: Vec, input: &'a Input) -> Wrap3D<'a> { + Self { + input, + state: State3D { + face: 0, + position: Position::new(0, 0), + }, + direction: Direction::new(1, 0), + faces: configuration, + } + } + + fn face(&self) -> &Face { + &self.faces[self.state.face] + } +} +impl Wrap for Wrap3D<'_> { + type State = State3D; + + // simulation + fn is_blocked(&self) -> bool { + let (next_state, _) = self.next(); + self.input.map[next_state.global_position(&self.faces)] == '#' + } + + fn step(&mut self, steps: isize) { + for _ in 0..steps { + if self.is_blocked() { + return; + } + (self.state, self.direction) = self.next(); + } + } + + fn turn_left(&mut self) { + self.direction = turn_left(self.direction); + } + + fn turn_right(&mut self) { + self.direction = turn_right(self.direction); + } + + // movement + fn next(&self) -> (Self::State, Direction) { + let side = self.faces[0].horizontal_bound.len() as isize; + let wraps = + |pos: Position| pos.x() < 0 || pos.x() >= side || pos.y() < 0 || pos.y() >= side; + + let mut next_face = self.state.face; + let mut position = self.state.position + self.direction; + let mut direction = self.direction; + + if wraps(position) { + let (face, rotations) = self.face().continuation[DIRECTIONS[&self.direction]]; + + next_face = face; + position = Position::new((position.x() + side) % side, (position.y() + side) % side); + + for _ in 0..rotations { + position = Position::new(side - position.y() - 1, position.x()); + direction = turn_right(direction) + } + } + + ( + State3D { + face: next_face, + position, + }, + direction, + ) + } + + // final answer + fn answer(&self) -> Output { + 1000 * (self.state.global_position(&self.faces).y() + 1) + + 4 * (self.state.global_position(&self.faces).x() + 1) + + DIRECTIONS[&self.direction] as isize + } +} + +fn solve(initial_state: impl Wrap, input: &Input) -> Output { + input .instructions .iter() - .fold(State::new(input), |mut state, y| { + .fold(initial_state, |previous_state, y| { + let mut state = previous_state; + match &y { - Instruction::Move(steps) => state.step(wrapper, *steps), + Instruction::Move(steps) => state.step(*steps), Instruction::TurnLeft => state.turn_left(), Instruction::TurnRight => state.turn_right(), } state - }); - - (1000 * (final_state.position.y() + 1) - + 4 * (final_state.position.x() + 1) - + DIRECTIONS[&(final_state.direction.x(), final_state.direction.y())]) - .try_into() - .unwrap() + }) + .answer() } struct Day22; @@ -169,27 +281,39 @@ impl Solution for Day22 { 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 find_boundaries = |constructor: fn(usize) -> Orientation, + iterator: &mut dyn Iterator, + upper_bound, + i| { + let mut first_non_empty = iterator.enumerate().skip_while(|&(_, &c)| c == ' '); + let start = first_non_empty.next().unwrap().0 as isize; let mut last_non_empty = first_non_empty.skip_while(|&(_, &c)| c != ' '); - let end = last_non_empty.next().unwrap_or((map[row].len(), &'_')).0; + let end = last_non_empty.next().unwrap_or((upper_bound, &'_')).0 as isize; - boundaries.insert(Orientation::Horizontal(row), start..end); - } + boundaries.insert(constructor(i), start..end); + (constructor(i), 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; + // construct all horizontal boundaries + (0..map.len()).for_each(|row| { + find_boundaries( + Orientation::horizontal, + &mut map[row].iter(), + map[row].len(), + row, + ); + }); - 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); - } + // construct all vertical boundaries + (0..map[0].len()).for_each(|col| { + find_boundaries( + Orientation::vertical, + &mut ColumnIterator::new(&map, col), + map.len(), + col, + ); + }); let unparsed_instructions_str = instructions.trim_end(); @@ -204,7 +328,7 @@ impl Solution for Day22 { .collect(); if !steps.is_empty() { // found a move instruction - instructions.push(Instruction::Move(steps.parse::().unwrap())); + instructions.push(Instruction::Move(steps.parse::().unwrap())); skip += steps.len(); continue; } @@ -225,12 +349,79 @@ impl Solution for Day22 { } fn part_1(input: &Input) -> Output { - solve(&Wrap2D, input) + solve(Wrap2D::new(input), input) } fn part_2(input: &Input) -> Output { - let wrapper = Wrap2D; - solve(&wrapper, input) + // sample + let config = vec![ + Face { + horizontal_bound: 8..12, + vertical_bound: 0..4, + continuation: vec![(5, 2), (3, 0), (2, 1), (1, 2)], + }, + Face { + horizontal_bound: 0..4, + vertical_bound: 4..8, + continuation: vec![(2, 0), (4, 2), (5, 3), (0, 2)], + }, + Face { + horizontal_bound: 4..8, + vertical_bound: 4..8, + continuation: vec![(3, 0), (4, 3), (1, 0), (0, 1)], + }, + Face { + horizontal_bound: 8..12, + vertical_bound: 4..8, + continuation: vec![(5, 1), (4, 0), (2, 0), (0, 0)], + }, + Face { + horizontal_bound: 8..12, + vertical_bound: 8..12, + continuation: vec![(5, 0), (1, 2), (2, 1), (3, 0)], + }, + Face { + horizontal_bound: 12..16, + vertical_bound: 8..12, + continuation: vec![(0, 2), (1, 2), (4, 0), (3, 3)], + }, + ]; + + // challenge input + // let config = vec![ + // Face { + // horizontal_bound: 50..100, + // vertical_bound: 0..50, + // continuation: vec![(1, 0), (2, 0), (3, 2), (5, 1)], + // }, + // Face { + // horizontal_bound: 100..150, + // vertical_bound: 0..50, + // continuation: vec![(4, 2), (2, 1), (0, 0), (5, 0)], + // }, + // Face { + // horizontal_bound: 50..100, + // vertical_bound: 50..100, + // continuation: vec![(1, 3), (4, 0), (3, 3), (0, 0)], + // }, + // Face { + // horizontal_bound: 0..50, + // vertical_bound: 100..150, + // continuation: vec![(4, 0), (5, 0), (0, 2), (2, 1)], + // }, + // Face { + // horizontal_bound: 50..100, + // vertical_bound: 100..150, + // continuation: vec![(1, 2), (5, 1), (3, 0), (2, 0)], + // }, + // Face { + // horizontal_bound: 0..50, + // vertical_bound: 150..200, + // continuation: vec![(4, 3), (1, 0), (0, 3), (3, 0)], + // }, + // ]; + + solve(Wrap3D::new(config, input), input) } }