diff --git a/2022/src/bin/day22.rs b/2022/src/bin/day22.rs index bd74ec9..4c2f0a3 100644 --- a/2022/src/bin/day22.rs +++ b/2022/src/bin/day22.rs @@ -1,6 +1,6 @@ #![feature(test)] use core::fmt; -use std::str::FromStr; +use std::rc::Rc; use anyhow::Result; use aoc::Solver; @@ -42,14 +42,18 @@ mod tests { } } -#[derive(PartialEq, Eq)] +const PLANES: usize = 4; + +type Transform = Rc Player>>; + +#[derive(Copy, Clone, PartialEq, Eq)] enum Tile { Void, Open, Wall, } -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] enum Direction { Right, Down, @@ -106,125 +110,263 @@ struct Vec2 { y: usize, } -struct Map { - map: Vec>, - pos: Vec2, +impl Vec2 { + fn new(x: usize, y: usize) -> Self { + Self { x, y } + } +} + +#[derive(Copy, Clone)] +struct Player { + plane: Vec2, + position: Vec2, direction: Direction, } -impl FromStr for Map { - type Err = anyhow::Error; +#[derive(Clone)] +struct Plane { + grid: Vec>, + neighbours: Vec>, +} - fn from_str(s: &str) -> Result { - let map = s +impl Plane { + fn new(size: usize) -> Plane { + let grid = vec![vec![Tile::Void; size]; size]; + + let mut neighbours = Vec::with_capacity(4); + for _ in 0..4 { + neighbours.push(None); + } + + Plane { grid, neighbours } + } +} + +struct Map { + planes: Vec>>, + player: Player, + size: usize, +} + +impl Map { + fn new(input: &str, size: usize) -> Self { + let mut planes = vec![vec![None; PLANES]; PLANES]; + + input .lines() - .take_while(|line| !line.is_empty()) - .map(|line| { + .enumerate() + .take_while(|(_, line)| !line.is_empty()) + .for_each(|(y, line)| { line .chars() - .map(Tile::from) - .collect::>() - }) - .collect::>(); + .enumerate() + .for_each(|(x, c)| { + let tile = Tile::from(c); + if tile == Tile::Void { + return; + } - // Get the starting point - let x = map[0].iter().position(|t| *t == Tile::Open).unwrap(); - let pos = Vec2 { - x, - y: 0, + let plane = &mut planes[y/size][x/size]; + if plane.is_none() { + *plane = Some(Plane::new(size)); + } + + if let Some(plane) = plane { + plane.grid[y % size][x % size] = tile; + } + }); + }); + + let x = planes[0].iter().position(|plane| plane.is_some()).unwrap(); + + let player = Player { + plane: Vec2::new(x, 0), + position: Vec2::new(0, 0), + direction: Direction::Right, }; - Ok(Self { map, pos, direction: Direction::Right }) + Self { planes, player, size } + } + + fn _print(&self) { + for y in 0..self.size*PLANES { + for x in 0..self.size*PLANES { + if let Some(plane) = &self.planes[y/self.size][x/self.size] { + if self.player.plane.x == x/self.size && self.player.plane.y == y/self.size && self.player.position.x == x%self.size && self.player.position.y == y%self.size { + print!("$"); + } else { + let tile = &plane.grid[y%self.size][x%self.size]; + print!("{tile}"); + } + } else { + print!(" "); + } + } + println!(""); + } } } impl Map { - fn movement(&mut self, steps: usize) { - for _ in 0..steps { - let mut np = self.pos; - match self.direction { - Direction::Right => { - np.x += 1; - // Wrap around if we walk out of the map on the right - if np.x >= self.map[np.y].len() { - np.x = 0; - - // Move over the void, we only do this when we wrap around, since there are - // no void tiles on the right - while self.map[np.y][np.x] == Tile::Void { - np.x += 1; - } - } - }, - Direction::Left => { - // Make sure we do not underflow - if np.x == 0 { - np.x = self.map[np.y].len(); - } - - // Update our location - np.x -= 1; - - // Jump over the void - if self.map[np.y][np.x] == Tile::Void { - np.x = self.map[np.y].len()-1; - } - }, - Direction::Up => { - // Make sure we do not underflow - if np.y == 0 { - np.y = self.map.len(); - } - - // Update our location - np.y -= 1; - - // Jump over the void - if np.x >= self.map[np.y].len() || self.map[np.y][np.x] == Tile::Void { - np.y = self.map.len()-1; - } - - while np.x >= self.map[np.y].len() || self.map[np.y][np.x] == Tile::Void { - np.y -= 1; - } - }, - Direction::Down => { - // Update our location - np.y += 1; - - // Wrap around - if np.y >= self.map.len() { - np.y = 0; - } - - // Jump over the void - if np.x >= self.map[np.y].len() || self.map[np.y][np.x] == Tile::Void { - np.y = 0; - } - - while np.x >= self.map[np.y].len() || self.map[np.y][np.x] == Tile::Void { - np.y += 1; - } - } - } - - // If the space is open we update our location, otherwise we stay where we are - if self.map[np.y][np.x] == Tile::Open { - self.pos = np; - } else { - // There is a wall in front of us, so no point in trying the rest of the - // steps - break; + fn movement(&mut self, moves: Vec<(usize, Option)>) { + for m in moves { + self.step(m.0); + if let Some(clockwise) = m.1 { + self.rotate(clockwise); } } } + fn get_neighbour(&self, plane: &Vec2, direction: &Direction) -> &(Vec2, Transform) { + // Get the position of the neighbour + let plane = self.planes[plane.y][plane.x].as_ref().unwrap(); + plane.neighbours[usize::from(direction)].as_ref().unwrap() + } + + fn step(&mut self, steps: usize) { + for _ in 0..steps { + // Make a copy of the player + let mut np = self.player; + + match self.player.direction { + Direction::Right => { + // Move the new player + np.position.x += 1; + + // We walk out of the current plane + if np.position.x >= self.size { + // Get the position of the neighbour + let neighbour = self.get_neighbour(&np.plane, &np.direction); + np.plane = neighbour.0; + + // Update the coordinates of the player + np = neighbour.1(np); + } + }, + Direction::Left => { + // Move the new player + let temp = np.position.x as isize - 1; + + // We walk out of the current plane + if temp < 0 { + // Get the position of the neighbour + let neighbour = self.get_neighbour(&np.plane, &np.direction); + np.plane = neighbour.0; + + // Update the coordinates of the player + np = neighbour.1(np); + } else { + np.position.x -= 1; + } + + }, + Direction::Down => { + // Move the new player + np.position.y += 1; + + // We walk out of the current plane + if np.position.y >= self.size { + // Get the position of the neighbour + let neighbour = self.get_neighbour(&np.plane, &np.direction); + np.plane = neighbour.0; + + // Update the coordinates of the player + np = neighbour.1(np); + } + }, + Direction::Up => { + // Move the new player + let temp = np.position.y as isize - 1; + + // We walk out of the current plane + if temp < 0 { + // Get the position of the neighbour + let neighbour = self.get_neighbour(&np.plane, &np.direction); + np.plane = neighbour.0; + + // Update the coordinates of the player + np = neighbour.1(np); + } else { + np.position.y -= 1; + } + + }, + } + + // Get the current plane + let plane = self.planes[np.plane.y][np.plane.x].as_ref().unwrap(); + + // Make sure the new location is free + if plane.grid[np.position.y][np.position.x] == Tile::Wall { + // We are done moving + break; + } + + // Update the player location + self.player = np; + } + } + fn rotate(&mut self, clockwise: bool) { - self.direction.rotate(clockwise); + self.player.direction.rotate(clockwise); } fn score(&self) -> usize { - 1000 * (self.pos.y+1) + 4 * (self.pos.x+1) + (usize::from(&self.direction)) + 1000 * (self.player.plane.y*self.size + self.player.position.y + 1) + 4 * (self.player.plane.x*self.size + self.player.position.x + 1) + (usize::from(&self.player.direction)) + } + + // Connect the planes together according to the rules in part 1 + fn fill_neighbours_part1(&mut self) { + let size = self.size; + for y in 0..PLANES { + for x in 0..PLANES { + if self.planes[y][x].is_some() { + // Check up neighbour + { + let y_neighbour = (0..PLANES).rev().cycle().skip(PLANES-y).take(PLANES).find(|y| self.planes[*y][x].is_some()).unwrap(); + self.planes[y][x].as_mut().unwrap().neighbours[usize::from(&Direction::Up)] = Some((Vec2::new(x, y_neighbour), Rc::new(Box::new(move |mut p: Player| { + p.position.y = size-1; + return p; + })))); + } + + // Check down neighbour + { + let y_neighbour = (0..PLANES).cycle().skip(y+1).take(PLANES).find(|y| self.planes[*y][x].is_some()).unwrap(); + self.planes[y][x].as_mut().unwrap().neighbours[usize::from(&Direction::Down)] = Some((Vec2::new(x, y_neighbour), Rc::new(Box::new(move |mut p: Player| { + // This happens post move + p.position.y = 0; + return p; + })))); + } + + // Check left neighbour + { + let x_neighbour = (0..PLANES).rev().cycle().skip(PLANES-x).take(PLANES).find(|x| self.planes[y][*x].is_some()).unwrap(); + self.planes[y][x].as_mut().unwrap().neighbours[usize::from(&Direction::Left)] = Some((Vec2::new(x_neighbour, y), Rc::new(Box::new(move |mut p: Player| { + p.position.x = size-1; + return p; + })))); + } + + // Check right neighbour + { + let x_neighbour = (0..PLANES).cycle().skip(x+1).take(PLANES).find(|x| self.planes[y][*x].is_some()).unwrap(); + self.planes[y][x].as_mut().unwrap().neighbours[usize::from(&Direction::Right)] = Some((Vec2::new(x_neighbour, y), Rc::new(Box::new(move |mut p: Player| { + // This happens post move + p.position.x = 0; + return p; + })))); + } + } + } + } + } + + fn fill_neighbours_part2(&mut self) { + { + + } } } @@ -246,6 +388,10 @@ fn parse_movement(input: &str) -> Vec<(usize, Option)> { }).collect() } +fn is_test(input: &str) -> bool { + input.len() == 189 +} + // -- Solution -- pub struct Day; impl aoc::Solver for Day { @@ -257,21 +403,36 @@ impl aoc::Solver for Day { } fn part1(input: &str) -> Self::Output1 { + // Split the input into the two different parts let (map, movement) = input.split_once("\n\n").unwrap(); - let mut map = Map::from_str(map).unwrap(); - let movement = parse_movement(movement); - for mov in movement { - map.movement(mov.0); - if let Some(clockwise) = mov.1 { - map.rotate(clockwise); - } - } + // Create the map + let size = if is_test(input) { 4 } else { 50 }; + let mut map = Map::new(map, size); + + // Connect the planes together + map.fill_neighbours_part1(); + + // Create the movement instructions and execute them + let moves = parse_movement(movement); + map.movement(moves); map.score() } fn part2(input: &str) -> Self::Output2 { - 0 + // Split the input into the two different parts + let (map, movement) = input.split_once("\n\n").unwrap(); + + // Create the map + let size = if is_test(input) { 4 } else { 50 }; + let mut map = Map::new(map, size); + + map.fill_neighbours_part2(); + + let moves = parse_movement(movement); + map.movement(moves); + + map.score() } }