Solution for 2022, day 22, part one
This commit is contained in:
parent
da2bde0fdf
commit
113e9cf107
12
2022/day22-part1/Cargo.toml
Normal file
12
2022/day22-part1/Cargo.toml
Normal 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"] }
|
||||||
259
2022/day22-part1/src/main.rs
Normal file
259
2022/day22-part1/src/main.rs
Normal 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());
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user