From 113e9cf107c7a522ca10d156bc5fb67fd9ac2bc1 Mon Sep 17 00:00:00 2001 From: Tobias Marschner Date: Sun, 22 Sep 2024 14:57:31 +0200 Subject: [PATCH] Solution for 2022, day 22, part one --- 2022/day22-part1/Cargo.toml | 12 ++ 2022/day22-part1/src/main.rs | 259 +++++++++++++++++++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 2022/day22-part1/Cargo.toml create mode 100644 2022/day22-part1/src/main.rs diff --git a/2022/day22-part1/Cargo.toml b/2022/day22-part1/Cargo.toml new file mode 100644 index 0000000..0160b0c --- /dev/null +++ b/2022/day22-part1/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "day22-part1" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rayon = "1" +itertools = "0" +id-arena = { version = "2", features = ["rayon"] } +ndarray = { version = "0", features = ["rayon"] } diff --git a/2022/day22-part1/src/main.rs b/2022/day22-part1/src/main.rs new file mode 100644 index 0000000..2161459 --- /dev/null +++ b/2022/day22-part1/src/main.rs @@ -0,0 +1,259 @@ +use itertools::Itertools; +use ndarray::prelude::*; + +/// The state each cell in the map can be in. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum MapCell { + /// Terrain where the player can be. + Open, + /// Terrain where the player cannot be. + Wall, + /// Terrain that is not part of the map. + /// The player wraps around if they are on this tile. + Void, +} + +type MonkeyMap = Array>; + +/// A single command from the input's command-chain. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum Command { + Movement(u32), + TurnRight, + TurnLeft, +} + +/// The four directions the player can face. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum Direction { + Right = 0, + Down, + Left, + Up, +} + +impl Direction { + /// Returns the direction you'd get when turning right. + fn turn_right(&self) -> Direction { + match self { + Direction::Up => Direction::Right, + Direction::Down => Direction::Left, + Direction::Left => Direction::Up, + Direction::Right => Direction::Down, + } + } + + /// Returns the direction you'd get when turning left. + fn turn_left(&self) -> Direction { + match self { + Direction::Up => Direction::Left, + Direction::Down => Direction::Right, + Direction::Left => Direction::Down, + Direction::Right => Direction::Up, + } + } +} + +/// A single instance of a player existing on the map. +#[derive(Debug, Clone)] +struct Player<'a> { + x_pos: usize, + y_pos: usize, + direction: Direction, + monkey_map: &'a MonkeyMap, +} + +impl Player<'_> { + /// Run the simulation for the given list of commands. + fn run_simulation(&mut self, commands: &[Command]) { + for com in commands { + // self.print_map(); + match com { + Command::TurnLeft => { + self.direction = self.direction.turn_left(); + } + Command::TurnRight => { + self.direction = self.direction.turn_right(); + } + Command::Movement(mv) => { + for _ in 0..*mv { + self.move_one_tile(); + } + } + } + } + } + + /// Move the player by one tile along the direction they're facing. + fn move_one_tile(&mut self) { + let height = self.monkey_map.dim().0; + let width = self.monkey_map.dim().1; + + // Keep track of our original position. + let og_x = self.x_pos; + let og_y = self.y_pos; + + loop { + // Determine the theoretical new coordianates. + let (new_x, new_y) = match self.direction { + Direction::Up => (self.x_pos as i32, self.y_pos as i32 - 1), + Direction::Down => (self.x_pos as i32, self.y_pos as i32 + 1), + Direction::Left => (self.x_pos as i32 - 1, self.y_pos as i32), + Direction::Right => (self.x_pos as i32 + 1, self.y_pos as i32), + }; + + // Check if they're out-of-bounds and wrap them around, if so. + let new_x = if new_x < 0 { + width - 1 + } else if new_x >= width as i32 { + 0usize + } else { + new_x as usize + }; + + let new_y = if new_y < 0 { + height - 1 + } else if new_y >= height as i32 { + 0usize + } else { + new_y as usize + }; + + // Next, check the kind of tile we've reached. + match self.monkey_map[[new_y, new_x]] { + MapCell::Open => { + // Free space? Great, then move there and return. + self.x_pos = new_x; + self.y_pos = new_y; + return; + } + MapCell::Wall => { + // Wall? We can't move there. + // Return to where we started (in case we moved through the void) and return. + self.x_pos = og_x; + self.y_pos = og_y; + return; + } + MapCell::Void => { + // If we're in the Void we need to wrap around, i.e. keep moving. + // Update the position but don't return just yet. + // This way we'll run another loop iteration. + self.x_pos = new_x; + self.y_pos = new_y; + } + } + } + } + + /// Calculate the final solution as outlined in the task description. + fn calculate_solution(&self) -> usize { + ((self.y_pos + 1) * 1000) + ((self.x_pos + 1) * 4) + (self.direction as usize) + } + + /// Print the current state of the map as well as the player's location for debugging. + #[allow(dead_code)] + fn print_map(&self) { + println!(); + + let height = self.monkey_map.dim().0; + let width = self.monkey_map.dim().1; + + for y in 0..height { + for x in 0..width { + if x == self.x_pos && y == self.y_pos { + match self.direction { + Direction::Up => print!("^"), + Direction::Down => print!("v"), + Direction::Left => print!("<"), + Direction::Right => print!(">"), + } + } else { + match self.monkey_map[[y, x]] { + MapCell::Void => print!(" "), + MapCell::Open => print!("."), + MapCell::Wall => print!("#"), + } + } + } + println!(); + } + } +} + +fn main() { + // Use command line arguments to specify the input filename. + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + panic!("Usage: ./main \nNo input file provided. Exiting."); + } + + // Next, read the contents of the input file into a string for easier processing. + let input = std::fs::read_to_string(&args[1]).expect("Error opening file"); + + // --- TASK BEGIN --- + let mut lines = input.lines().collect_vec(); + + // Parse the list of commands. + let commands = lines + .last() + .unwrap() + .chars() + .chunk_by(|e| e.is_ascii_digit()) + .into_iter() + .map(|(is_number, chunk)| { + let chunk = chunk.collect::(); + match (is_number, chunk.chars().next().unwrap()) { + (true, _) => Command::Movement(chunk.parse::().unwrap()), + (false, 'R') => Command::TurnRight, + (false, 'L') => Command::TurnLeft, + _ => panic!("could not parse series of movement commands"), + } + }) + .collect_vec(); + + // Get rid of the last two lines, as they're the only ones not part of the map. + lines.pop(); + lines.pop(); + + // Determine the height and width of the map. + let width = lines.iter().map(|e| e.len()).max().unwrap(); + let height = lines.len(); + // Allocate it using ndarray. + let mut monkey_map = Array::::from_elem((height, width), MapCell::Void); + // Now populate the map with the map-data. + for (y, line) in lines.iter().enumerate() { + for (x, c) in line.chars().enumerate() { + monkey_map[[y, x]] = match c { + ' ' => MapCell::Void, + '.' => MapCell::Open, + '#' => MapCell::Wall, + _ => panic!("unkown character detected in drawing of map"), + } + } + } + + // Determine the initial coordinates. + let (initial_y, initial_x) = monkey_map + .indexed_iter() + .find_map(|((y, x), c)| { + if *c != MapCell::Void { + Some((y, x)) + } else { + None + } + }) + .unwrap(); + // Construct a player out of this. + let mut player = Player { + y_pos: initial_y, + x_pos: initial_x, + direction: Direction::Right, + monkey_map: &monkey_map, + }; + + // And let the player run the simulation. + player.run_simulation(&commands); + + // Calculate and return the solution. + println!("Result: {}", player.calculate_solution()); +}