Solution for 2022, day 22, part one

This commit is contained in:
Tobias Marschner 2024-09-22 14:57:31 +02:00
parent da2bde0fdf
commit 113e9cf107
2 changed files with 271 additions and 0 deletions

View File

@ -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"] }

View File

@ -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<MapCell, Dim<[usize; 2]>>;
/// 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<String> = std::env::args().collect();
if args.len() < 2 {
panic!("Usage: ./main <input-file>\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::<String>();
match (is_number, chunk.chars().next().unwrap()) {
(true, _) => Command::Movement(chunk.parse::<u32>().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::<MapCell, _>::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());
}